(function () { "use strict"; let gameState = null; let timerInterval = null; let selectedCells = []; // DOM refs const setupDiv = document.getElementById("game-setup"); const boardDiv = document.getElementById("game-board"); const categorySelect = document.getElementById("category-select"); const boardSizeInput = document.getElementById("board-size"); const sizeDisplay = document.getElementById("size-display"); const newGameBtn = document.getElementById("new-game-btn"); const gridTable = document.getElementById("grid-table"); const wordList = document.getElementById("word-list"); const wordInput = document.getElementById("word-input"); const guessBtn = document.getElementById("guess-btn"); const submitSelectionBtn = document.getElementById("submit-selection-btn"); const clearSelectionBtn = document.getElementById("clear-selection-btn"); const guessFeedback = document.getElementById("guess-feedback"); const timerEl = document.getElementById("timer"); const completionBanner = document.getElementById("completion-banner"); const finalTime = document.getElementById("final-time"); const playAgainBtn = document.getElementById("play-again-btn"); const inputArea = document.getElementById("input-area"); // Load categories async function loadCategories() { const resp = await fetch("/api/categories"); const categories = await resp.json(); categorySelect.innerHTML = ""; categories.forEach(function (cat) { const opt = document.createElement("option"); opt.value = cat.id; opt.textContent = cat.name.charAt(0).toUpperCase() + cat.name.slice(1); categorySelect.appendChild(opt); }); } boardSizeInput.addEventListener("input", function () { sizeDisplay.textContent = this.value; }); // New game newGameBtn.addEventListener("click", async function () { const categoryId = parseInt(categorySelect.value); const boardSize = parseInt(boardSizeInput.value); const resp = await fetch("/api/game/new", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ category_id: categoryId, board_size: boardSize }), }); gameState = await resp.json(); showBoard(); }); function showBoard() { setupDiv.classList.add("d-none"); boardDiv.classList.remove("d-none"); completionBanner.classList.add("d-none"); playAgainBtn.classList.add("d-none"); inputArea.classList.remove("d-none"); selectedCells = []; renderGrid(); renderWordList(); startTimer(); } function renderGrid() { gridTable.innerHTML = ""; gameState.grid.forEach(function (row, r) { const tr = document.createElement("tr"); row.forEach(function (letter, c) { const td = document.createElement("td"); td.textContent = letter; td.dataset.row = r; td.dataset.col = c; td.addEventListener("click", function () { onCellClick(r, c, td); }); tr.appendChild(td); }); gridTable.appendChild(tr); }); } function renderWordList() { wordList.innerHTML = ""; gameState.words.forEach(function (w) { const li = document.createElement("li"); li.className = "list-group-item"; li.textContent = w.text; li.dataset.word = w.text; if (w.found) { li.classList.add("word-found"); } wordList.appendChild(li); }); } // Timer function startTimer() { if (timerInterval) clearInterval(timerInterval); const startedAt = new Date(gameState.started_at); function tick() { const elapsed = Math.floor((Date.now() - startedAt.getTime()) / 1000); const mins = Math.floor(elapsed / 60); const secs = elapsed % 60; timerEl.textContent = mins + ":" + (secs < 10 ? "0" : "") + secs; } tick(); timerInterval = setInterval(tick, 1000); } function stopTimer() { if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } } // Text guess guessBtn.addEventListener("click", submitTextGuess); wordInput.addEventListener("keydown", function (e) { if (e.key === "Enter") submitTextGuess(); }); async function submitTextGuess() { const word = wordInput.value.trim(); if (!word) return; wordInput.value = ""; await sendGuess({ word: word }); } // Click selection function onCellClick(row, col, td) { if (gameState.status === "completed") return; // Check if already selected — deselect const idx = selectedCells.findIndex( function (c) { return c.row === row && c.col === col; } ); if (idx !== -1) { selectedCells.splice(idx, 1); td.classList.remove("cell-selected"); updateSelectionButtons(); return; } // Validate straight line if (selectedCells.length >= 2) { if (!isValidExtension(row, col)) { showFeedback("Selection must form a straight line.", "text-warning"); return; } } selectedCells.push({ row: row, col: col }); td.classList.add("cell-selected"); updateSelectionButtons(); } function isValidExtension(row, col) { if (selectedCells.length < 2) return true; const dr = selectedCells[1].row - selectedCells[0].row; const dc = selectedCells[1].col - selectedCells[0].col; const len = selectedCells.length; const expectedRow = selectedCells[0].row + dr * len; const expectedCol = selectedCells[0].col + dc * len; return row === expectedRow && col === expectedCol; } function updateSelectionButtons() { if (selectedCells.length > 0) { submitSelectionBtn.classList.remove("d-none"); clearSelectionBtn.classList.remove("d-none"); } else { submitSelectionBtn.classList.add("d-none"); clearSelectionBtn.classList.add("d-none"); } } submitSelectionBtn.addEventListener("click", async function () { if (selectedCells.length === 0) return; await sendGuess({ cells: selectedCells }); clearSelection(); }); clearSelectionBtn.addEventListener("click", clearSelection); function clearSelection() { selectedCells = []; document.querySelectorAll(".cell-selected").forEach(function (td) { td.classList.remove("cell-selected"); }); updateSelectionButtons(); } // Send guess to server async function sendGuess(payload) { const resp = await fetch("/api/game/" + gameState.id + "/guess", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const result = await resp.json(); gameState.words = result.words; gameState.status = result.status; if (result.correct) { highlightFoundCells(result.cells); renderWordList(); showFeedback("Found: " + result.word + "!", "text-success"); } else { showFeedback("Not a match.", "text-danger"); } if (result.status === "completed") { onGameComplete(result.completed_at); } } function highlightFoundCells(cells) { cells.forEach(function (c) { const td = gridTable.querySelector( 'td[data-row="' + c.row + '"][data-col="' + c.col + '"]' ); if (td) td.classList.add("cell-found"); }); } function showFeedback(msg, cls) { guessFeedback.textContent = msg; guessFeedback.className = "mt-2 " + cls; setTimeout(function () { guessFeedback.textContent = ""; guessFeedback.className = "mt-2"; }, 2000); } function onGameComplete(completedAt) { stopTimer(); completionBanner.classList.remove("d-none"); finalTime.textContent = timerEl.textContent; playAgainBtn.classList.remove("d-none"); inputArea.classList.add("d-none"); } playAgainBtn.addEventListener("click", function () { boardDiv.classList.add("d-none"); setupDiv.classList.remove("d-none"); }); // Init loadCategories(); })();