Spaces:
Sleeping
Sleeping
| document.addEventListener('DOMContentLoaded', () => { | |
| const gridContainer = document.querySelector('.grid-container'); | |
| const scoreElement = document.getElementById('score'); | |
| const bestScoreElement = document.getElementById('best-score'); | |
| const newGameButton = document.getElementById('new-game'); | |
| const tryAgainButton = document.getElementById('try-again'); | |
| const keepGoingButton = document.getElementById('keep-going'); | |
| const gameOverScreen = document.querySelector('.game-over'); | |
| const gameWonScreen = document.querySelector('.game-won'); | |
| let grid = []; | |
| let score = 0; | |
| let bestScore = localStorage.getItem('bestScore') || 0; | |
| let gameOver = false; | |
| let gameWon = false; | |
| // Initialize the game | |
| function initGame() { | |
| // Clear the grid | |
| grid = Array(4).fill().map(() => Array(4).fill(0)); | |
| score = 0; | |
| gameOver = false; | |
| gameWon = false; | |
| // Clear the grid container | |
| gridContainer.innerHTML = ''; | |
| // Create the grid cells | |
| for (let i = 0; i < 4; i++) { | |
| for (let j = 0; j < 4; j++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'grid-cell'; | |
| gridContainer.appendChild(cell); | |
| } | |
| } | |
| // Add initial tiles | |
| addRandomTile(); | |
| addRandomTile(); | |
| // Update the score display | |
| updateScore(); | |
| // Hide game over/win screens | |
| gameOverScreen.style.display = 'none'; | |
| gameWonScreen.style.display = 'none'; | |
| } | |
| // Add a random tile (2 or 4) to an empty cell | |
| function addRandomTile() { | |
| const emptyCells = []; | |
| // Find all empty cells | |
| for (let i = 0; i < 4; i++) { | |
| for (let j = 0; j < 4; j++) { | |
| if (grid[i][j] === 0) { | |
| emptyCells.push({x: i, y: j}); | |
| } | |
| } | |
| } | |
| if (emptyCells.length > 0) { | |
| // Choose a random empty cell | |
| const {x, y} = emptyCells[Math.floor(Math.random() * emptyCells.length)]; | |
| // 90% chance for 2, 10% chance for 4 | |
| grid[x][y] = Math.random() < 0.9 ? 2 : 4; | |
| // Create and animate the new tile | |
| createTile(x, y, grid[x][y], true); | |
| } | |
| } | |
| // Create a tile element at the specified position | |
| function createTile(x, y, value, isNew = false) { | |
| const tile = document.createElement('div'); | |
| tile.className = `tile tile-${value} ${isNew ? 'new-tile' : ''}`; | |
| tile.textContent = value; | |
| // Position the tile | |
| const posX = y * 25 + (y * 15); | |
| const posY = x * 25 + (x * 15); | |
| tile.style.left = `${posX}%`; | |
| tile.style.top = `${posY}%`; | |
| // Add data attributes for tracking position | |
| tile.dataset.x = x; | |
| tile.dataset.y = y; | |
| tile.dataset.value = value; | |
| gridContainer.appendChild(tile); | |
| return tile; | |
| } | |
| // Update the score display | |
| function updateScore() { | |
| scoreElement.textContent = score; | |
| bestScore = Math.max(score, bestScore); | |
| bestScoreElement.textContent = bestScore; | |
| localStorage.setItem('bestScore', bestScore); | |
| } | |
| // Check if there are any moves left | |
| function hasMovesLeft() { | |
| // Check for any empty cells | |
| for (let i = 0; i < 4; i++) { | |
| for (let j = 0; j < 4; j++) { | |
| if (grid[i][j] === 0) return true; | |
| // Check adjacent cells for possible merges | |
| if (i < 3 && grid[i][j] === grid[i + 1][j]) return true; | |
| if (j < 3 && grid[i][j] === grid[i][j + 1]) return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // Move tiles in the specified direction | |
| function moveTiles(direction) { | |
| if (gameOver || gameWon) return false; | |
| let moved = false; | |
| const oldGrid = JSON.parse(JSON.stringify(grid)); | |
| // Remove all tiles from the DOM (we'll recreate them after moving) | |
| const tiles = document.querySelectorAll('.tile'); | |
| const tilesData = Array.from(tiles).map(tile => ({ | |
| x: parseInt(tile.dataset.x), | |
| y: parseInt(tile.dataset.y), | |
| value: parseInt(tile.dataset.value), | |
| element: tile | |
| })); | |
| tiles.forEach(tile => tile.remove()); | |
| // Process the move based on direction | |
| switch (direction) { | |
| case 'up': | |
| moved = moveUp(); | |
| break; | |
| case 'right': | |
| moved = moveRight(); | |
| break; | |
| case 'down': | |
| moved = moveDown(); | |
| break; | |
| case 'left': | |
| moved = moveLeft(); | |
| break; | |
| } | |
| // If the grid changed, add a new random tile | |
| if (moved) { | |
| addRandomTile(); | |
| updateScore(); | |
| // Check for win condition | |
| if (!gameWon && checkWin()) { | |
| gameWon = true; | |
| gameWonScreen.style.display = 'flex'; | |
| } | |
| // Check for game over | |
| else if (!hasMovesLeft()) { | |
| gameOver = true; | |
| gameOverScreen.style.display = 'flex'; | |
| } | |
| } | |
| // Recreate tiles with new positions | |
| for (let i = 0; i < 4; i++) { | |
| for (let j = 0; j < 4; j++) { | |
| if (grid[i][j] !== 0) { | |
| createTile(i, j, grid[i][j]); | |
| } | |
| } | |
| } | |
| return moved; | |
| } | |
| // Move tiles up | |
| function moveUp() { | |
| let moved = false; | |
| for (let j = 0; j < 4; j++) { | |
| // Move all tiles up as much as possible | |
| for (let i = 1; i < 4; i++) { | |
| if (grid[i][j] !== 0) { | |
| let row = i; | |
| while (row > 0 && grid[row - 1][j] === 0) { | |
| grid[row - 1][j] = grid[row][j]; | |
| grid[row][j] = 0; | |
| row--; | |
| moved = true; | |
| } | |
| // Check for merge with the tile above | |
| if (row > 0 && grid[row - 1][j] === grid[row][j] && | |
| !document.querySelector(`.tile[data-x="${row-1}"][data-y="${j}"].merged`)) { | |
| grid[row - 1][j] *= 2; | |
| score += grid[row - 1][j]; | |
| grid[row][j] = 0; | |
| moved = true; | |
| } | |
| } | |
| } | |
| } | |
| return moved; | |
| } | |
| // Move tiles right | |
| function moveRight() { | |
| let moved = false; | |
| for (let i = 0; i < 4; i++) { | |
| for (let j = 2; j >= 0; j--) { | |
| if (grid[i][j] !== 0) { | |
| let col = j; | |
| while (col < 3 && grid[i][col + 1] === 0) { | |
| grid[i][col + 1] = grid[i][col]; | |
| grid[i][col] = 0; | |
| col++; | |
| moved = true; | |
| } | |
| // Check for merge with the tile to the right | |
| if (col < 3 && grid[i][col + 1] === grid[i][col] && | |
| !document.querySelector(`.tile[data-x="${i}"][data-y="${col+1}"].merged`)) { | |
| grid[i][col + 1] *= 2; | |
| score += grid[i][col + 1]; | |
| grid[i][col] = 0; | |
| moved = true; | |
| } | |
| } | |
| } | |
| } | |
| return moved; | |
| } | |
| // Move tiles down | |
| function moveDown() { | |
| let moved = false; | |
| for (let j = 0; j < 4; j++) { | |
| for (let i = 2; i >= 0; i--) { | |
| if (grid[i][j] !== 0) { | |
| let row = i; | |
| while (row < 3 && grid[row + 1][j] === 0) { | |
| grid[row + 1][j] = grid[row][j]; | |
| grid[row][j] = 0; | |
| row++; | |
| moved = true; | |
| } | |
| // Check for merge with the tile below | |
| if (row < 3 && grid[row + 1][j] === grid[row][j] && | |
| !document.querySelector(`.tile[data-x="${row+1}"][data-y="${j}"].merged`)) { | |
| grid[row + 1][j] *= 2; | |
| score += grid[row + 1][j]; | |
| grid[row][j] = 0; | |
| moved = true; | |
| } | |
| } | |
| } | |
| } | |
| return moved; | |
| } | |
| // Move tiles left | |
| function moveLeft() { | |
| let moved = false; | |
| for (let i = 0; i < 4; i++) { | |
| for (let j = 1; j < 4; j++) { | |
| if (grid[i][j] !== 0) { | |
| let col = j; | |
| while (col > 0 && grid[i][col - 1] === 0) { | |
| grid[i][col - 1] = grid[i][col]; | |
| grid[i][col] = 0; | |
| col--; | |
| moved = true; | |
| } | |
| // Check for merge with the tile to the left | |
| if (col > 0 && grid[i][col - 1] === grid[i][col] && | |
| !document.querySelector(`.tile[data-x="${i}"][data-y="${col-1}"].merged`)) { | |
| grid[i][col - 1] *= 2; | |
| score += grid[i][col - 1]; | |
| grid[i][col] = 0; | |
| moved = true; | |
| } | |
| } | |
| } | |
| } | |
| return moved; | |
| } | |
| // Check if the player has won (reached 2048) | |
| function checkWin() { | |
| for (let i = 0; i < 4; i++) { | |
| for (let j = 0; j < 4; j++) { | |
| if (grid[i][j] === 2048) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| // Event listeners | |
| document.addEventListener('keydown', (e) => { | |
| switch (e.key) { | |
| case 'ArrowUp': | |
| e.preventDefault(); | |
| moveTiles('up'); | |
| break; | |
| case 'ArrowRight': | |
| e.preventDefault(); | |
| moveTiles('right'); | |
| break; | |
| case 'ArrowDown': | |
| e.preventDefault(); | |
| moveTiles('down'); | |
| break; | |
| case 'ArrowLeft': | |
| e.preventDefault(); | |
| moveTiles('left'); | |
| break; | |
| } | |
| }); | |
| // Touch support for mobile devices | |
| let touchStartX = 0; | |
| let touchStartY = 0; | |
| let touchEndX = 0; | |
| let touchEndY = 0; | |
| document.addEventListener('touchstart', (e) => { | |
| touchStartX = e.touches[0].clientX; | |
| touchStartY = e.touches[0].clientY; | |
| }, false); | |
| document.addEventListener('touchend', (e) => { | |
| touchEndX = e.changedTouches[0].clientX; | |
| touchEndY = e.changedTouches[0].clientY; | |
| handleSwipe(); | |
| }, false); | |
| function handleSwipe() { | |
| const dx = touchEndX - touchStartX; | |
| const dy = touchEndY - touchStartY; | |
| // Determine if the swipe was more horizontal or vertical | |
| if (Math.abs(dx) > Math.abs(dy)) { | |
| // Horizontal swipe | |
| if (dx > 0) { | |
| moveTiles('right'); | |
| } else { | |
| moveTiles('left'); | |
| } | |
| } else { | |
| // Vertical swipe | |
| if (dy > 0) { | |
| moveTiles('down'); | |
| } else { | |
| moveTiles('up'); | |
| } | |
| } | |
| } | |
| // Button event listeners | |
| newGameButton.addEventListener('click', initGame); | |
| tryAgainButton.addEventListener('click', initGame); | |
| keepGoingButton.addEventListener('click', () => { | |
| gameWonScreen.style.display = 'none'; | |
| gameWon = false; | |
| }); | |
| // Initialize the game | |
| initGame(); | |
| }); | |