Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>Offline 2048</title> | |
| <style> | |
| body { font-family: Arial, sans-serif; background:#faf8ef; display:flex; flex-direction:column; align-items:center; } | |
| h1 { color:#776e65; } | |
| .score-board { display:flex; gap:12px; margin-bottom:10px; } | |
| .score-container, .best-container { background:#bbada0; color:#fff; padding:10px 15px; border-radius:4px; min-width:90px; text-align:center; } | |
| .game-container { position:relative; background:#bbada0; width:500px; height:500px; border-radius:6px; padding:15px; box-sizing:border-box; } | |
| .grid { position:relative; width:100%; height:100%; display:grid; grid-template-columns:repeat(4,1fr); grid-template-rows:repeat(4,1fr); gap:15px; } | |
| .cell, .tile { width:100%; height:100%; border-radius:6px; display:flex; align-items:center; justify-content:center; font-weight:bold; font-size:32px; } | |
| .cell { background:#cdc1b4; } | |
| .tile { position:absolute; } | |
| .tile-inner { width:100%; height:100%; border-radius:6px; display:flex; align-items:center; justify-content:center; } | |
| .controls { margin:8px 0 16px; color:#776e65; } | |
| .game-message { position:absolute; inset:0; background:rgba(238,228,218,0.73); display:none; align-items:center; justify-content:center; font-size:40px; color:#776e65; border-radius:6px; } | |
| .game-message.game-over { display:flex; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Offline 2048</h1> | |
| <div class="score-board"> | |
| <div class="score-container">0</div> | |
| <div class="best-container">0</div> | |
| </div> | |
| <div class="controls">Use Arrow Keys to play (this offline copy is tailored for automation).</div> | |
| <div class="game-container"> | |
| <div class="grid" id="grid"></div> | |
| <div class="game-message" id="gameMessage">Game Over</div> | |
| </div> | |
| <script> | |
| const gridSize = 4; | |
| let score = 0; | |
| let best = 0; | |
| const gridEl = document.getElementById('grid'); | |
| const scoreEl = document.querySelector('.score-container'); | |
| const bestEl = document.querySelector('.best-container'); | |
| const msgEl = document.getElementById('gameMessage'); | |
| // board as 2D array | |
| let board = Array.from({length:gridSize}, () => Array(gridSize).fill(0)); | |
| function setupGrid() { | |
| gridEl.innerHTML = ''; | |
| // base cells | |
| for (let r = 0; r < gridSize; r++) { | |
| for (let c = 0; c < gridSize; c++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'cell'; | |
| cell.style.gridRowStart = r+1; | |
| cell.style.gridColumnStart = c+1; | |
| gridEl.appendChild(cell); | |
| } | |
| } | |
| renderTiles(); | |
| } | |
| function randomEmpty() { | |
| const empties = []; | |
| for (let r=0; r<gridSize; r++) for (let c=0; c<gridSize; c++) if (board[r][c]===0) empties.push({r,c}); | |
| if (empties.length === 0) return null; | |
| return empties[Math.floor(Math.random()*empties.length)]; | |
| } | |
| function addRandomTile() { | |
| const spot = randomEmpty(); | |
| if (!spot) return; | |
| board[spot.r][spot.c] = Math.random() < 0.9 ? 2 : 4; | |
| } | |
| function tileColor(v) { | |
| const map = { | |
| 0:'#cdc1b4', 2:'#eee4da',4:'#ede0c8',8:'#f2b179',16:'#f59563', | |
| 32:'#f67c5f',64:'#f65e3b',128:'#edcf72',256:'#edcc61',512:'#edc850', | |
| 1024:'#edc53f',2048:'#edc22e' | |
| }; | |
| return map[v] || '#3c3a32'; | |
| } | |
| function renderTiles() { | |
| // remove existing tiles | |
| [...gridEl.querySelectorAll('.tile')].forEach(t => t.remove()); | |
| // draw from board | |
| for (let r = 0; r < gridSize; r++) { | |
| for (let c = 0; c < gridSize; c++) { | |
| const v = board[r][c]; | |
| if (v > 0) { | |
| const t = document.createElement('div'); | |
| t.className = `tile tile-${v} tile-position-${c+1}-${r+1}`; // x-y like original site | |
| t.style.gridRowStart = r+1; | |
| t.style.gridColumnStart = c+1; | |
| const inner = document.createElement('div'); | |
| inner.className = 'tile-inner'; | |
| inner.style.background = tileColor(v); | |
| inner.textContent = v; | |
| t.appendChild(inner); | |
| gridEl.appendChild(t); | |
| } | |
| } | |
| } | |
| scoreEl.textContent = score; | |
| best = Math.max(best, score); | |
| bestEl.textContent = best; | |
| } | |
| function slide(row) { | |
| // slide non-zeros to left and merge | |
| let arr = row.filter(v => v !== 0); | |
| for (let i=0; i<arr.length-1; i++) { | |
| if (arr[i] === arr[i+1]) { | |
| arr[i] *= 2; | |
| score += arr[i]; | |
| arr[i+1] = 0; | |
| i++; | |
| } | |
| } | |
| arr = arr.filter(v => v !== 0); | |
| while (arr.length < gridSize) arr.push(0); | |
| return arr; | |
| } | |
| function rotateRight(mat) { | |
| const res = Array.from({length:gridSize}, () => Array(gridSize).fill(0)); | |
| for (let r=0;r<gridSize;r++) for (let c=0;c<gridSize;c++) res[c][gridSize-1-r] = mat[r][c]; | |
| return res; | |
| } | |
| function move(dir) { | |
| // dir: 'left','right','up','down' | |
| let rotated = board; | |
| if (dir === 'up') rotated = rotateRight(board); | |
| if (dir === 'right') rotated = rotateRight(rotateRight(board)); | |
| if (dir === 'down') rotated = rotateRight(rotateRight(rotateRight(board))); | |
| let moved = false; | |
| const newB = rotated.map(row => { | |
| const before = row.slice(); | |
| const slid = slide(row); | |
| if (JSON.stringify(before) !== JSON.stringify(slid)) moved = true; | |
| return slid; | |
| }); | |
| // rotate back | |
| let result = newB; | |
| if (dir === 'up') result = rotateRight(rotateRight(rotateRight(newB))); | |
| if (dir === 'right') result = rotateRight(rotateRight(newB)); | |
| if (dir === 'down') result = rotateRight(newB); | |
| if (moved) { | |
| board = result; | |
| addRandomTile(); | |
| renderTiles(); | |
| if (isGameOver()) showGameOver(); | |
| } | |
| return moved; | |
| } | |
| function isGameOver() { | |
| // Any empty? | |
| for (let r=0;r<gridSize;r++) for (let c=0;c<gridSize;c++) if (board[r][c]===0) return false; | |
| // Any merges available? | |
| for (let r=0;r<gridSize;r++) for (let c=0;c<gridSize;c++) { | |
| const v = board[r][c]; | |
| if (r+1<gridSize && board[r+1][c]===v) return false; | |
| if (c+1<gridSize && board[r][c+1]===v) return false; | |
| } | |
| return true; | |
| } | |
| function showGameOver() { | |
| msgEl.classList.add('game-over'); | |
| } | |
| document.addEventListener('keydown', (e) => { | |
| if (msgEl.classList.contains('game-over')) return; | |
| if (e.key === 'ArrowLeft') move('left'); | |
| if (e.key === 'ArrowRight') move('right'); | |
| if (e.key === 'ArrowUp') move('up'); | |
| if (e.key === 'ArrowDown') move('down'); | |
| }); | |
| function init() { | |
| score = 0; | |
| board = Array.from({length:gridSize}, () => Array(gridSize).fill(0)); | |
| addRandomTile(); addRandomTile(); | |
| setupGrid(); | |
| msgEl.classList.remove('game-over'); | |
| } | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |