feat: auto-save replays on game over and page unload

- Add autoSave() function that saves replay silently on game over
- Add beforeunload listener to save incomplete games when navigating away
- Add prominent 'Save Now' button in status bar (visible during play)
- Show 'Replay auto-saved!' notification in status bar
- Add gameSaved flag to prevent duplicate saves
This commit is contained in:
mattlamb227@gmail.com
2026-08-09 22:11:50 -04:00
parent ba6e26ab59
commit 10d8b0a376
3 changed files with 60 additions and 7 deletions
+38 -6
View File
@@ -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() {
+18
View File
@@ -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 ===== */
+4 -1
View File
@@ -15,7 +15,10 @@
</div>
</div>
</div>
<div id="status-bar">Loading...</div>
<div id="status-bar">
<span id="status-text">Loading...</span>
<button id="status-save-btn" style="display:none;">Save Now</button>
</div>
<script>
window.GAME_SESSION_ID = "{{ session_id }}";