132 lines
3.2 KiB
JavaScript
132 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
const CpuGame = (() => {
|
|
const SCORES_KEY = 'ttt-scores';
|
|
const CPU_DELAY = 600;
|
|
|
|
const state = {
|
|
board: Array(9).fill(null),
|
|
turn: 'X',
|
|
gameOver: false,
|
|
scores: { you: 0, opponent: 0, draws: 0 },
|
|
};
|
|
|
|
let cpuTimer = 0;
|
|
|
|
function persistScores() {
|
|
try {
|
|
localStorage.setItem(SCORES_KEY, JSON.stringify(state.scores));
|
|
} catch (e) { /* storage unavailable */ }
|
|
}
|
|
|
|
function loadScores() {
|
|
try {
|
|
const raw = localStorage.getItem(SCORES_KEY);
|
|
if (raw) {
|
|
const s = JSON.parse(raw);
|
|
state.scores = {
|
|
you: Number(s.you) || 0,
|
|
opponent: Number(s.cpu || s.opponent) || 0,
|
|
draws: Number(s.draws) || 0,
|
|
};
|
|
}
|
|
} catch (e) { /* corrupted storage */ }
|
|
}
|
|
|
|
function handleMove(i) {
|
|
if (state.gameOver || state.turn !== 'X' || state.board[i]) return;
|
|
state.board[i] = 'X';
|
|
App.renderMark(i, 'X');
|
|
const result = App.checkWinner(state.board);
|
|
if (result) {
|
|
endRound(result);
|
|
return;
|
|
}
|
|
state.turn = 'O';
|
|
App.renderStatus('CPU is thinking…', 'thinking');
|
|
cpuTimer = setTimeout(cpuMove, CPU_DELAY);
|
|
}
|
|
|
|
function cpuMove() {
|
|
if (state.gameOver) return;
|
|
const empty = [];
|
|
state.board.forEach((v, i) => { if (v === null) empty.push(i); });
|
|
if (!empty.length) return;
|
|
const i = empty[Math.floor(Math.random() * empty.length)];
|
|
state.board[i] = 'O';
|
|
App.renderMark(i, 'O');
|
|
const result = App.checkWinner(state.board);
|
|
if (result) {
|
|
endRound(result);
|
|
return;
|
|
}
|
|
state.turn = 'X';
|
|
App.renderStatus('Your turn', 'normal');
|
|
}
|
|
|
|
function endRound(result) {
|
|
state.gameOver = true;
|
|
let title;
|
|
let key;
|
|
if (result.winner === 'X') {
|
|
key = 'you';
|
|
title = 'You Win!';
|
|
App.renderStatus('You win!', 'normal');
|
|
} else if (result.winner === 'O') {
|
|
key = 'opponent';
|
|
title = 'CPU Wins';
|
|
App.renderStatus('CPU wins', 'normal');
|
|
document.getElementById('board').classList.add('shake');
|
|
} else {
|
|
key = 'draws';
|
|
title = "It's a Draw";
|
|
App.renderStatus("It's a draw", 'normal');
|
|
document.getElementById('board').classList.add('pulse');
|
|
}
|
|
|
|
state.scores[key]++;
|
|
persistScores();
|
|
App.renderScores(state.scores);
|
|
|
|
if (result.line) App.drawWinLine(result.line);
|
|
|
|
setTimeout(() => {
|
|
App.showOverlay(title);
|
|
if (result.winner === 'X') App.burstConfetti();
|
|
}, result.winner === 'draw' ? 400 : 600);
|
|
}
|
|
|
|
function newRound() {
|
|
clearTimeout(cpuTimer);
|
|
state.board = Array(9).fill(null);
|
|
state.turn = 'X';
|
|
state.gameOver = false;
|
|
App.clearBoard();
|
|
App.renderStatus('Your turn', 'normal');
|
|
}
|
|
|
|
function resetScores() {
|
|
state.scores = { you: 0, opponent: 0, draws: 0 };
|
|
persistScores();
|
|
App.renderScores(state.scores);
|
|
App.showToast('Scores reset');
|
|
}
|
|
|
|
function destroy() {
|
|
clearTimeout(cpuTimer);
|
|
}
|
|
|
|
function init() {
|
|
clearTimeout(cpuTimer);
|
|
App.clearBoard();
|
|
state.board = Array(9).fill(null);
|
|
state.turn = 'X';
|
|
state.gameOver = false;
|
|
loadScores();
|
|
App.renderScores(state.scores);
|
|
App.renderStatus('Your turn', 'normal');
|
|
}
|
|
|
|
return { init, handleMove, newRound, resetScores, destroy };
|
|
})();
|