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:
@@ -14,6 +14,7 @@ function initGame() {
|
|||||||
|
|
||||||
let selectedPieceId = null;
|
let selectedPieceId = null;
|
||||||
let currentPlacements = [];
|
let currentPlacements = [];
|
||||||
|
let gameSaved = false;
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
const response = await fetch(`/api/game/${sessionId}/state`);
|
const response = await fetch(`/api/game/${sessionId}/state`);
|
||||||
@@ -87,6 +88,7 @@ function initGame() {
|
|||||||
sidebar.appendChild(controls);
|
sidebar.appendChild(controls);
|
||||||
|
|
||||||
document.getElementById('save-btn').addEventListener('click', saveReplay);
|
document.getElementById('save-btn').addEventListener('click', saveReplay);
|
||||||
|
document.getElementById('status-save-btn').addEventListener('click', saveReplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPiece(piece, playerColor) {
|
function renderPiece(piece, playerColor) {
|
||||||
@@ -194,27 +196,48 @@ function initGame() {
|
|||||||
const response = await fetch(`/api/game/${sessionId}/save`, { method: 'POST' });
|
const response = await fetch(`/api/game/${sessionId}/save`, { method: 'POST' });
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
gameSaved = true;
|
||||||
alert('Replay saved!');
|
alert('Replay saved!');
|
||||||
} else {
|
} else {
|
||||||
alert('Failed to save: ' + result.error);
|
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) {
|
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.game_over) {
|
||||||
if (state.winner !== null) {
|
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 {
|
} else {
|
||||||
statusBar.textContent = 'Game Over! Tie!';
|
statusText.textContent = 'Game Over! Tie!';
|
||||||
}
|
}
|
||||||
|
saveBtn.style.display = 'none';
|
||||||
} else {
|
} 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) {
|
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! ';
|
let text = 'Game Over! ';
|
||||||
if (state.winner !== null) {
|
if (state.winner !== null) {
|
||||||
text += 'Winner: ' + state.players[state.winner].name;
|
text += 'Winner: ' + state.players[state.winner].name;
|
||||||
@@ -224,15 +247,24 @@ function initGame() {
|
|||||||
text += ' | Scores: ' + state.players.map(function(p) {
|
text += ' | Scores: ' + state.players.map(function(p) {
|
||||||
return p.name + ': ' + p.score;
|
return p.name + ': ' + p.score;
|
||||||
}).join(', ');
|
}).join(', ');
|
||||||
statusBar.textContent = text;
|
statusText.textContent = text;
|
||||||
|
saveBtn.style.display = 'none';
|
||||||
|
|
||||||
document.querySelectorAll('.piece').forEach(function(p) {
|
document.querySelectorAll('.piece').forEach(function(p) {
|
||||||
p.style.pointerEvents = 'none';
|
p.style.pointerEvents = 'none';
|
||||||
p.style.opacity = '0.5';
|
p.style.opacity = '0.5';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
autoSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (!gameSaved) {
|
||||||
|
navigator.sendBeacon(`/api/game/${sessionId}/save`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initReplay() {
|
function initReplay() {
|
||||||
|
|||||||
@@ -342,6 +342,24 @@ h3 {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.05em;
|
font-size: 1.05em;
|
||||||
min-height: 24px;
|
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 ===== */
|
/* ===== Replay Page ===== */
|
||||||
|
|||||||
@@ -15,7 +15,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
<script>
|
||||||
window.GAME_SESSION_ID = "{{ session_id }}";
|
window.GAME_SESSION_ID = "{{ session_id }}";
|
||||||
|
|||||||
Reference in New Issue
Block a user