v0.10.0 fix to engine game state mgmt, client and web ui updates to match

This commit is contained in:
2026-07-23 14:03:09 -04:00
parent a23ed1fb21
commit 21dea47694
17 changed files with 741 additions and 549 deletions
@@ -120,9 +120,11 @@ function updateAuthIndicator(authenticated) {
if (authenticated) {
light.setAttribute('data-status', 'authenticated');
indicator.setAttribute('data-status', gameState.username);
indicator.setAttribute('data-tooltip', 'Account');
} else {
light.setAttribute('data-status', 'anonymous');
indicator.setAttribute('data-status', 'anonymous');
indicator.setAttribute('data-status', 'click to login');
indicator.setAttribute('data-tooltip', 'Login');
}
}
@@ -438,6 +440,9 @@ function updateTurnIndicator(state, turn) {
status = 'unknown';
tooltipText = 'Game Over';
}
} else if (state === 'stuck') {
status = 'degraded';
tooltipText = 'Engine Error';
} else if (turn === 'w') {
status = 'white';
tooltipText = 'White';
@@ -637,7 +642,10 @@ async function startNewGame() {
initializeBoard();
updateGameDisplay(game);
document.getElementById('undo-btn').disabled = true;
if (!gameState.isPlayerWhite) triggerComputerMove();
const computerTurn = gameState.isPlayerWhite ? 'b' : 'w';
if (isPlayable(game.state) && game.turn === computerTurn) {
triggerComputerMove();
}
setModalMessage('new-game-modal-message', `Game started - you play ${willBePlayerWhite ? 'White' : 'Black'}`, 'success');
setTimeout(hideNewGameModal, MODAL_SUCCESS_DISPLAY_MS);
@@ -711,7 +719,7 @@ function handleSquareClick(e) {
if (gameState.isLocked) return;
// Block moves after game over
if (isGameOver(gameState.state)) return;
if (!isPlayable(gameState.state)) return;
const squareEl = e.currentTarget;
const { square, pieceColor } = squareEl.dataset;
@@ -776,7 +784,7 @@ async function handleHumanMove(from, to) {
flashSquare(fromEl, true);
flashSquare(toEl, true);
updateGameDisplay(game);
if (!isGameOver(game.state)) {
if (isPlayable(game.state)) {
triggerComputerMove();
}
} catch (error) {
@@ -906,6 +914,9 @@ async function undoMoves() {
const game = await response.json();
gameState.state = game.state;
updateGameDisplay(game);
if (game.state === 'stuck') {
flashErrorMessage('Engine error — Undo to recover or start a new game');
}
} catch (error) {
if (error.message === 'Failed to fetch') {
handleApiError('undo', error);
@@ -1021,6 +1032,9 @@ function markMatedKing(game) {
function isGameOver(state) {
return ['white wins', 'black wins', 'stalemate', 'draw'].includes(state);
}
function isPlayable(state) {
return !isGameOver(state) && state !== 'stuck';
}
function handleApiError(action, error, response = null) {
let serverStatus = 'degraded';
@@ -647,18 +647,26 @@ input[type="range"]::-webkit-slider-thumb {
}
/* Auth Indicator */
.auth-indicator {
cursor: pointer;
border: 1px solid transparent;
border-radius: 6px;
transition: border-color .2s, background .2s;
}
.auth-indicator:hover {
border-color: var(--host-royal);
background: rgba(95, 87, 245, 0.15);
}
.auth-indicator .light[data-status="anonymous"] {
color: var(--tokyo-border);
color: var(--tokyo-yellow);
}
.auth-indicator .light[data-status="authenticated"] {
color: var(--tokyo-green);
}
.auth-indicator {
cursor: pointer;
}
/* --- Modal status message (Issue 2) --- */
.modal-message {
display: none;
@@ -871,41 +879,20 @@ input[type="range"]:disabled {
}
@media (max-width: 530px) {
body {
overflow-y: auto;
overflow-x: auto;
min-width: clamp(440px, 100vw, 530px);
body { min-width: 0; overflow-x: hidden; }
.outer-container { width: 100%; min-width: 0; }
.container { width: calc(100% - 16px); min-width: 0; }
.board-container {
width: min(92vw, 440px);
height: min(92vw, 440px);
padding: 12px;
}
.outer-container {
width: clamp(440px, 100vw, 530px);
min-width: clamp(440px, 100vw, 530px);
padding: 8px;
min-height: 100vh;
height: auto;
display: flex;
justify-content: center;
align-items: flex-start;
overflow: visible;
.board-wrapper {
width: calc(min(92vw, 440px) - 24px);
height: calc(min(92vw, 440px) - 24px);
}
.container {
width: calc(100% - 16px);
min-width: clamp(424px, calc(100vw - 16px), 514px);
border-radius: 12px;
min-height: calc(100vh - 16px);
height: auto;
padding: 1rem;
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
overflow: visible;
}
.board-container,
.info-panel {
width: clamp(360px, 83vw, 440px);
min-width: clamp(360px, 83vw, 440px);
width: min(92vw, 440px);
min-width: 0;
}
}