Commit f49fc5cb authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: live chess — resign syncs, reconnect preserves color, clocks sync

Three critical multiplayer sync issues fixed:

1. Resign now notifies server (action:'resign') BEFORE ending locally.
   Opponent's polling detects status:'completed' + result and shows win.

2. Reconnect recovery completely rewritten:
   - Determines player color from match data (white_player_id vs userId)
   - Flips board correctly for black
   - Sets lastKnownMoveCount from server to prevent duplicate move processing
   - Detects if game already ended while disconnected (opponent resigned)
   - Restores canSelect with correct color check

3. sendLiveMove now includes clock times (white_time_remaining_ms,
   black_time_remaining_ms) so opponent sees accurate clocks.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 6d23a1f2
......@@ -35,20 +35,57 @@ export function mountGame(el, params) {
engine.create();
clock = new ChessClock(tc.time, tc.increment);
// If recovering from a refresh, fetch current state from server
// If recovering from a refresh, fetch current state from server and fix color
if (params.recovered && matchId) {
net.post('game.php', { action: 'get', match_id: matchId }).then(data => {
if (data && !data.error && data.current_fen) {
if (!data || data.error) return;
// If game already ended (opponent resigned while we were away)
if (data.status === 'completed') {
const myId = store.get('auth.userId');
const isWhite = data.white_player_id === myId;
const isWin = (data.result === 'white_wins' && isWhite) || (data.result === 'black_wins' && !isWhite);
endGame(isWin ? 'win' : data.result === 'draw' ? 'draw' : 'loss', 'resign');
return;
}
// Fix color: determine from match data which side we are
const myId = store.get('auth.userId');
if (data.white_player_id === myId) {
gameState.playerColor = 'w';
if (board) board.flipped = false;
} else {
gameState.playerColor = 'b';
if (board) board.flipped = true;
}
// Update board canSelect to use correct color
if (board) board.canSelect = (piece) => {
if (gameState.gameOver || gameState.botThinking) return false;
if (!gameState.isPlayerTurn) return false;
const pieceColor = piece === piece.toUpperCase() ? 'w' : 'b';
return pieceColor === gameState.playerColor;
};
// Load position
if (data.current_fen) {
engine.load(data.current_fen);
board?.setPosition(data.current_fen);
if (board) board.setPosition(data.current_fen);
gameState.moveCount = data.move_count || 0;
if (data.white_time_remaining_ms) clock.white = data.white_time_remaining_ms;
if (data.black_time_remaining_ms) clock.black = data.black_time_remaining_ms;
// Determine whose turn from FEN
lastKnownMoveCount = data.move_count || 0;
// Determine whose turn
const turnFromFen = data.current_fen.split(' ')[1];
gameState.isPlayerTurn = turnFromFen === playerColor;
gameState.isPlayerTurn = turnFromFen === gameState.playerColor;
if (!gameState.isPlayerTurn) clock.start(turnFromFen);
else clock.start(gameState.playerColor);
}
// Restore clocks
if (data.white_time_remaining_ms) clock.white = data.white_time_remaining_ms;
if (data.black_time_remaining_ms) clock.black = data.black_time_remaining_ms;
if (board) board.draw();
}).catch(() => {});
}
......@@ -161,12 +198,14 @@ export function mountGame(el, params) {
clock.onFlag = (color) => endGame(color === playerColor ? 'loss' : 'win', 'timeout');
// Controls
el.querySelector('#btn-resign').addEventListener('click', () => {
el.querySelector('#btn-resign').addEventListener('click', async () => {
if (gameState.gameOver) return;
if (confirm('هل أنت متأكد من الاستسلام؟')) {
audio.play('gameOver', 'game');
endGame('loss', 'resign');
if (!confirm('هل أنت متأكد من الاستسلام؟')) return;
audio.play('gameOver', 'game');
if (gameState.mode === 'live' && gameState.matchId) {
await net.post('game.php', { action: 'resign', match_id: gameState.matchId }).catch(() => {});
}
endGame('loss', 'resign');
});
el.querySelector('#btn-draw').addEventListener('click', () => {
if (gameState.gameOver) return;
......@@ -527,7 +566,6 @@ let lastKnownMoveCount = 0;
async function sendLiveMove(el) {
if (!gameState.matchId) return;
gameState.moveCount;
lastKnownMoveCount = gameState.moveCount;
try {
await net.post('game.php', {
......@@ -535,7 +573,9 @@ async function sendLiveMove(el) {
match_id: gameState.matchId,
fen: engine.fen(),
move: JSON.stringify(gameState.moveHistory),
move_count: gameState.moveCount
move_count: gameState.moveCount,
white_time_remaining_ms: clock.white,
black_time_remaining_ms: clock.black
});
} catch (e) {}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment