/* ------------------------ 5. GAME LOGIC ------------------------ */ function addNewTile() { const empty = []; for (let r = 0; r < 4; r++) { for (let c = 0; c < 4; c++) { if (board[r][c] === 0) empty.push({ r, c }); } } if (empty.length === 0) return false; const spot = empty[Math.floor(Math.random() * empty.length)]; board[spot.r][spot.c] = 2; const tile = document.getElementById(`${spot.r}-${spot.c}`); if (tile) { tile.classList.add("new"); playSound(audio.pop); setTimeout(() => tile.classList.remove("new"), 300); } updateTile(spot.r, spot.c, 2); return true; } function filterZero(row) { return row.filter(n => n !== 0); } function slide(row) { row = filterZero(row); let mergedThisMove = false; let mergedPositions = []; let mergeCount = 0; for (let i = 0; i < row.length - 1; i++) { if (row[i] === row[i + 1]) { row[i] = row[i] * 2; playSound(audio.merge); if (navigator.vibrate) { navigator.vibrate([80, 20, 80]); } currentScore += row[i]; row[i + 1] = 0; mergedThisMove = true; mergedPositions.push(i); mergeCount++; } } row = filterZero(row); while (row.length < 4) row.push(0); return { row, merged: mergedThisMove, mergedPositions, mergeCount }; } function arraysEqual(a, b) { return a.length === b.length && a.every((v, i) => v === b[i]); } /* Move functions */ function moveLeft() { let moved = false; let mergedCells = []; mergesInCurrentMove = 0; for (let r = 0; r < 4; r++) { const { row: newRow, mergedPositions, mergeCount } = slide(board[r]); if (!arraysEqual(newRow, board[r])) moved = true; board[r] = newRow; mergesInCurrentMove += mergeCount; if (mergedPositions && mergedPositions.length > 0) { mergedPositions.forEach(c => { mergedCells.push({ r, c }); }); } } if (moved) { refreshBoard(); triggerComboEffect(mergedCells, mergesInCurrentMove); } return moved; } function moveRight() { let moved = false; let mergedCells = []; mergesInCurrentMove = 0; for (let r = 0; r < 4; r++) { let reversed = [...board[r]].reverse(); const { row: slid, mergedPositions, mergeCount } = slide(reversed); let newRow = slid.reverse(); if (!arraysEqual(newRow, board[r])) moved = true; board[r] = newRow; mergesInCurrentMove += mergeCount; if (mergedPositions && mergedPositions.length > 0) { mergedPositions.forEach(pos => { const c = 3 - pos; mergedCells.push({ r, c }); }); } } if (moved) { refreshBoard(); triggerComboEffect(mergedCells, mergesInCurrentMove); } return moved; } function moveUp() { let moved = false; let mergedCells = []; mergesInCurrentMove = 0; for (let c = 0; c < 4; c++) { const col = [board[0][c], board[1][c], board[2][c], board[3][c]]; const { row: newCol, mergedPositions, mergeCount } = slide(col); for (let r = 0; r < 4; r++) { if (board[r][c] !== newCol[r]) moved = true; board[r][c] = newCol[r]; } mergesInCurrentMove += mergeCount; if (mergedPositions && mergedPositions.length > 0) { mergedPositions.forEach(r => { mergedCells.push({ r, c }); }); } } if (moved) { refreshBoard(); triggerComboEffect(mergedCells, mergesInCurrentMove); } return moved; } function moveDown() { let moved = false; let mergedCells = []; mergesInCurrentMove = 0; for (let c = 0; c < 4; c++) { const col = [board[3][c], board[2][c], board[1][c], board[0][c]]; const { row: slid, mergedPositions, mergeCount } = slide(col); const newCol = slid.reverse(); for (let r = 0; r < 4; r++) { if (board[r][c] !== newCol[r]) moved = true; board[r][c] = newCol[r]; } mergesInCurrentMove += mergeCount; if (mergedPositions && mergedPositions.length > 0) { mergedPositions.forEach(pos => { const r = 3 - pos; mergedCells.push({ r, c }); }); } } if (moved) { refreshBoard(); triggerComboEffect(mergedCells, mergesInCurrentMove); } return moved; } function canMove() { for (let r = 0; r < 4; r++) { for (let c = 0; c < 4; c++) { if (board[r][c] === 0) return true; } } for (let r = 0; r < 4; r++) { for (let c = 0; c < 4; c++) { const current = board[r][c]; if (c < 3 && board[r][c + 1] === current) return true; if (r < 3 && board[r + 1][c] === current) return true; } } return false; }