game / 2048.html
luqmanabban's picture
Upload 5 files
11f2446 verified
<!DOCTYPE html>
<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>