730 lines
20 KiB
JavaScript
730 lines
20 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
|
|
const COLORS = [
|
|
'#6abf69', '#4a90d9', '#e8a838', '#c06dbf',
|
|
'#e06060', '#40b8c8', '#d4c830', '#8b6b4a',
|
|
'#7088a0', '#d04080', '#5060b0', '#30a080'
|
|
];
|
|
|
|
const CAT_EMOJI = '\uD83D\uDC31';
|
|
const X_MARK = '\u2715';
|
|
|
|
let state = {
|
|
size: 8,
|
|
grid: [],
|
|
solution: [],
|
|
errors: 3,
|
|
maxErrors: 3,
|
|
catsPlaced: 0,
|
|
totalCats: 0,
|
|
level: 1,
|
|
score: 0,
|
|
hints: 3,
|
|
gameOver: false,
|
|
won: false,
|
|
playerName: '',
|
|
levelStartTime: null,
|
|
levelTime: 0,
|
|
sessionRecords: []
|
|
};
|
|
|
|
let pressTimer = null;
|
|
let longPressTriggered = false;
|
|
let gameTimer = null;
|
|
const LONG_PRESS_MS = 500;
|
|
|
|
// DOM refs
|
|
const gridContainer = document.getElementById('grid-container');
|
|
const levelDisplay = document.getElementById('level-display');
|
|
const scoreDisplay = document.getElementById('score-display');
|
|
const catsDisplay = document.getElementById('cats-display');
|
|
const errorsDisplay = document.getElementById('errors-display');
|
|
const hintCountEl = document.getElementById('hint-count');
|
|
const modalOverlay = document.getElementById('modal-overlay');
|
|
const modalTitle = document.getElementById('modal-title');
|
|
const modalMessage = document.getElementById('modal-message');
|
|
const modalBtn = document.getElementById('modal-btn');
|
|
const gridSizeSelect = document.getElementById('grid-size');
|
|
const btnNewGame = document.getElementById('btn-new-game');
|
|
const btnHint = document.getElementById('btn-hint');
|
|
const btnEraser = document.getElementById('btn-eraser');
|
|
const timeDisplay = document.getElementById('time-display');
|
|
const playerNameDisplay = document.getElementById('player-name-display');
|
|
const nameInputOverlay = document.getElementById('name-input-overlay');
|
|
const nameInput = document.getElementById('name-input');
|
|
const nameSubmitBtn = document.getElementById('name-submit-btn');
|
|
|
|
// --- Generator ---
|
|
|
|
function shuffle(arr) {
|
|
for (let i = arr.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
function isAdjacent(r1, c1, r2, c2) {
|
|
return Math.abs(r1 - r2) <= 1 && Math.abs(c1 - c2) <= 1 && !(r1 === r2 && c1 === c2);
|
|
}
|
|
|
|
function canPlaceCat(grid, row, col, size, placed) {
|
|
for (const p of placed) {
|
|
if (p.row === row || p.col === col) return false;
|
|
if (isAdjacent(p.row, p.col, row, col)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function generateSolution(size) {
|
|
const placed = [];
|
|
const rows = shuffle([...Array(size).keys()]);
|
|
|
|
function backtrack(rowIdx) {
|
|
if (rowIdx === size) return true;
|
|
const row = rows[rowIdx];
|
|
const cols = shuffle([...Array(size).keys()]);
|
|
for (const col of cols) {
|
|
if (canPlaceCat(null, row, col, size, placed)) {
|
|
placed.push({ row, col });
|
|
if (backtrack(rowIdx + 1)) return true;
|
|
placed.pop();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Try multiple times with different shuffles
|
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
placed.length = 0;
|
|
for (let i = 0; i < size; i++) rows[i] = i;
|
|
shuffle(rows);
|
|
if (backtrack(0)) return placed;
|
|
}
|
|
|
|
// Fallback: simple placement
|
|
placed.length = 0;
|
|
for (let i = 0; i < size; i++) {
|
|
placed.push({ row: i, col: (i * 2) % size });
|
|
}
|
|
return placed;
|
|
}
|
|
|
|
function generateColors(size, solution) {
|
|
const grid = Array.from({ length: size }, () => Array(size).fill(-1));
|
|
const colorCount = solution.length;
|
|
const colorOrder = shuffle([...Array(colorCount).keys()]);
|
|
|
|
// Assign each solution cat its color
|
|
solution.forEach((cat, i) => {
|
|
grid[cat.row][cat.col] = colorOrder[i];
|
|
});
|
|
|
|
// Flood fill from each cat to expand regions contiguously
|
|
// Use BFS from all cats simultaneously, expanding one step at a time
|
|
const queues = colorOrder.map((color, i) => {
|
|
const cat = solution[i];
|
|
return [{ r: cat.row, c: cat.col }];
|
|
});
|
|
|
|
let totalFilled = colorCount;
|
|
const totalCells = size * size;
|
|
|
|
while (totalFilled < totalCells) {
|
|
let expanded = false;
|
|
// Shuffle queue order each round for randomness
|
|
const order = shuffle([...Array(colorCount).keys()]);
|
|
for (const i of order) {
|
|
if (queues[i].length === 0) continue;
|
|
const idx = Math.floor(Math.random() * queues[i].length);
|
|
const { r, c } = queues[i].splice(idx, 1)[0];
|
|
const dirs = shuffle([[0,1],[0,-1],[1,0],[-1,0]]);
|
|
for (const [dr, dc] of dirs) {
|
|
const nr = r + dr, nc = c + dc;
|
|
if (nr >= 0 && nr < size && nc >= 0 && nc < size && grid[nr][nc] === -1) {
|
|
grid[nr][nc] = colorOrder[i];
|
|
queues[i].push({ r: nr, c: nc });
|
|
totalFilled++;
|
|
expanded = true;
|
|
if (totalFilled >= totalCells) break;
|
|
}
|
|
}
|
|
if (totalFilled >= totalCells) break;
|
|
}
|
|
if (!expanded) break;
|
|
}
|
|
|
|
// Fallback: colora eventuali celle rimaste scoperte (-1)
|
|
// Assegna il colore di una cella adiacente già colorata
|
|
if (totalFilled < totalCells) {
|
|
for (let r = 0; r < size; r++) {
|
|
for (let c = 0; c < size; c++) {
|
|
if (grid[r][c] === -1) {
|
|
// Cerca una cella adiacente colorata
|
|
const dirs = [[0,1],[0,-1],[1,0],[-1,0]];
|
|
for (const [dr, dc] of dirs) {
|
|
const nr = r + dr, nc = c + dc;
|
|
if (nr >= 0 && nr < size && nc >= 0 && nc < size && grid[nr][nc] !== -1) {
|
|
grid[r][c] = grid[nr][nc];
|
|
break;
|
|
}
|
|
}
|
|
// Se ancora -1, assegna un colore casuale
|
|
if (grid[r][c] === -1) {
|
|
grid[r][c] = colorOrder[Math.floor(Math.random() * colorCount)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return grid;
|
|
}
|
|
|
|
function generatePuzzle(size) {
|
|
const solution = generateSolution(size);
|
|
const colorGrid = generateColors(size, solution);
|
|
|
|
const grid = [];
|
|
for (let r = 0; r < size; r++) {
|
|
grid[r] = [];
|
|
for (let c = 0; c < size; c++) {
|
|
grid[r][c] = {
|
|
color: colorGrid[r][c],
|
|
hasCat: false,
|
|
hasX: false,
|
|
isSolution: solution.some(s => s.row === r && s.col === c)
|
|
};
|
|
}
|
|
}
|
|
|
|
return { grid, solution };
|
|
}
|
|
|
|
// --- Game Logic ---
|
|
|
|
const STORAGE_KEY_STATE = 'catdoku_game_state';
|
|
|
|
function saveGameState() {
|
|
const stateToSave = {
|
|
size: state.size,
|
|
grid: state.grid,
|
|
solution: state.solution,
|
|
errors: state.errors,
|
|
catsPlaced: state.catsPlaced,
|
|
totalCats: state.totalCats,
|
|
level: state.level,
|
|
score: state.score,
|
|
hints: state.hints,
|
|
gameOver: state.gameOver,
|
|
won: state.won,
|
|
playerName: state.playerName,
|
|
levelStartTime: state.levelStartTime,
|
|
levelTime: state.levelTime
|
|
};
|
|
localStorage.setItem(STORAGE_KEY_STATE, JSON.stringify(stateToSave));
|
|
}
|
|
|
|
function loadGameState() {
|
|
const saved = localStorage.getItem(STORAGE_KEY_STATE);
|
|
if (!saved) return null;
|
|
try {
|
|
return JSON.parse(saved);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function clearGameState() {
|
|
localStorage.removeItem(STORAGE_KEY_STATE);
|
|
}
|
|
|
|
function startNewGame() {
|
|
// Genera sempre una nuova partita, ignorando stato salvato
|
|
clearGameState();
|
|
const size = parseInt(gridSizeSelect.value);
|
|
state.size = size;
|
|
state.errors = state.maxErrors;
|
|
state.catsPlaced = 0;
|
|
state.totalCats = size;
|
|
state.gameOver = false;
|
|
state.won = false;
|
|
state.hints = 3;
|
|
state.levelStartTime = Date.now();
|
|
state.levelTime = 0;
|
|
|
|
const puzzle = generatePuzzle(size);
|
|
state.grid = puzzle.grid;
|
|
state.solution = puzzle.solution;
|
|
|
|
updateUI();
|
|
renderGrid();
|
|
hideModal();
|
|
startTimer();
|
|
saveGameState();
|
|
}
|
|
|
|
function initGame() {
|
|
// Prima prova a caricare stato salvato
|
|
const savedState = loadGameState();
|
|
|
|
if (savedState && !savedState.gameOver && !savedState.won) {
|
|
// Ripristina stato salvato
|
|
state.size = savedState.size;
|
|
state.grid = savedState.grid;
|
|
state.solution = savedState.solution;
|
|
state.errors = savedState.errors;
|
|
state.catsPlaced = savedState.catsPlaced;
|
|
state.totalCats = savedState.totalCats;
|
|
state.level = savedState.level;
|
|
state.score = savedState.score;
|
|
state.hints = savedState.hints;
|
|
state.gameOver = savedState.gameOver;
|
|
state.won = savedState.won;
|
|
state.playerName = savedState.playerName;
|
|
state.levelStartTime = savedState.levelStartTime;
|
|
state.levelTime = savedState.levelTime;
|
|
|
|
// Ripristina UI
|
|
if (playerNameDisplay) {
|
|
playerNameDisplay.textContent = state.playerName;
|
|
}
|
|
updateUI();
|
|
renderGrid();
|
|
hideModal();
|
|
hideNameInput();
|
|
startTimer();
|
|
return;
|
|
}
|
|
|
|
// Nuova partita
|
|
const size = parseInt(gridSizeSelect.value);
|
|
state.size = size;
|
|
state.errors = state.maxErrors;
|
|
state.catsPlaced = 0;
|
|
state.totalCats = size;
|
|
state.gameOver = false;
|
|
state.won = false;
|
|
state.hints = 3;
|
|
state.levelStartTime = Date.now();
|
|
state.levelTime = 0;
|
|
|
|
const puzzle = generatePuzzle(size);
|
|
state.grid = puzzle.grid;
|
|
state.solution = puzzle.solution;
|
|
|
|
updateUI();
|
|
renderGrid();
|
|
hideModal();
|
|
startTimer();
|
|
}
|
|
|
|
function startTimer() {
|
|
if (gameTimer) clearInterval(gameTimer);
|
|
gameTimer = setInterval(() => {
|
|
if (!state.gameOver && !state.won) {
|
|
state.levelTime = Math.floor((Date.now() - state.levelStartTime) / 1000);
|
|
updateTimeDisplay();
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function stopTimer() {
|
|
if (gameTimer) {
|
|
clearInterval(gameTimer);
|
|
gameTimer = null;
|
|
}
|
|
}
|
|
|
|
function formatTime(seconds) {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
function updateTimeDisplay() {
|
|
if (timeDisplay) {
|
|
timeDisplay.textContent = formatTime(state.levelTime);
|
|
}
|
|
}
|
|
|
|
function saveLevelRecord() {
|
|
const record = {
|
|
playerName: state.playerName,
|
|
level: state.level,
|
|
gridSize: state.size,
|
|
time: state.levelTime,
|
|
score: state.score,
|
|
errors: state.maxErrors - state.errors,
|
|
hintsUsed: 3 - state.hints,
|
|
date: new Date().toISOString()
|
|
};
|
|
state.sessionRecords.push(record);
|
|
|
|
// Salva in localStorage
|
|
const allRecords = JSON.parse(localStorage.getItem('catdoku_records') || '[]');
|
|
allRecords.push(record);
|
|
localStorage.setItem('catdoku_records', JSON.stringify(allRecords));
|
|
}
|
|
|
|
function getCatCountInRow(row) {
|
|
let count = 0;
|
|
for (let c = 0; c < state.size; c++) {
|
|
if (state.grid[row][c].hasCat) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function getCatCountInCol(col) {
|
|
let count = 0;
|
|
for (let r = 0; r < state.size; r++) {
|
|
if (state.grid[r][col].hasCat) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function getCatCountInColor(color) {
|
|
let count = 0;
|
|
for (let r = 0; r < state.size; r++) {
|
|
for (let c = 0; c < state.size; c++) {
|
|
if (state.grid[r][c].color === color && state.grid[r][c].hasCat) count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function isCatAdjacent(row, col) {
|
|
for (let dr = -1; dr <= 1; dr++) {
|
|
for (let dc = -1; dc <= 1; dc++) {
|
|
if (dr === 0 && dc === 0) continue;
|
|
const nr = row + dr, nc = col + dc;
|
|
if (nr >= 0 && nr < state.size && nc >= 0 && nc < state.size) {
|
|
if (state.grid[nr][nc].hasCat) return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isValidPlacement(row, col) {
|
|
const cell = state.grid[row][col];
|
|
if (cell.hasX) return false;
|
|
if (getCatCountInRow(row) > 0) return false;
|
|
if (getCatCountInCol(col) > 0) return false;
|
|
if (getCatCountInColor(cell.color) > 0) return false;
|
|
if (isCatAdjacent(row, col)) return false;
|
|
return true;
|
|
}
|
|
|
|
function placeCat(row, col) {
|
|
if (state.gameOver || state.won) return;
|
|
|
|
const cell = state.grid[row][col];
|
|
|
|
// If cat already there, remove it
|
|
if (cell.hasCat) {
|
|
cell.hasCat = false;
|
|
state.catsPlaced--;
|
|
renderGrid();
|
|
updateUI();
|
|
saveGameState();
|
|
return;
|
|
}
|
|
|
|
// Check if placement is valid
|
|
if (!isValidPlacement(row, col)) {
|
|
state.errors--;
|
|
flashError(row, col);
|
|
updateUI();
|
|
saveGameState();
|
|
|
|
if (state.errors <= 0) {
|
|
state.gameOver = true;
|
|
clearGameState();
|
|
showModal('Game Over!', 'Hai esaurito tutti i tentativi. Riprova!', 'Ricomincia', () => {
|
|
startNewGame();
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
cell.hasCat = true;
|
|
state.catsPlaced++;
|
|
renderGrid();
|
|
updateUI();
|
|
saveGameState();
|
|
|
|
// Check win
|
|
if (state.catsPlaced === state.totalCats) {
|
|
state.won = true;
|
|
stopTimer();
|
|
state.score += state.errors * 10 + state.hints * 5;
|
|
saveLevelRecord();
|
|
clearGameState();
|
|
state.level++;
|
|
showModal('Vittoria!',
|
|
`Hai completato il livello ${state.level - 1}!\nTempo: ${formatTime(state.levelTime)}\nPunteggio: ${state.score}`,
|
|
'Livello Successivo', () => {
|
|
startNewGame(); // Nuovo livello, mantiene il nome
|
|
});
|
|
}
|
|
}
|
|
|
|
function toggleX(row, col) {
|
|
if (state.gameOver || state.won) return;
|
|
const cell = state.grid[row][col];
|
|
if (cell.hasCat) return; // Can't X a cat cell
|
|
cell.hasX = !cell.hasX;
|
|
renderGrid();
|
|
saveGameState();
|
|
}
|
|
|
|
function flashError(row, col) {
|
|
const cellEl = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
|
|
if (cellEl) {
|
|
cellEl.classList.add('error-flash');
|
|
setTimeout(() => cellEl.classList.remove('error-flash'), 500);
|
|
}
|
|
}
|
|
|
|
function useHint() {
|
|
if (state.gameOver || state.won) return;
|
|
|
|
// Se non ci sono suggerimenti, mostra popup premium
|
|
if (state.hints <= 0) {
|
|
showPremiumPopup();
|
|
return;
|
|
}
|
|
|
|
// Find a solution cat that isn't placed yet
|
|
for (const sol of state.solution) {
|
|
if (!state.grid[sol.row][sol.col].hasCat) {
|
|
state.grid[sol.row][sol.col].hasCat = true;
|
|
state.catsPlaced++;
|
|
state.hints--;
|
|
renderGrid();
|
|
updateUI();
|
|
|
|
if (state.catsPlaced === state.totalCats) {
|
|
state.won = true;
|
|
stopTimer();
|
|
state.score += state.errors * 10 + state.hints * 5;
|
|
saveLevelRecord();
|
|
clearGameState();
|
|
state.level++;
|
|
showModal('Vittoria!', 'Hai completato il livello! Punteggio: ' + state.score, 'Livello Successivo', () => {
|
|
startNewGame();
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Rendering ---
|
|
|
|
function renderGrid() {
|
|
gridContainer.innerHTML = '';
|
|
gridContainer.style.gridTemplateColumns = `repeat(${state.size}, 1fr)`;
|
|
|
|
for (let r = 0; r < state.size; r++) {
|
|
for (let c = 0; c < state.size; c++) {
|
|
const cell = state.grid[r][c];
|
|
const cellEl = document.createElement('div');
|
|
cellEl.className = 'cell';
|
|
cellEl.dataset.row = r;
|
|
cellEl.dataset.col = c;
|
|
cellEl.style.backgroundColor = COLORS[cell.color % COLORS.length];
|
|
|
|
if (cell.hasCat) {
|
|
cellEl.innerHTML = `<span class="cat">${CAT_EMOJI}</span>`;
|
|
} else if (cell.hasX) {
|
|
cellEl.innerHTML = `<span class="x-mark">${X_MARK}</span>`;
|
|
}
|
|
|
|
// Events
|
|
cellEl.addEventListener('mousedown', (e) => onPointerDown(e, r, c));
|
|
cellEl.addEventListener('mouseup', (e) => onPointerUp(e, r, c));
|
|
cellEl.addEventListener('mouseleave', () => onPointerLeave());
|
|
cellEl.addEventListener('touchstart', (e) => onTouchStart(e, r, c), { passive: false });
|
|
cellEl.addEventListener('touchend', (e) => onTouchEnd(e, r, c));
|
|
cellEl.addEventListener('touchcancel', () => onPointerLeave());
|
|
cellEl.addEventListener('contextmenu', (e) => e.preventDefault());
|
|
|
|
gridContainer.appendChild(cellEl);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateUI() {
|
|
levelDisplay.textContent = state.level;
|
|
scoreDisplay.textContent = state.score;
|
|
catsDisplay.textContent = `${state.catsPlaced}/${state.totalCats}`;
|
|
hintCountEl.textContent = state.hints;
|
|
|
|
// Aggiungi classe "zero" quando i suggerimenti sono esauriti
|
|
hintCountEl.classList.toggle('zero', state.hints <= 0);
|
|
|
|
// Update error hearts
|
|
const hearts = errorsDisplay.querySelectorAll('.heart');
|
|
hearts.forEach((h, i) => {
|
|
h.classList.toggle('lost', i >= state.errors);
|
|
});
|
|
}
|
|
|
|
// --- Input Handling ---
|
|
|
|
function onPointerDown(e, row, col) {
|
|
if (e.button !== 0) return;
|
|
longPressTriggered = false;
|
|
pressTimer = setTimeout(() => {
|
|
longPressTriggered = true;
|
|
placeCat(row, col);
|
|
}, LONG_PRESS_MS);
|
|
}
|
|
|
|
function onPointerUp(e, row, col) {
|
|
clearTimeout(pressTimer);
|
|
if (!longPressTriggered) {
|
|
toggleX(row, col);
|
|
}
|
|
}
|
|
|
|
function onPointerLeave() {
|
|
clearTimeout(pressTimer);
|
|
longPressTriggered = false;
|
|
}
|
|
|
|
function onTouchStart(e, row, col) {
|
|
e.preventDefault();
|
|
longPressTriggered = false;
|
|
pressTimer = setTimeout(() => {
|
|
longPressTriggered = true;
|
|
placeCat(row, col);
|
|
}, LONG_PRESS_MS);
|
|
}
|
|
|
|
function onTouchEnd(e, row, col) {
|
|
clearTimeout(pressTimer);
|
|
if (!longPressTriggered) {
|
|
toggleX(row, col);
|
|
}
|
|
}
|
|
|
|
// --- Modal ---
|
|
|
|
function showModal(title, message, btnText, callback) {
|
|
modalTitle.textContent = title;
|
|
modalMessage.textContent = message;
|
|
modalBtn.textContent = btnText;
|
|
modalOverlay.classList.remove('hidden');
|
|
modalBtn.onclick = () => {
|
|
hideModal();
|
|
if (callback) callback();
|
|
};
|
|
}
|
|
|
|
function hideModal() {
|
|
modalOverlay.classList.add('hidden');
|
|
}
|
|
|
|
function showPremiumPopup() {
|
|
const premiumOverlay = document.getElementById('premium-overlay');
|
|
const premiumBtn = document.getElementById('premium-btn');
|
|
const premiumTimer = document.getElementById('premium-timer');
|
|
|
|
premiumOverlay.classList.remove('hidden');
|
|
|
|
let countdown = 5;
|
|
premiumBtn.disabled = true;
|
|
premiumBtn.textContent = `Attendi ${countdown}s...`;
|
|
premiumTimer.textContent = countdown;
|
|
|
|
const timer = setInterval(() => {
|
|
countdown--;
|
|
premiumTimer.textContent = countdown;
|
|
|
|
if (countdown <= 0) {
|
|
clearInterval(timer);
|
|
premiumBtn.disabled = false;
|
|
premiumBtn.textContent = 'OK';
|
|
} else {
|
|
premiumBtn.textContent = `Attendi ${countdown}s...`;
|
|
}
|
|
}, 1000);
|
|
|
|
premiumBtn.onclick = () => {
|
|
clearInterval(timer);
|
|
premiumOverlay.classList.add('hidden');
|
|
state.hints++;
|
|
updateUI();
|
|
};
|
|
}
|
|
|
|
// --- Event Listeners ---
|
|
|
|
btnNewGame.addEventListener('click', () => {
|
|
state.level = 1;
|
|
state.score = 0;
|
|
state.hints = 3;
|
|
state.sessionRecords = [];
|
|
clearGameState();
|
|
state.playerName = '';
|
|
showNameInput();
|
|
});
|
|
|
|
btnHint.addEventListener('click', useHint);
|
|
|
|
btnEraser.addEventListener('click', () => {
|
|
// Clear all X marks (not cats)
|
|
for (let r = 0; r < state.size; r++) {
|
|
for (let c = 0; c < state.size; c++) {
|
|
state.grid[r][c].hasX = false;
|
|
}
|
|
}
|
|
renderGrid();
|
|
});
|
|
|
|
// Name input handling
|
|
function showNameInput() {
|
|
nameInputOverlay.classList.remove('hidden');
|
|
nameInput.value = state.playerName || '';
|
|
nameInput.focus();
|
|
}
|
|
|
|
function hideNameInput() {
|
|
nameInputOverlay.classList.add('hidden');
|
|
}
|
|
|
|
nameSubmitBtn.addEventListener('click', () => {
|
|
const name = nameInput.value.trim();
|
|
if (name) {
|
|
state.playerName = name;
|
|
if (playerNameDisplay) {
|
|
playerNameDisplay.textContent = name;
|
|
}
|
|
hideNameInput();
|
|
startNewGame(); // Nuova partita, non initGame che carica stato salvato
|
|
}
|
|
});
|
|
|
|
nameInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
nameSubmitBtn.click();
|
|
}
|
|
});
|
|
|
|
// Init - controlla se c'è una partita salvata
|
|
const savedState = loadGameState();
|
|
if (savedState && savedState.playerName && savedState.grid && savedState.grid.length > 0 && !savedState.gameOver && !savedState.won) {
|
|
// Ripristina partita in corso, salta input nome
|
|
initGame();
|
|
} else {
|
|
// Prima partita o stato invalido
|
|
clearGameState();
|
|
showNameInput();
|
|
}
|
|
|
|
})();
|