Chess
GPT 5.6 Luna Pro · typescript

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 24,635 bytes · SHA-256 9601d939a87c
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Royal Gambit — Chess vs Computer</title>
<style>
/* ── css: base ── */
:root{--ink:#241f1b;--muted:#84766a;--paper:#f5efe4;--panel:#fffaf2;--line:#ded2c2;--gold:#c59447;--gold2:#f0cf88;--wood1:#dfc092;--wood2:#aa774d;--danger:#c85d4d;--shadow:0 18px 50px #51351918}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 20% 0%,#fffaf1 0,#efe5d6 42%,#e3d6c6 100%);color:var(--ink);font-family:Georgia,'Times New Roman',serif}.app-shell{min-height:100vh;max-width:1180px;margin:auto;padding:28px 34px 40px}.topbar{display:flex;justify-content:space-between;align-items:end;margin-bottom:22px}.eyebrow,.section-label{font:700 10px/1.2 Arial,sans-serif;letter-spacing:.17em;color:var(--muted)}h1{font-size:36px;letter-spacing:-.03em;margin:5px 0 0;font-weight:500}.top-actions{display:flex;gap:8px;flex-wrap:wrap}button,select{font:600 12px Arial,sans-serif;color:var(--ink);background:#fffaf3;border:1px solid var(--line);border-radius:7px;padding:10px 13px;cursor:pointer;transition:.15s}button:hover{border-color:var(--gold);transform:translateY(-1px)}.game-layout{display:grid;grid-template-columns:minmax(420px,680px) 300px;gap:28px;align-items:start;justify-content:center}.board-wrap{position:relative}.turn-badge{display:inline-flex;background:var(--ink);color:#fff7ea;padding:8px 13px;border-radius:999px;font:700 11px Arial,sans-serif;letter-spacing:.05em;margin:0 0 12px 2px}.board{width:min(680px,calc(100vw - 420px));aspect-ratio:1;display:grid;grid-template-columns:repeat(8,1fr);border:10px solid #765037;border-radius:4px;box-shadow:var(--shadow),inset 0 0 0 1px #f4d4a0;overflow:hidden}.square{position:relative;display:grid;place-items:center;user-select:none}.light{background:var(--wood1)}.dark{background:var(--wood2)}.square.selected{box-shadow:inset 0 0 0 4px var(--gold2)}.square.last{background-image:linear-gradient(#d6a64a55,#d6a64a55)}.square.check{background-image:radial-gradient(circle,#d84f3fbb 0 38%,transparent 40%)}.piece{font-family:'Times New Roman',serif;font-size:clamp(38px,6.8vw,67px);line-height:1;position:relative;z-index:2;cursor:grab;text-shadow:1px 2px 1px #3b241855}.white-piece{color:#fffdf7;-webkit-text-stroke:1px #4b3324;text-shadow:0 2px 1px #4b332488}.black-piece{color:#29211d;text-shadow:0 2px 1px #fff1d122}.dot{width:17%;height:17%;background:#5c402c99;border-radius:50%;position:absolute}.capture-ring{position:absolute;inset:10%;border:5px solid #5c402c99;border-radius:50%}.coord{position:absolute;font:700 10px Arial;color:#5e412d88;bottom:4px;left:5px}.dark .coord{color:#f7dfb188}.status{min-height:24px;margin:12px 3px;color:var(--muted);font:13px Arial}.side-panel{background:#fffaf2cc;border:1px solid #fff;border-radius:12px;box-shadow:var(--shadow);overflow:hidden}.panel-section{padding:17px 18px;border-bottom:1px solid var(--line)}.section-head{display:flex;justify-content:space-between;align-items:center}.segmented{display:flex;margin-top:10px}.segmented button{flex:1;border-radius:0;padding:8px 5px}.segmented button:first-child{border-radius:6px 0 0 6px}.segmented button:last-child{border-radius:0 6px 6px 0}.segmented .active{background:var(--ink);color:white;border-color:var(--ink)}.material{color:#9b7135;font:bold 16px Georgia}.captured{min-height:29px;margin-top:10px;font-size:22px;letter-spacing:2px}.moves-section{height:310px}.moves{height:270px;overflow:auto;margin-top:10px;font:14px/1.8 Arial}.move-row{display:grid;grid-template-columns:34px 1fr 1fr;border-bottom:1px solid #eadfce}.move-no{color:#a69380}.move-cell{font-weight:600}.muted,.hint{color:var(--muted);font:12px Arial}.controls select{width:100%;margin-top:10px}.hint{line-height:1.5;margin:12px 0 0}.promotion{position:fixed;inset:0;background:#241f1b55;display:grid;place-items:center;z-index:10}.promo-card{background:var(--paper);padding:18px;border-radius:12px;box-shadow:var(--shadow);text-align:center}.promo-choices{display:flex;gap:8px;margin-top:12px}.promo-choices button{font-size:34px;width:58px;padding:6px}@media(max-width:850px){.app-shell{padding:18px 12px}.topbar{align-items:start;gap:12px;flex-direction:column}h1{font-size:30px}.game-layout{display:block}.board{width:min(100%,680px);margin:auto}.side-panel{margin:22px auto 0;max-width:680px}.top-actions{gap:5px}.top-actions button{padding:8px 9px}.moves-section{height:240px}.moves{height:200px}}
</style>
</head>
<body>
<main class="app-shell" id="app"><header class="topbar"><div><div class="eyebrow">THE ROYAL GAME</div><h1>Royal Gambit</h1></div><div class="top-actions"><button id="newGame">New game</button><button id="undo">Undo</button><button id="flip">Flip board</button><button id="mute" aria-label="Toggle sound">Sound on</button></div></header><section class="game-layout"><div class="board-wrap"><div id="turnBadge" class="turn-badge">White to move</div><div id="board" class="board" role="grid"></div><div id="status" class="status">Choose a move</div></div><aside class="side-panel"><div class="panel-section"><div class="section-label">PLAY AS</div><div class="segmented"><button class="side-choice active" data-side="white">White</button><button class="side-choice" data-side="black">Black</button><button class="side-choice" data-side="both">Both</button></div></div><div class="panel-section"><div class="section-head"><span class="section-label">CAPTURED</span><span id="material" class="material">+0</span></div><div id="captured" class="captured"></div></div><div class="panel-section moves-section"><div class="section-head"><span class="section-label">MOVES</span><span id="moveCount" class="muted">0</span></div><div id="moves" class="moves"></div></div><div class="panel-section controls"><label class="section-label" for="difficulty">COMPUTER STRENGTH</label><select id="difficulty"><option value="1">Casual · depth 1</option><option value="2" selected="">Club · depth 2</option><option value="3">Master · depth 3</option></select><p class="hint">Click a piece or drag it to move. Gold dots show legal destinations.</p></div></aside></section><div id="promotion" class="promotion" hidden=""><div class="promo-card"><div class="section-label">PROMOTE TO</div><div id="promoChoices" class="promo-choices"></div></div></div></main>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {};
function __require(id) {
if (__cache[id]) return __cache[id].exports;
var module = __cache[id] = { exports: {} };
var factory = __mods[id];
if (!factory) throw new Error("Module not found: " + id);
factory(module.exports, function (spec) {
var target = (__map[id] && __map[id][spec]) || spec;
return __require(target);
}, module);
return module.exports;
}
// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// ROYAL GAMBIT — rules engine, UI, AI, persistence
const GLYPH = { P: '♙', N: '♘', B: '♗', R: '♖', Q: '♕', K: '♔', p: '♟', n: '♞', b: '♝', r: '♜', q: '♛', k: '♚' };
const VAL = { P: 100, N: 320, B: 330, R: 500, Q: 900, K: 20000, p: -100, n: -320, b: -330, r: -500, q: -900, k: -20000 };
const files = 'abcdefgh';
const initial = () => { const b = Array(64).fill(null); const back = 'RNBQKBNR'; for (let x = 0; x < 8; x++) {
b[x] = { t: back[x], c: 'w' };
b[8 + x] = { t: 'P', c: 'w' };
b[48 + x] = { t: 'p', c: 'b' };
b[56 + x] = { t: back[x].toLowerCase(), c: 'b' };
} return { b, turn: 'w', castle: 'KQkq', ep: null, half: 0, full: 1, last: null, history: [], captured: { w: [], b: [] }, snap: [] }; };
let s = load() || initial();
let flipped = false, selected = null, legal = [];
let human = 'w';
let muted = false;
let audio = null;
const el = (q) => document.querySelector(q);
const boardEl = el('#board');
function copy(x) { return { b: x.b.map(p => p ? { ...p } : null), turn: x.turn, castle: x.castle, ep: x.ep, half: x.half, full: x.full, last: x.last ? { ...x.last } : null, history: [...x.history], captured: { w: [...x.captured.w], b: [...x.captured.b] }, snap: [] }; }
function xy(i) { return [i % 8, Math.floor(i / 8)]; }
function ix(x, y) { return y * 8 + x; }
function enemy(c) { return c === 'w' ? 'b' : 'w'; }
function king(st, c) { return st.b.findIndex(p => p?.t === 'K' && p.c === c); }
function attacked(st, sq, by) { const [x, y] = xy(sq); for (let i = 0; i < 64; i++) {
const p = st.b[i];
if (!p || p.c !== by)
continue;
const [a, d] = xy(i), dx = x - a, dy = y - d;
if (p.t.toUpperCase() === 'P' && Math.abs(dx) === 1 && dy === (by === 'w' ? 1 : -1))
return true;
if (p.t.toUpperCase() === 'N' && [[1, 2], [2, 1], [-1, 2], [-2, 1], [1, -2], [2, -1], [-1, -2], [-2, -1]].some(v => v[0] === dx && v[1] === dy))
return true;
if (p.t.toUpperCase() === 'K' && Math.max(Math.abs(dx), Math.abs(dy)) === 1)
return true;
const diag = Math.abs(dx) === Math.abs(dy), straight = dx === 0 || dy === 0;
if ((p.t.toUpperCase() === 'B' && diag) || (p.t.toUpperCase() === 'R' && straight) || (p.t.toUpperCase() === 'Q' && (diag || straight))) {
const sx = Math.sign(dx), sy = Math.sign(dy);
let xx = a + sx, yy = d + sy, clear = true;
while (xx !== x || yy !== y) {
if (st.b[ix(xx, yy)])
clear = false;
xx += sx;
yy += sy;
}
if (clear)
return true;
}
} return false; }
function pseudo(st) {
const out = [];
const c = st.turn;
for (let i = 0; i < 64; i++) {
const p = st.b[i];
if (!p || p.c !== c)
continue;
const [x, y] = xy(i), typ = p.t.toUpperCase();
const add = (to, extra = {}) => { if (to < 0 || to >= 64)
return; const [tx, ty] = xy(to); if (Math.abs(tx - x) > 2 || Math.abs(ty - y) > 2)
return; if (!st.b[to] || st.b[to].c !== c)
out.push({ from: i, to, ...extra }); };
if (typ === 'P') {
const dir = c === 'w' ? 1 : -1, start = c === 'w' ? 1 : 6, end = c === 'w' ? 7 : 0;
let to = ix(x, y + dir);
if (to >= 0 && to < 64 && !st.b[to]) {
if (y + dir === end)
['Q', 'R', 'B', 'N'].forEach(prom => out.push({ from: i, to, prom }));
else
out.push({ from: i, to });
if (y === start && !st.b[ix(x, y + 2 * dir)])
out.push({ from: i, to: ix(x, y + 2 * dir) });
}
for (const dx of [-1, 1]) {
if (x + dx < 0 || x + dx > 7)
continue;
to = ix(x + dx, y + dir);
const q = st.b[to];
if (q && q.c !== c) {
if (y + dir === end)
['Q', 'R', 'B', 'N'].forEach(prom => out.push({ from: i, to, prom }));
else
out.push({ from: i, to });
}
if (st.ep === to)
out.push({ from: i, to, ep: true });
}
}
else if (typ === 'N') {
[[1, 2], [2, 1], [-1, 2], [-2, 1], [1, -2], [2, -1], [-1, -2], [-2, -1]].forEach(v => { if (x + v[0] >= 0 && x + v[0] < 8 && y + v[1] >= 0 && y + v[1] < 8)
add(ix(x + v[0], y + v[1])); });
}
else if (typ === 'K') {
for (let dx = -1; dx <= 1; dx++)
for (let dy = -1; dy <= 1; dy++)
if ((dx || dy) && x + dx >= 0 && x + dx < 8 && y + dy >= 0 && y + dy < 8)
add(ix(x + dx, y + dy));
const rank = c === 'w' ? 0 : 7;
if (i === ix(4, rank) && !attacked(st, i, enemy(c))) {
if (st.castle.includes(c === 'w' ? 'K' : 'k') && !st.b[ix(5, rank)] && !st.b[ix(6, rank)] && !attacked(st, ix(5, rank), enemy(c)) && !attacked(st, ix(6, rank), enemy(c)))
out.push({ from: i, to: ix(6, rank), castle: true });
if (st.castle.includes(c === 'w' ? 'Q' : 'q') && !st.b[ix(1, rank)] && !st.b[ix(2, rank)] && !st.b[ix(3, rank)] && !attacked(st, ix(3, rank), enemy(c)) && !attacked(st, ix(2, rank), enemy(c)))
out.push({ from: i, to: ix(2, rank), castle: true });
}
}
else {
const dirs = typ === 'B' ? [[1, 1], [-1, 1], [1, -1], [-1, -1]] : typ === 'R' ? [[1, 0], [-1, 0], [0, 1], [0, -1]] : [[1, 1], [-1, 1], [1, -1], [-1, -1], [1, 0], [-1, 0], [0, 1], [0, -1]];
for (const d of dirs) {
let xx = x + d[0], yy = y + d[1];
while (xx >= 0 && xx < 8 && yy >= 0 && yy < 8) {
const to = ix(xx, yy);
if (!st.b[to])
out.push({ from: i, to });
else {
if (st.b[to].c !== c)
out.push({ from: i, to });
break;
}
xx += d[0];
yy += d[1];
}
}
}
}
return out;
}
function apply(st, m) { const n = copy(st), p = n.b[m.from]; const cap = m.ep ? n.b[m.to + (p.c === 'w' ? -8 : 8)] : n.b[m.to]; n.b[m.from] = null; n.b[m.to] = { t: m.prom || (p.c === 'w' ? p.t.toUpperCase() : p.t.toLowerCase()), c: p.c }; if (m.ep)
n.b[m.to + (p.c === 'w' ? -8 : 8)] = null; if (m.castle) {
const r = m.to > m.from ? m.to + 1 : m.to - 2;
const rt = m.to > m.from ? m.to - 1 : m.to + 1;
n.b[rt] = n.b[r];
n.b[r] = null;
} if (p.t.toUpperCase() === 'K')
n.castle = n.castle.replace(p.c === 'w' ? /[KQ]/g : /[kq]/g, ''); if (p.t.toUpperCase() === 'R') {
if (m.from === 0)
n.castle = n.castle.replace('Q', '');
if (m.from === 7)
n.castle = n.castle.replace('K', '');
if (m.from === 56)
n.castle = n.castle.replace('q', '');
if (m.from === 63)
n.castle = n.castle.replace('k', '');
} if (cap && cap.t.toUpperCase() === 'R') {
if (m.to === 0)
n.castle = n.castle.replace('Q', '');
if (m.to === 7)
n.castle = n.castle.replace('K', '');
if (m.to === 56)
n.castle = n.castle.replace('q', '');
if (m.to === 63)
n.castle = n.castle.replace('k', '');
} n.ep = null; if (p.t.toUpperCase() === 'P' && Math.abs(m.to - m.from) === 16)
n.ep = (m.to + m.from) / 2; n.half = (p.t.toUpperCase() === 'P' || cap) ? 0 : n.half + 1; if (st.turn === 'b')
n.full++; n.turn = enemy(st.turn); n.last = m; if (cap)
n.captured[p.c].push(cap.t); return n; }
function legalMoves(st = s) { return pseudo(st).filter(m => { const n = apply(st, m); return !attacked(n, king(n, st.turn), enemy(st.turn)); }); }
function san(st, m) { const p = st.b[m.from]; if (m.castle)
return m.to > m.from ? 'O-O' : 'O-O-O'; let out = p.t.toUpperCase() === 'P' ? (st.b[m.to] || m.ep ? files[m.from % 8] + 'x' : '') : p.t.toUpperCase(); if (p.t.toUpperCase() !== 'P') {
const peers = legalMoves(st).filter(q => q.to === m.to && q.from !== m.from && st.b[q.from]?.t === p.t);
if (peers.length)
out += files[m.from % 8];
} if (st.b[m.to] || m.ep)
out += 'x'; out += files[m.to % 8] + (Math.floor(m.to / 8) + 1); if (m.prom)
out += '=' + m.prom; const n = apply(st, m); if (attacked(n, king(n, n.turn), n.turn === 'w' ? 'b' : 'w'))
out += legalMoves(n).length ? ' +' : '#'; return out; }
function doMove(m, record = true) { const notation = san(s, m); const stack = s.snap; const before = copy(s); before.snap = []; if (record)
stack.push(before); s = apply(s, m); s.snap = stack; if (record) {
s.history.push(notation);
save();
} selected = null; legal = []; render(); tone(s.history[s.history.length - 1]?.includes('x') ? 'capture' : notation.includes('+') ? 'check' : 'move'); if (!gameOver() && ((human !== 'both' && s.turn !== human))) {
setTimeout(aiMove, 80);
} }
function gameOver() { const lm = legalMoves(s); let msg = ''; if (!lm.length) {
if (attacked(s, king(s, s.turn), enemy(s.turn)))
msg = `Checkmate · ${s.turn === 'w' ? 'Black' : 'White'} wins`;
else
msg = 'Stalemate · Draw';
}
else if (s.half >= 100)
msg = 'Draw · 50-move rule';
else {
const pieces = s.b.filter(Boolean);
if (pieces.every(p => p.t.toUpperCase() === 'K' || p.t.toUpperCase() === 'B' || p.t.toUpperCase() === 'N') && pieces.filter(p => p.t.toUpperCase() !== 'K').length <= 1)
msg = 'Draw · insufficient material';
} if (msg) {
el('#status').textContent = msg;
return true;
} return false; }
function render() { boardEl.innerHTML = ''; const lm = legalMoves(s), check = attacked(s, king(s, s.turn), enemy(s.turn)); el('#turnBadge').textContent = (s.turn === 'w' ? 'White' : 'Black') + ' to move' + (check ? ' · Check' : ''); for (let row = 0; row < 8; row++)
for (let col = 0; col < 8; col++) {
const x = flipped ? 7 - col : col, y = flipped ? row : 7 - row, i = ix(x, y), d = document.createElement('div');
d.className = 'square ' + ((x + y) % 2 ? 'dark' : 'light');
d.dataset.i = String(i);
if (s.last && (s.last.from === i || s.last.to === i))
d.classList.add('last');
if (i === selected)
d.classList.add('selected');
if (check && i === king(s, s.turn))
d.classList.add('check');
const mv = selected !== null ? lm.filter(m => m.from === selected && m.to === i) : [];
if (mv.length) {
const q = document.createElement('span');
q.className = s.b[i] ? 'capture-ring' : 'dot';
d.append(q);
}
const p = s.b[i];
if (p) {
const q = document.createElement('span');
q.className = 'piece ' + (p.c === 'w' ? 'white-piece' : 'black-piece');
q.textContent = GLYPH[p.c === 'w' ? p.t.toUpperCase() : p.t.toLowerCase()];
q.draggable = true;
q.ondragstart = e => { e.dataTransfer?.setData('text/plain', String(i)); select(i); };
d.append(q);
}
if ((flipped ? x === 7 : x === 0) && (y === 0 || y === 7)) {
const q = document.createElement('span');
q.className = 'coord';
q.textContent = String(y + 1);
d.append(q);
}
d.onclick = () => select(i);
d.ondragover = e => e.preventDefault();
d.ondrop = e => { e.preventDefault(); const from = Number(e.dataTransfer?.getData('text/plain')); tryMove(from, i); };
boardEl.append(d);
} renderSide(); el('#status').textContent = check ? 'Your king is in check.' : (gameOver() ? '' : 'Select a piece to begin'); }
function select(i) { if (gameOver() || s.turn !== human && human !== 'both')
return; if (s.b[i]?.c === s.turn) {
selected = i;
legal = legalMoves(s);
render();
}
else if (selected !== null)
tryMove(selected, i); }
function tryMove(from, to) { const ms = legalMoves(s).filter(m => m.from === from && m.to === to); if (!ms.length)
return; if (ms.some(m => m.prom)) {
const choices = el('#promoChoices');
choices.innerHTML = '';
el('#promotion').hidden = false;
for (const p of ['Q', 'R', 'B', 'N']) {
const b = document.createElement('button');
b.textContent = GLYPH[p];
b.onclick = () => { el('#promotion').hidden = true; doMove(ms.find(m => m.prom === p)); };
choices.append(b);
}
}
else
doMove(ms[0]); }
function renderSide() { el('#moveCount').textContent = String(s.history.length); const box = el('#moves'); box.innerHTML = ''; for (let i = 0; i < s.history.length; i += 2) {
const r = document.createElement('div');
r.className = 'move-row';
r.innerHTML = `<span class="move-no">${i / 2 + 1}.</span><span class="move-cell">${s.history[i] || ''}</span><span class="move-cell">${s.history[i + 1] || ''}</span>`;
box.append(r);
} box.scrollTop = box.scrollHeight; el('#captured').textContent = s.captured.w.map(x => GLYPH[x.toLowerCase()]).join('') + ' ' + s.captured.b.map(x => GLYPH[x.toUpperCase()]).join(''); const diff = s.captured.w.reduce((a, p) => a + (VAL[p] || 0), 0) + s.captured.b.reduce((a, p) => a - (VAL[p] || 0), 0); el('#material').textContent = (diff >= 0 ? '+' : '') + diff; }
function aiMove() { if (gameOver() || s.turn === human || human === 'both')
return; const depth = Number(el('#difficulty').value), best = minimax(s, depth, -1e9, 1e9); if (best)
doMove(best); }
function minimax(st, d, a, b) { const ms = legalMoves(st).sort((x, y) => (st.b[y.to] ? 1 : 0) - (st.b[x.to] ? 1 : 0)); if (!d || !ms.length)
return null; if (d === 1) {
let bm = ms[0], bv = st.turn === 'w' ? -1e9 : 1e9;
for (const m of ms) {
const v = score(apply(st, m));
if (st.turn === 'w' ? v > bv : v < bv) {
bv = v;
bm = m;
}
}
return bm;
} let bm = null, bv = st.turn === 'w' ? -1e9 : 1e9; for (const m of ms) {
const n = apply(st, m), child = minimaxValue(n, d - 1, a, b);
if (st.turn === 'w' ? child > bv : child < bv) {
bv = child;
bm = m;
}
if (st.turn === 'w')
a = Math.max(a, bv);
else
b = Math.min(b, bv);
if (b <= a)
break;
} return bm; }
function minimaxValue(st, d, a, b) { const ms = legalMoves(st); if (!d || !ms.length)
return score(st); let v = st.turn === 'w' ? -1e9 : 1e9; for (const m of ms) {
const z = minimaxValue(apply(st, m), d - 1, a, b);
if (st.turn === 'w') {
v = Math.max(v, z);
a = Math.max(a, v);
}
else {
v = Math.min(v, z);
b = Math.min(b, v);
}
if (b <= a)
break;
} return v; }
function score(st) { return st.b.reduce((v, p) => v + (p ? (VAL[p.t] || 0) : 0), 0); }
function save() { localStorage.setItem('royal-gambit', JSON.stringify({ ...s, snap: [] })); }
function load() { try {
const x = JSON.parse(localStorage.getItem('royal-gambit') || 'null');
return x && x.b ? x : null;
}
catch {
return null;
} }
function tone(kind) { if (muted)
return; audio ?? (audio = new AudioContext()); const o = audio.createOscillator(), g = audio.createGain(); o.frequency.value = kind === 'capture' ? 180 : kind === 'check' ? 520 : 330; g.gain.value = .035; o.connect(g); g.connect(audio.destination); o.start(); o.stop(audio.currentTime + .07); }
function uci(str) { const f = files.indexOf(str[0]) + (Number(str[1]) - 1) * 8, t = files.indexOf(str[2]) + (Number(str[3]) - 1) * 8; return legalMoves(s).find(m => m.from === f && m.to === t && (!str[4] || m.prom === str[4].toUpperCase())) || null; }
document.querySelector('#newGame').onclick = () => { s = initial(); save(); render(); };
document.querySelector('#undo').onclick = () => { if (!s.snap.length)
return; s = s.snap.pop(); save(); render(); };
document.querySelector('#flip').onclick = () => { flipped = !flipped; render(); };
document.querySelector('#mute').onclick = () => { muted = !muted; el('#mute').textContent = muted ? 'Sound off' : 'Sound on'; };
document.querySelectorAll('.side-choice').forEach(x => x.addEventListener('click', () => { document.querySelectorAll('.side-choice').forEach(y => y.classList.remove('active')); x.classList.add('active'); human = x.dataset.side; render(); if (human === 'b' && s.turn === 'w')
aiMove(); if (human === 'w' && s.turn === 'b')
aiMove(); }));
window.game = { fen: () => s.b.map((p, i) => p ? p.t : '.').join('') + ' ' + s.turn + ' ' + s.castle + ' ' + (s.ep === null ? '-' : files[s.ep % 8] + (Math.floor(s.ep / 8) + 1)), legalMoves: () => legalMoves(s).map(m => files[m.from % 8] + (Math.floor(m.from / 8) + 1) + files[m.to % 8] + (Math.floor(m.to / 8) + 1) + (m.prom || '').toLowerCase()), move: (u) => { human = 'both'; const m = uci(u); if (!m)
return false; doMove(m); return true; }, state: () => JSON.parse(JSON.stringify(s)), setSide: (side) => { human = side === 'both' ? 'both' : side === 'black' ? 'b' : 'w'; render(); }, reset: () => { s = initial(); human = 'both'; save(); render(); } };
render();
if (human !== s.turn && human !== 'both')
setTimeout(aiMove, 250);
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-luna-pro","provider":"openrouter","persona":"typescript","sessionId":"61949d48-2f49-4b24-b891-c60d3399ba6e","tokensIn":2170227,"tokensOut":47455,"tokensTotal":2217682,"turns":38,"toolCalls":39,"failedToolCalls":2,"timestamp":"2026-07-31T02:35:56.147Z"} -->