91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
190 lines
5.4 KiB
HTML
190 lines
5.4 KiB
HTML
<div class="p-2">
|
|
<h1 class="text-xl mb-2">2048</h1>
|
|
<div id="board" class="grid grid-cols-4 gap-1 w-64 h-64 select-none"></div>
|
|
<div class="mt-2 text-sm">
|
|
<span>Score: <span id="score">0</span></span>
|
|
<span class="ml-4">Max: <span id="max">0</span></span>
|
|
<span class="ml-4" id="status"></span>
|
|
</div>
|
|
<p class="mt-2 text-xs text-gray-500">Use arrow keys.</p>
|
|
</div>
|
|
<script>
|
|
(function () {
|
|
const boardEl = document.getElementById('board');
|
|
const scoreEl = document.getElementById('score');
|
|
const maxEl = document.getElementById('max');
|
|
const statusEl = document.getElementById('status');
|
|
|
|
const size = 4;
|
|
let grid = Array.from({ length: size }, () => Array(size).fill(0));
|
|
let score = 0;
|
|
window.__lost = false;
|
|
|
|
function tileClass(v) {
|
|
if (v === 0) return 'bg-gray-200';
|
|
const map = {
|
|
2: 'bg-amber-100',
|
|
4: 'bg-amber-200',
|
|
8: 'bg-amber-300',
|
|
16: 'bg-amber-400',
|
|
32: 'bg-orange-400',
|
|
64: 'bg-orange-500',
|
|
128: 'bg-yellow-500',
|
|
256: 'bg-yellow-600',
|
|
512: 'bg-lime-600',
|
|
1024: 'bg-green-600',
|
|
2048: 'bg-emerald-600',
|
|
};
|
|
return (map[v] || 'bg-slate-600') + ' text-black';
|
|
}
|
|
|
|
function render() {
|
|
boardEl.innerHTML = '';
|
|
let m = 0;
|
|
for (let r = 0; r < size; r++) {
|
|
for (let c = 0; c < size; c++) {
|
|
const v = grid[r][c];
|
|
m = Math.max(m, v);
|
|
const cell = document.createElement('div');
|
|
cell.className = 'flex items-center justify-center ' + tileClass(v);
|
|
cell.style.height = '4rem';
|
|
cell.textContent = v ? v : '';
|
|
boardEl.appendChild(cell);
|
|
}
|
|
}
|
|
scoreEl.textContent = score;
|
|
maxEl.textContent = m;
|
|
statusEl.textContent = window.__lost ? 'Lost' : '';
|
|
window.__max_tile = m;
|
|
}
|
|
|
|
function emptyCells() {
|
|
const out = [];
|
|
for (let r = 0; r < size; r++)
|
|
for (let c = 0; c < size; c++) if (grid[r][c] === 0) out.push([r, c]);
|
|
return out;
|
|
}
|
|
|
|
function spawn() {
|
|
const empties = emptyCells();
|
|
if (empties.length === 0) return false;
|
|
const [r, c] = empties[Math.floor(Math.random() * empties.length)];
|
|
grid[r][c] = Math.random() < 0.9 ? 2 : 4;
|
|
return true;
|
|
}
|
|
|
|
function compress(line) {
|
|
const arr = line.filter((v) => v !== 0);
|
|
const out = [];
|
|
let gained = 0;
|
|
for (let i = 0; i < arr.length; i++) {
|
|
if (i + 1 < arr.length && arr[i] === arr[i + 1]) {
|
|
out.push(arr[i] * 2);
|
|
gained += arr[i] * 2;
|
|
i++;
|
|
} else out.push(arr[i]);
|
|
}
|
|
while (out.length < size) out.push(0);
|
|
return { line: out, gained };
|
|
}
|
|
|
|
function getCol(c) {
|
|
return Array.from({ length: size }, (_, r) => grid[r][c]);
|
|
}
|
|
function setCol(c, col) {
|
|
for (let r = 0; r < size; r++) grid[r][c] = col[r];
|
|
}
|
|
|
|
function move(dir) {
|
|
// returns true if grid changed
|
|
let changed = false;
|
|
let gainedTotal = 0;
|
|
if (dir === 'left' || dir === 'right') {
|
|
for (let r = 0; r < size; r++) {
|
|
let row = grid[r].slice();
|
|
if (dir === 'right') row.reverse();
|
|
const { line, gained } = compress(row);
|
|
if (dir === 'right') line.reverse();
|
|
if (line.some((v, i) => v !== grid[r][i])) {
|
|
grid[r] = line;
|
|
changed = true;
|
|
}
|
|
gainedTotal += gained;
|
|
}
|
|
} else if (dir === 'up' || dir === 'down') {
|
|
for (let c = 0; c < size; c++) {
|
|
let col = getCol(c);
|
|
if (dir === 'down') col.reverse();
|
|
const { line, gained } = compress(col);
|
|
if (dir === 'down') line.reverse();
|
|
if (line.some((v, i) => v !== grid[i][c])) {
|
|
setCol(c, line);
|
|
changed = true;
|
|
}
|
|
gainedTotal += gained;
|
|
}
|
|
}
|
|
if (changed) {
|
|
score += gainedTotal;
|
|
spawn();
|
|
}
|
|
window.__lost = !canMove();
|
|
render();
|
|
return changed;
|
|
}
|
|
|
|
function canMove() {
|
|
if (emptyCells().length > 0) return true;
|
|
for (let r = 0; r < size; r++)
|
|
for (let c = 0; c < size; c++) {
|
|
const v = grid[r][c];
|
|
if (r + 1 < size && grid[r + 1][c] === v) return true;
|
|
if (c + 1 < size && grid[r][c + 1] === v) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// simple heuristic next move: try directions that change board, prefer resulting empties and merges
|
|
function tryMove(dir) {
|
|
const snap = grid.map((row) => row.slice());
|
|
const s0 = score;
|
|
const changed = move(dir);
|
|
const empties = emptyCells().length;
|
|
// revert
|
|
grid = snap;
|
|
score = s0;
|
|
render();
|
|
return { changed, empties };
|
|
}
|
|
|
|
window.__next_move = function () {
|
|
const dirs = ['up', 'left', 'right', 'down'];
|
|
let best = null;
|
|
let bestScore = -1;
|
|
for (const d of dirs) {
|
|
const t = tryMove(d);
|
|
if (!t.changed) continue;
|
|
const sc = t.empties;
|
|
if (sc > bestScore) {
|
|
bestScore = sc;
|
|
best = d;
|
|
}
|
|
}
|
|
return best; // can be null
|
|
};
|
|
|
|
window.addEventListener('keydown', (e) => {
|
|
const k = e.key;
|
|
if (k === 'ArrowLeft') move('left');
|
|
if (k === 'ArrowRight') move('right');
|
|
if (k === 'ArrowUp') move('up');
|
|
if (k === 'ArrowDown') move('down');
|
|
});
|
|
|
|
// init
|
|
spawn();
|
|
spawn();
|
|
render();
|
|
})();
|
|
</script>
|