document.addEventListener('DOMContentLoaded', function() {
if (document.getElementById('game-container')) {
initGame();
} else if (document.getElementById('replay-container')) {
initReplay();
}
});
function initGame() {
const sessionId = window.GAME_SESSION_ID;
const boardSize = window.BOARD_SIZE;
const cellSize = window.CELL_SIZE || 30;
const cellGap = 1;
let selectedPieceId = null;
let currentPlacements = [];
let gameSaved = false;
async function refresh() {
const response = await fetch(`/api/game/${sessionId}/state`);
const state = await response.json();
renderBoard(state.board, boardSize, cellSize);
renderSidebar(state);
renderStatusBar(state);
if (state.game_over) {
handleGameOver(state);
}
}
function renderBoard(board, size, cs) {
const boardEl = document.getElementById('board');
boardEl.innerHTML = '';
boardEl.style.gridTemplateColumns = `repeat(${size}, ${cs}px)`;
boardEl.style.gridTemplateRows = `repeat(${size}, ${cs}px)`;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const cell = document.createElement('div');
cell.className = `cell player-${board[y][x]}`;
boardEl.appendChild(cell);
}
}
}
function renderSidebar(state) {
const sidebar = document.getElementById('sidebar');
sidebar.innerHTML = '';
const playerInfo = document.createElement('div');
playerInfo.id = 'player-info';
state.players.forEach(function(player) {
const el = document.createElement('div');
el.className = 'player-info';
el.innerHTML =
'' + player.name + '' +
'' +
'Score: ' + player.score + '' +
'' + player.pieces_remaining + '/' + player.total_pieces + '' +
'';
playerInfo.appendChild(el);
});
sidebar.appendChild(playerInfo);
const piecesEl = document.createElement('div');
piecesEl.id = 'pieces';
const piecesTitle = document.createElement('h3');
piecesTitle.textContent = 'Your Pieces';
piecesEl.appendChild(piecesTitle);
if (state.available_pieces.length === 0) {
const emptyMsg = document.createElement('p');
emptyMsg.className = 'empty';
emptyMsg.textContent = 'No pieces remaining';
piecesEl.appendChild(emptyMsg);
} else {
state.available_pieces.forEach(function(piece) {
piecesEl.appendChild(renderPiece(piece, state.players[state.human_player].color));
});
}
sidebar.appendChild(piecesEl);
const controls = document.createElement('div');
controls.id = 'controls';
controls.innerHTML =
'' +
'Back to Menu';
sidebar.appendChild(controls);
document.getElementById('save-btn').addEventListener('click', saveReplay);
document.getElementById('status-save-btn').addEventListener('click', saveReplay);
}
function renderPiece(piece, playerColor) {
const container = document.createElement('div');
container.className = 'piece';
container.dataset.pieceId = piece.id;
const width = Math.max.apply(null, piece.squares.map(function(s) { return s[0]; })) + 1;
const height = Math.max.apply(null, piece.squares.map(function(s) { return s[1]; })) + 1;
container.style.gridTemplateColumns = 'repeat(' + width + ', 1fr)';
container.style.gridTemplateRows = 'repeat(' + height + ', 1fr)';
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const cell = document.createElement('div');
cell.className = 'piece-cell';
const isFilled = piece.squares.some(function(s) { return s[0] === x && s[1] === y; });
if (isFilled) {
cell.classList.add('filled');
cell.style.background = getPlayerColor(playerColor);
}
container.appendChild(cell);
}
}
container.addEventListener('click', function() { selectPiece(piece.id); });
return container;
}
function getPlayerColor(name) {
var colors = { red: '#ff6b6b', blue: '#4dabf7', yellow: '#ffd43b', green: '#51cf66' };
return colors[name] || '#ff6b6b';
}
async function selectPiece(pieceId) {
if (selectedPieceId === pieceId) {
selectedPieceId = null;
clearPlacements();
document.querySelectorAll('.piece').forEach(function(p) { p.classList.remove('selected'); });
return;
}
selectedPieceId = pieceId;
document.querySelectorAll('.piece').forEach(function(p) { p.classList.remove('selected'); });
var selectedEl = document.querySelector('.piece[data-piece-id="' + pieceId + '"]');
if (selectedEl) { selectedEl.classList.add('selected'); }
const response = await fetch(`/api/game/${sessionId}/placements?piece_id=${pieceId}`);
const data = await response.json();
currentPlacements = data.placements;
renderPlacements(currentPlacements);
}
function renderPlacements(placements) {
const overlay = document.getElementById('placement-overlay');
overlay.innerHTML = '';
if (placements.length === 0) {
return;
}
placements.forEach(function(placement) {
placement.cells.forEach(function(cell) {
const x = cell[0], y = cell[1];
const el = document.createElement('div');
el.className = 'placement-cell';
el.style.left = (x * (cellSize + cellGap)) + 'px';
el.style.top = (y * (cellSize + cellGap)) + 'px';
el.style.width = cellSize + 'px';
el.style.height = cellSize + 'px';
el.dataset.action = placement.action;
el.addEventListener('click', function() { makeMove(placement.action); });
overlay.appendChild(el);
});
});
}
function clearPlacements() {
currentPlacements = [];
document.getElementById('placement-overlay').innerHTML = '';
}
async function makeMove(action) {
const response = await fetch(`/api/game/${sessionId}/move`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: action }),
});
const result = await response.json();
if (result.success) {
selectedPieceId = null;
clearPlacements();
document.querySelectorAll('.piece').forEach(function(p) { p.classList.remove('selected'); });
await refresh();
} else {
console.error('Move failed:', result.error);
}
}
async function saveReplay() {
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 statusText = document.getElementById('status-text');
const saveBtn = document.getElementById('status-save-btn');
if (state.game_over) {
if (state.winner !== null) {
statusText.textContent = 'Game Over! Winner: ' + state.players[state.winner].name;
} else {
statusText.textContent = 'Game Over! Tie!';
}
saveBtn.style.display = 'none';
} else {
statusText.textContent = 'Current player: ' + state.players[state.current_player].name;
saveBtn.style.display = 'inline-block';
}
}
function handleGameOver(state) {
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;
} else {
text += 'Tie!';
}
text += ' | Scores: ' + state.players.map(function(p) {
return p.name + ': ' + p.score;
}).join(', ');
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() {
const boardSize = window.BOARD_SIZE;
const totalMoves = window.TOTAL_MOVES;
const replayData = window.REPLAY_DATA;
const cellSize = window.CELL_SIZE || 30;
const states = replayData.states;
const moves = replayData.moves;
const config = replayData.config;
const statistics = replayData.statistics;
let currentStep = 0;
let isPlaying = false;
let playInterval = null;
function renderStep(step) {
currentStep = step;
const state = states[step];
renderBoard(state.board, boardSize, cellSize);
renderMoveList(moves, step, config.human_player);
renderStatistics(statistics);
const slider = document.getElementById('step-slider');
slider.value = step;
slider.max = totalMoves;
document.getElementById('step-indicator').textContent = step + ' / ' + totalMoves;
document.getElementById('prev-btn').disabled = step === 0;
document.getElementById('next-btn').disabled = step >= totalMoves;
if (step >= totalMoves && isPlaying) {
stopPlay();
}
}
function renderBoard(board, size, cs) {
const boardEl = document.getElementById('board');
boardEl.innerHTML = '';
boardEl.style.gridTemplateColumns = 'repeat(' + size + ', ' + cs + 'px)';
boardEl.style.gridTemplateRows = 'repeat(' + size + ', ' + cs + 'px)';
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const cell = document.createElement('div');
cell.className = 'cell player-' + board[y][x];
boardEl.appendChild(cell);
}
}
}
function renderMoveList(moves, currentStep, humanPlayer) {
const listEl = document.getElementById('move-list');
listEl.innerHTML = '';
moves.forEach(function(move, index) {
const el = document.createElement('div');
el.className = 'move-entry';
if (index < currentStep) {
el.classList.add('played');
}
if (index === currentStep - 1) {
el.classList.add('current');
}
var playerColor = ['red', 'blue', 'yellow', 'green'][move.player_idx];
var isHuman = move.player_idx === humanPlayer ? ' (you)' : '';
el.innerHTML =
'' + (index + 1) + '' +
'P' + (move.player_idx + 1) + isHuman + '' +
'' + move.piece_name + '' +
'(' + move.x + ', ' + move.y + ')';
listEl.appendChild(el);
});
}
function renderStatistics(stats) {
const statsEl = document.getElementById('statistics');
statsEl.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Statistics';
statsEl.appendChild(title);
var rows = [
['Coverage', stats.coverage + '%'],
['Total Moves', stats.total_moves]
];
stats.squares_placed.forEach(function(score, i) {
rows.push(['P' + (i + 1) + ' Score', score]);
});
stats.pieces_placed.forEach(function(placed, i) {
rows.push(['P' + (i + 1) + ' Pieces', placed]);
});
rows.push(['Winner Margin', stats.winner_margin]);
rows.forEach(function(row) {
var r = document.createElement('div');
r.className = 'stat-row';
r.innerHTML = '' + row[0] + '' + row[1] + '';
statsEl.appendChild(r);
});
}
function togglePlay() {
if (isPlaying) {
stopPlay();
} else {
startPlay();
}
}
function startPlay() {
if (currentStep >= totalMoves) {
currentStep = 0;
}
isPlaying = true;
document.getElementById('play-btn').textContent = '\u23F8 Pause';
var speed = parseInt(document.getElementById('speed-select').value);
playInterval = setInterval(function() {
if (currentStep < totalMoves) {
renderStep(currentStep + 1);
} else {
stopPlay();
}
}, speed);
}
function stopPlay() {
isPlaying = false;
document.getElementById('play-btn').textContent = '\u25B6 Play';
if (playInterval) {
clearInterval(playInterval);
playInterval = null;
}
}
document.getElementById('prev-btn').addEventListener('click', function() {
if (currentStep > 0) {
renderStep(currentStep - 1);
}
});
document.getElementById('next-btn').addEventListener('click', function() {
if (currentStep < totalMoves) {
renderStep(currentStep + 1);
}
});
document.getElementById('play-btn').addEventListener('click', togglePlay);
document.getElementById('step-slider').addEventListener('input', function(e) {
var step = parseInt(e.target.value);
if (isPlaying) {
stopPlay();
}
renderStep(step);
});
renderStep(0);
}