"use strict"; // Board rendering shared by the online game (app.js) and practice mode (practice.js). const MARKS = ["X", "O"]; const PIECE_SVG = [ '', '', ]; // Fills `container` with nine cells. `onPlay(cell)` fires when a playable cell is clicked. function createBoard(container, onPlay) { const cells = []; let shown = null; // board as currently drawn; null means draw the next state without animation for (let i = 0; i < 9; i++) { const cell = document.createElement("button"); cell.className = "cell"; cell.addEventListener("click", () => { if (cell.classList.contains("playable")) onPlay(i); }); container.appendChild(cell); cells.push(cell); } return { // `s` is a game state from the server; `canPlay` makes empty cells clickable. render(s, canPlay) { // Age of each piece within its owner's pieces: 0 = newest. Styled only when the // board has the "freshness" class. const ages = Array(9).fill(null); for (const hist of s.history || []) { hist.forEach((cell, i) => { ages[cell] = hist.length - 1 - i; }); } s.board.forEach((v, i) => { const cell = cells[i]; if (!shown || v !== shown[i]) { cell.innerHTML = v === null ? "" : PIECE_SVG[v]; cell.classList.toggle("placed", v !== null && shown !== null); } cell.classList.toggle("playable", canPlay && v === null); cell.classList.toggle("doomed", s.doomed === i); if (ages[i] === null) delete cell.dataset.age; else cell.dataset.age = ages[i]; cell.classList.toggle("win", !!(s.winLine && s.winLine.includes(i))); cell.setAttribute("aria-label", `Square ${i + 1}: ${v === null ? "empty" : MARKS[v]}${s.doomed === i ? " (vanishes next)" : ""}`); }); shown = s.board.slice(); }, // Next render draws without the placement animation (e.g. after joining a game). reset() { shown = null; }, }; }