diff --git a/src/blokus_ui/static/game.js b/src/blokus_ui/static/game.js index 40eb28e..c082b2b 100644 --- a/src/blokus_ui/static/game.js +++ b/src/blokus_ui/static/game.js @@ -14,6 +14,7 @@ function initGame() { let selectedPieceId = null; let currentPlacements = []; + let gameSaved = false; async function refresh() { const response = await fetch(`/api/game/${sessionId}/state`); @@ -87,6 +88,7 @@ function initGame() { sidebar.appendChild(controls); document.getElementById('save-btn').addEventListener('click', saveReplay); + document.getElementById('status-save-btn').addEventListener('click', saveReplay); } function renderPiece(piece, playerColor) { @@ -194,27 +196,48 @@ function initGame() { const response = await fetch(`/api/game/${sessionId}/save`, { method: 'POST' }); const result = await response.json(); if (result.success) { + gameSaved = true; alert('Replay saved!'); } else { alert('Failed to save: ' + result.error); } } + async function autoSave() { + if (gameSaved) return; + try { + const response = await fetch(`/api/game/${sessionId}/save`, { method: 'POST' }); + const result = await response.json(); + if (result.success) { + gameSaved = true; + const statusBar = document.getElementById('status-bar'); + statusBar.textContent += ' | Replay auto-saved!'; + } + } catch (e) { + console.error('Auto-save failed:', e); + } + } + function renderStatusBar(state) { - const statusBar = document.getElementById('status-bar'); + const statusText = document.getElementById('status-text'); + const saveBtn = document.getElementById('status-save-btn'); + if (state.game_over) { if (state.winner !== null) { - statusBar.textContent = 'Game Over! Winner: ' + state.players[state.winner].name; + statusText.textContent = 'Game Over! Winner: ' + state.players[state.winner].name; } else { - statusBar.textContent = 'Game Over! Tie!'; + statusText.textContent = 'Game Over! Tie!'; } + saveBtn.style.display = 'none'; } else { - statusBar.textContent = 'Current player: ' + state.players[state.current_player].name; + statusText.textContent = 'Current player: ' + state.players[state.current_player].name; + saveBtn.style.display = 'inline-block'; } } function handleGameOver(state) { - const statusBar = document.getElementById('status-bar'); + const statusText = document.getElementById('status-text'); + const saveBtn = document.getElementById('status-save-btn'); let text = 'Game Over! '; if (state.winner !== null) { text += 'Winner: ' + state.players[state.winner].name; @@ -224,15 +247,24 @@ function initGame() { text += ' | Scores: ' + state.players.map(function(p) { return p.name + ': ' + p.score; }).join(', '); - statusBar.textContent = text; + statusText.textContent = text; + saveBtn.style.display = 'none'; document.querySelectorAll('.piece').forEach(function(p) { p.style.pointerEvents = 'none'; p.style.opacity = '0.5'; }); + + autoSave(); } refresh(); + + window.addEventListener('beforeunload', function() { + if (!gameSaved) { + navigator.sendBeacon(`/api/game/${sessionId}/save`); + } + }); } function initReplay() { diff --git a/src/blokus_ui/static/style.css b/src/blokus_ui/static/style.css index e110115..c994e7f 100644 --- a/src/blokus_ui/static/style.css +++ b/src/blokus_ui/static/style.css @@ -342,6 +342,24 @@ h3 { font-weight: bold; font-size: 1.05em; min-height: 24px; + display: flex; + justify-content: space-between; + align-items: center; +} + +#status-save-btn { + padding: 4px 12px; + background: var(--blue); + color: var(--bg); + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.9em; + font-weight: bold; +} + +#status-save-btn:hover { + background: #5bb3ff; } /* ===== Replay Page ===== */ diff --git a/src/blokus_ui/templates/game.html b/src/blokus_ui/templates/game.html index cb149cc..c89d421 100644 --- a/src/blokus_ui/templates/game.html +++ b/src/blokus_ui/templates/game.html @@ -15,7 +15,10 @@ -
Loading...
+
+ Loading... + +