Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

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>