Commit 231ba42e authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: a bot outage no longer freezes the board on "thinking…"

requestBotMove retried forever on failure and did nothing at all when the engine
answered without a move, so a Stockfish hiccup left the player watching the
thinking indicator with no way to continue.

Now: four attempts with exponential backoff, then a dialog offering to end the
game. Also guards against gameState having been torn down mid-request.

The engine itself is healthy — verified live: GET /api/chess/bots returns the
seven bots the client expects, and POST /api/chess/move answers the start
position with d2d4 at depth 3. api/bots.php's paths are correct.

Arabic and English strings added for the new message.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent 34f9459c
......@@ -43,6 +43,7 @@ const strings = {
'game.you_lose': 'خسرت',
'game.draw_result': 'تعادل',
'game.thinking': 'يفكر...',
'game.bot_unavailable': 'الخصم الآلي لا يستجيب. هل تريد إنهاء هذه المباراة؟',
'game.your_turn': 'دورك',
'game.opponent_turn': 'دور الخصم',
'rank.title': 'الترتيب',
......@@ -757,6 +758,7 @@ const strings = {
'game.you_lose': 'You Lose',
'game.draw_result': 'Draw',
'game.thinking': 'Thinking...',
'game.bot_unavailable': 'The computer opponent is not responding. End this game?',
'game.your_turn': 'Your Turn',
'game.opponent_turn': "Opponent's Turn",
'rank.title': 'Rankings',
......
......@@ -448,10 +448,11 @@ function executeMove(el, from, to, promotion) {
}
}
async function requestBotMove(el) {
async function requestBotMove(el, attempt = 0) {
if (!gameState || gameState.gameOver) return;
gameState.botThinking = true;
const thinkingEl = el.querySelector('#bot-thinking');
thinkingEl.style.display = 'block';
if (thinkingEl) thinkingEl.style.display = 'block';
try {
const data = await net.post('bots.php', {
......@@ -460,9 +461,17 @@ async function requestBotMove(el) {
bot_id: gameState.botId
});
thinkingEl.style.display = 'none';
if (!gameState) return;
if (thinkingEl) thinkingEl.style.display = 'none';
gameState.botThinking = false;
// An engine that answers but cannot produce a move leaves the board frozen
// on "thinking…" forever unless we say something.
if (!data.best_move && !gameState.gameOver) {
botUnavailable(el);
return;
}
if (data.best_move && !gameState.gameOver) {
const from = data.best_move.substring(0, 2);
const to = data.best_move.substring(2, 4);
......@@ -505,12 +514,38 @@ async function requestBotMove(el) {
}
}
} catch (e) {
thinkingEl.style.display = 'none';
if (!gameState) return;
if (thinkingEl) thinkingEl.style.display = 'none';
gameState.botThinking = false;
setTimeout(() => requestBotMove(el), 2000);
// This used to retry forever, so an engine outage left the player staring at
// "thinking…" with no way forward. Back off, then give up gracefully.
if (attempt < 4) {
const delay = 1000 * Math.pow(2, attempt);
setTimeout(() => requestBotMove(el, attempt + 1), delay);
} else {
botUnavailable(el);
}
}
}
/** The engine is not answering. Tell the player and let them leave. */
function botUnavailable(el) {
if (!gameState || gameState.gameOver) return;
gameState.botThinking = false;
const thinkingEl = el.querySelector('#bot-thinking');
if (thinkingEl) thinkingEl.style.display = 'none';
mpLogFront('bot_unavailable', { botId: gameState.botId });
modal.confirm(t('game.bot_unavailable') || 'The computer opponent is not responding. End this game?', {
title: t('game.bot') || 'Bot',
icon: '🤖',
confirmText: t('common.confirm') || 'OK',
cancelText: t('common.cancel') || 'Cancel'
}).then(ok => {
if (ok) endGame('draw', 'aborted');
});
}
function updateCapturedDisplay(el) {
const pieceSymbols = { p: '♟', n: '♞', b: '♝', r: '♜', q: '♛', k: '♚' };
const pieceOrder = ['q', 'r', 'b', 'n', 'p'];
......
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