← Chess results

Chess

GPT 5.6 terra · typescript

Initial view of Chess

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 22,651 bytes · SHA-256 499393256905
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ivory &amp; Walnut Chess</title>
<style>
/* ── css: base ── */
*{box-sizing:border-box}body{margin:0;min-height:100vh;background:radial-gradient(circle at 20% 10%,#3a302d,#171919 60%);color:#eadfc9;font-family:Georgia,'Times New Roman',serif;display:grid;place-items:center}.shell{display:flex;gap:30px;padding:24px;align-items:center}.board-wrap{position:relative}.board{width:min(76vw,640px);height:min(76vw,640px);max-height:calc(100vh - 70px);max-width:calc(100vh - 70px);display:grid;grid-template-columns:repeat(8,1fr);grid-template-rows:repeat(8,1fr);box-shadow:0 18px 50px #000a,0 0 0 7px #a67c4a,0 0 0 10px #342319;user-select:none;touch-action:none}.sq{position:relative;display:grid;place-items:center;font-size:clamp(30px,7vw,68px);line-height:1;cursor:pointer}.light{background:#e6cfaa}.dark{background:#96704d}.sq::after{content:'';position:absolute;inset:0;pointer-events:none}.last::after{background:#f5d56873}.selected::after{background:#d6aa3970;box-shadow:inset 0 0 0 4px #ffe58a}.target::before{content:'';position:absolute;width:20%;aspect-ratio:1;border-radius:50%;background:#3c342899;z-index:1}.capture::before{content:'';position:absolute;inset:8%;border:4px solid #552f28a8;border-radius:50%;z-index:1}.check{box-shadow:inset 0 0 24px 9px #d63434!important}.piece{position:relative;z-index:2;filter:drop-shadow(1px 2px 0 #0006)}.white{color:#fff8e8;text-shadow:0 1px #33251d,1px 0 #33251d,-1px 0 #33251d}.black{color:#292322;text-shadow:0 1px #d3b88d}.coord{position:absolute;font:bold 11px Arial;color:#4c3828;opacity:.85}.sq:nth-child(8n+1) .rank{top:3px;left:4px}.sq:nth-child(n+57) .file{bottom:3px;right:4px}.dark .coord{color:#ead4af}.status{text-align:center;font-size:15px;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:14px;color:#d9bb81}.panel{width:285px;min-height:520px;background:#211f1d;border:1px solid #5f4b38;box-shadow:0 15px 40px #0008;padding:19px}.brand{display:flex;align-items:center;gap:12px;border-bottom:1px solid #66513d;padding-bottom:16px}.brand>span{font-size:43px;color:#d2ae75}.brand h1{font-size:22px;margin:0;color:#f1dfc0;font-weight:normal}.brand p{margin:3px 0 0;font:11px Arial;letter-spacing:1.5px;color:#ad9372;text-transform:uppercase}.trays{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:14px 0}.trays label,.moves-head,.controls label{font:10px Arial;letter-spacing:1.2px;color:#a68d6e}.captures{min-height:25px;font-size:22px;letter-spacing:-4px;color:#e7d2ac}.moves-head{display:flex;justify-content:space-between;border-top:1px solid #514334;padding:10px 0 7px}.moves-head button{border:0;background:none;color:#d8bd8e;font:11px Arial;cursor:pointer}.moves{height:245px;overflow:auto;background:#181817;border:1px solid #3b332b;padding:6px;font:14px 'Courier New',monospace;color:#decaa6}.move-row{display:grid;grid-template-columns:30px 1fr 1fr;padding:4px}.move-row:nth-child(odd){background:#24221f}.num{color:#957757}.controls{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:14px 0}.controls label{display:grid;gap:5px}.controls select{background:#302b26;color:#eadfc9;border:1px solid #6d5840;padding:6px;font-family:Georgia}.buttons{display:flex;gap:7px;flex-wrap:wrap}.buttons button{background:#5a4230;color:#f4e1be;border:1px solid #98734c;padding:8px 9px;font-family:Georgia;cursor:pointer}.buttons button:hover{background:#765337}.hint{font:italic 12px Georgia;color:#9b866b;margin:15px 0 0}.banner{position:absolute;z-index:5;left:50%;top:50%;transform:translate(-50%,-50%);background:#211c18eF;border:1px solid #c29c63;box-shadow:0 8px 40px #000;padding:18px 28px;text-align:center;color:#f3dfba;font-size:21px;white-space:nowrap}.hidden{display:none}@media(max-width:850px){body{display:block}.shell{flex-direction:column;padding:18px;gap:22px}.board{width:min(92vw,640px);height:min(92vw,640px);max-width:none;max-height:none}.panel{width:min(92vw,640px);min-height:0}.moves{height:160px}}
/* ── css: promotion ── */
.promotion{position:fixed;z-index:20;display:flex;background:#241d18;border:2px solid #c29c63;box-shadow:0 8px 24px #000;padding:4px}.promotion.hidden{display:none}.promotion button{font-size:34px;width:48px;height:52px;background:#ead6ae;color:#3a2921;border:0;border-right:1px solid #a4774f;cursor:pointer}.promotion button:hover{background:#fff0c9}
</style>
</head>
<body>
<main class="shell"><section class="board-wrap"><div id="status" class="status"></div><div id="board" class="board" aria-label="Chess board"></div><div id="banner" class="banner hidden"></div></section><aside class="panel"><div class="brand"><span>♚</span><div><h1>Ivory &amp; Walnut</h1><p>Chess against the house</p></div></div><div class="trays"><div><label>WHITE TAKES</label><div id="cap-white" class="captures"></div></div><div><label>BLACK TAKES</label><div id="cap-black" class="captures"></div></div></div><div class="moves-head"><span>GAME SCORE</span><button id="flip" title="Flip board">↻ Flip</button></div><div id="moves" class="moves"></div><div class="controls"><label>YOU PLAY <select id="side"><option value="w">White</option><option value="b">Black</option><option value="both">Both sides</option></select></label><label>DIFFICULTY <select id="difficulty"><option value="1">Gentle · 1</option><option value="2" selected="">Club · 2</option><option value="3">Keen · 3</option></select></label></div><div class="buttons"><button id="undo">↶ Undo</button><button id="newgame">New Game</button><button id="mute">♬ Sound on</button></div><p class="hint">Click or drag a piece. Legal moves glow.</p></aside></main>
<div id="promotion" class="promotion hidden" aria-label="Choose promotion"><button data-p="Q">♕</button><button data-p="R">♖</button><button data-p="B">♗</button><button data-p="N">♘</button></div>
<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 });
// Ivory & Walnut Chess — rules engine, AI, and UI
const DEPTHS = { "1": 1, "2": 2, "3": 3 };
const G = '♔♕♖♗♘♙♚♛♜♝♞♟'.split('');
const glyph = (p) => G['KQRBNP'.indexOf(p.t) + (p.c === 'b' ? 6 : 0)];
const enemy = (c) => c === 'w' ? 'b' : 'w';
const xy = (i) => [i % 8, Math.floor(i / 8)];
const sq = (i) => 'abcdefgh'[i % 8] + (8 - Math.floor(i / 8));
const ix = (s) => 'abcdefgh'.indexOf(s[0]) + (8 - Number(s[1])) * 8;
function initial() { const b = Array(64).fill(null); const back = 'RNBQKBNR'; for (let x = 0; x < 8; x++) {
    b[x] = { c: 'b', t: back[x] };
    b[8 + x] = { c: 'b', t: 'P' };
    b[48 + x] = { c: 'w', t: 'P' };
    b[56 + x] = { c: 'w', t: back[x] };
} return { b, turn: 'w', castle: 'KQkq', ep: null, half: 0, full: 1 }; }
function clone(p) { return { b: p.b.map(x => x ? { ...x } : null), turn: p.turn, castle: p.castle, ep: p.ep, half: p.half, full: p.full }; }
function attacked(p, to, by) { const [tx, ty] = xy(to); for (let i = 0; i < 64; i++) {
    const q = p.b[i];
    if (!q || q.c !== by)
        continue;
    const [x, y] = xy(i), dx = tx - x, dy = ty - y, ad = Math.abs(dx), ay = Math.abs(dy);
    if (q.t === 'P' && ay === 1 && ad === 1 && dy === (by === 'w' ? -1 : 1))
        return true;
    if (q.t === 'N' && ((ad === 1 && ay === 2) || (ad === 2 && ay === 1)))
        return true;
    if (q.t === 'K' && ad <= 1 && ay <= 1)
        return true;
    let straight = q.t === 'R' || q.t === 'Q', diag = q.t === 'B' || q.t === 'Q';
    if ((straight && (dx === 0 || dy === 0)) || (diag && ad === ay)) {
        const sx = Math.sign(dx), sy = Math.sign(dy);
        let a = x + sx, z = y + sy, clear = true;
        while (a !== tx || z !== ty) {
            if (p.b[z * 8 + a]) {
                clear = false;
                break;
            }
            a += sx;
            z += sy;
        }
        if (clear)
            return true;
    }
} return false; }
function pseudo(p) { const out = []; const c = p.turn; const add = (f, t, extra = {}) => { const q = p.b[t]; if (!q || q.c !== c)
    out.push({ f, t, cap: q, ...extra }); }; for (let f = 0; f < 64; f++) {
    const q = p.b[f];
    if (!q || q.c !== c)
        continue;
    const [x, y] = xy(f);
    if (q.t === 'P') {
        const d = c === 'w' ? -1 : 1, sy = c === 'w' ? 6 : 1, py = y + d;
        if (py >= 0 && py < 8 && !p.b[py * 8 + x]) {
            let t = py * 8 + x;
            if (py === 0 || py === 7)
                ['Q', 'R', 'B', 'N'].forEach(promo => add(f, t, { promo }));
            else
                add(f, t);
            if (y === sy && !p.b[(y + 2 * d) * 8 + x])
                add(f, (y + 2 * d) * 8 + x);
        }
        for (const dx of [-1, 1]) {
            const tx = x + dx, t = py * 8 + tx;
            if (tx >= 0 && py >= 0 && py < 8) {
                if (p.b[t] && p.b[t].c !== c) {
                    if (py === 0 || py === 7)
                        ['Q', 'R', 'B', 'N'].forEach(promo => add(f, t, { promo }));
                    else
                        add(f, t);
                }
                if (t === p.ep)
                    add(f, t, { ep: true, cap: p.b[y * 8 + tx] });
            }
        }
    }
    else if (q.t === 'N' || q.t === 'K') {
        const ds = q.t === 'N' ? [[1, 2], [2, 1], [-1, 2], [-2, 1], [1, -2], [2, -1], [-1, -2], [-2, -1]] : [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]];
        for (const [dX, dY] of ds) {
            const tx = x + dX, ty = y + dY;
            if (tx >= 0 && tx < 8 && ty >= 0 && ty < 8)
                add(f, ty * 8 + tx);
        }
        if (q.t === 'K') {
            const home = c === 'w' ? 60 : 4;
            const opp = enemy(c);
            if (f === home && !attacked(p, home, opp)) {
                if (p.castle.includes(c === 'w' ? 'K' : 'k') && !p.b[home + 1] && !p.b[home + 2] && !attacked(p, home + 1, opp) && !attacked(p, home + 2, opp))
                    add(f, home + 2, { castle: 'K' });
                if (p.castle.includes(c === 'w' ? 'Q' : 'q') && !p.b[home - 1] && !p.b[home - 2] && !p.b[home - 3] && !attacked(p, home - 1, opp) && !attacked(p, home - 2, opp))
                    add(f, home - 2, { castle: 'Q' });
            }
        }
    }
    else {
        const ds = q.t === 'B' ? [[1, 1], [1, -1], [-1, 1], [-1, -1]] : q.t === '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 [dX, dY] of ds) {
            let tx = x + dX, ty = y + dY;
            while (tx >= 0 && tx < 8 && ty >= 0 && ty < 8) {
                const t = ty * 8 + tx;
                if (p.b[t]) {
                    if (p.b[t].c !== c)
                        add(f, t);
                    break;
                }
                add(f, t);
                tx += dX;
                ty += dY;
            }
        }
    }
} return out; }
function apply(p, m) { const n = clone(p), q = n.b[m.f]; n.b[m.f] = null; n.b[m.t] = { ...q, t: m.promo || q.t }; if (m.ep) {
    const [x, y] = xy(m.t);
    n.b[(y + (q.c === 'w' ? 1 : -1)) * 8 + x] = null;
} if (m.castle) {
    const r = m.castle === 'K' ? m.t + 1 : m.t - 2, rt = m.castle === 'K' ? m.t - 1 : m.t + 1;
    n.b[rt] = n.b[r];
    n.b[r] = null;
} const strip = (s) => n.castle = n.castle.replace(s, ''); if (q.t === 'K')
    strip(q.c === 'w' ? 'KQ' : 'kq'); if (m.f === 63 || m.t === 63)
    strip('K'); if (m.f === 56 || m.t === 56)
    strip('Q'); if (m.f === 7 || m.t === 7)
    strip('k'); if (m.f === 0 || m.t === 0)
    strip('q'); n.ep = q.t === 'P' && Math.abs(m.t - m.f) === 16 ? (m.f + m.t) / 2 : null; n.half = (q.t === 'P' || m.cap) ? 0 : p.half + 1; n.full = p.full + (p.turn === 'b' ? 1 : 0); n.turn = enemy(p.turn); return n; }
function legal(p) { return pseudo(p).filter(m => { const n = apply(p, m); const k = n.b.findIndex(z => z?.c === p.turn && z.t === 'K'); return k >= 0 && !attacked(n, k, enemy(p.turn)); }); }
function check(p, c) { const k = p.b.findIndex(z => z?.c === c && z.t === 'K'); return attacked(p, k, enemy(c)); }
function insufficient(p) { const a = p.b.filter(Boolean); const non = a.filter(x => x.t !== 'K'); return non.length === 0 || (non.length === 1 && ['B', 'N'].includes(non[0].t)); }
let pos = initial(), history = [], notations = [], flipped = false, selected = null, legalSel = [], dragFrom = null, muted = false, busy = false;
function san(p, m) { const q = p.b[m.f]; let s = m.castle ? (m.castle === 'K' ? 'O-O' : 'O-O-O') : (q.t === 'P' ? (m.cap ? sq(m.f)[0] + 'x' : '') : q.t) + (m.cap ? 'x' : '') + sq(m.t) + (m.promo ? '=' + m.promo : '') + (m.ep ? ' e.p.' : ''); const n = apply(p, m), lm = legal(n); if (check(n, n.turn))
    s += lm.length ? ' +' : '#'; return s; }
function result() { const lm = legal(pos); if (!lm.length)
    return check(pos, pos.turn) ? (pos.turn === 'w' ? 'Black wins — checkmate' : 'White wins — checkmate') : 'Draw — stalemate'; if (insufficient(pos))
    return 'Draw — insufficient material'; if (pos.half >= 100)
    return 'Draw — fifty-move rule'; return ''; }
function persist() { localStorage.setItem('ivoryChess', JSON.stringify({ pos, history, notations, flipped })); }
function restore() { try {
    const x = JSON.parse(localStorage.getItem('ivoryChess') || 'null');
    if (x?.pos) {
        pos = x.pos;
        history = x.history || [];
        notations = x.notations || [];
        flipped = !!x.flipped;
    }
}
catch { } }
function commit(m) { history.push(clone(pos)); notations.push(san(pos, m)); pos = apply(pos, m); selected = null; legalSel = []; persist(); render(); tone(m.cap ? 160 : 240, .06); if (check(pos, pos.turn))
    tone(500, .12); if (!result())
    setTimeout(ai, 70); }
function evalPos(p) { const v = { P: 100, N: 320, B: 330, R: 500, Q: 900, K: 0 }; let n = 0; p.b.forEach((q, i) => { if (q) {
    const [, y] = xy(i), center = 3.5 - Math.abs(3.5 - (i % 8)) + 3.5 - Math.abs(3.5 - y);
    n += (q.c === 'w' ? 1 : -1) * (v[q.t] + (q.t === 'P' ? (q.c === 'w' ? 6 - y : y) * 7 : q.t === 'N' || q.t === 'B' ? center * 5 : 0));
} }); return n; }
function search(p, d, a, b) { if (!d)
    return evalPos(p); const ms = legal(p).sort((x, y) => (y.cap ? 10 : 0) - (x.cap ? 10 : 0)); if (!ms.length)
    return check(p, p.turn) ? (p.turn === 'w' ? -99999 : 99999) : 0; if (p.turn === 'w') {
    let v = -1e9;
    for (const m of ms) {
        v = Math.max(v, search(apply(p, m), d - 1, a, b));
        a = Math.max(a, v);
        if (a >= b)
            break;
    }
    return v;
}
else {
    let v = 1e9;
    for (const m of ms) {
        v = Math.min(v, search(apply(p, m), d - 1, a, b));
        b = Math.min(b, v);
        if (a >= b)
            break;
    }
    return v;
} }
function ai() { const side = document.querySelector('#side').value; if (busy || result() || side === 'both' || side === pos.turn)
    return; busy = true; setTimeout(() => { const ms = legal(pos), d = DEPTHS[document.querySelector('#difficulty').value] || 2; let best = ms[0], bv = pos.turn === 'w' ? -1e9 : 1e9; for (const m of ms) {
    const v = search(apply(pos, m), d - 1, -1e9, 1e9);
    if ((pos.turn === 'w' && v > bv) || (pos.turn === 'b' && v < bv)) {
        bv = v;
        best = m;
    }
} busy = false; if (best)
    commit(best); }, 20); }
function tone(freq, d) { if (muted)
    return; try {
    const c = new AudioContext(), o = c.createOscillator(), g = c.createGain();
    o.frequency.value = freq;
    g.gain.setValueAtTime(.04, c.currentTime);
    g.gain.exponentialRampToValueAtTime(.001, c.currentTime + d);
    o.connect(g).connect(c.destination);
    o.start();
    o.stop(c.currentTime + d);
}
catch { } }
function render() { const b = document.querySelector('#board'); b.innerHTML = ''; const order = flipped ? [...Array(64).keys()].reverse() : [...Array(64).keys()]; const lm = legal(pos); let lastFrom = -1, lastTo = -1; if (history.length) {
    const h = history[history.length - 1];
    const dif = [];
    for (let z = 0; z < 64; z++) {
        const a = h.b[z], c = pos.b[z];
        if ((a?.c !== c?.c) || (a?.t !== c?.t))
            dif.push(z);
    }
    lastFrom = dif.find(z => h.b[z] !== null && pos.b[z] === null) ?? dif[0] ?? -1;
    lastTo = dif.find(z => pos.b[z] !== null && z !== lastFrom) ?? dif[dif.length - 1] ?? -1;
} for (const i of order) {
    const q = pos.b[i], el = document.createElement('div');
    el.className = 'sq ' + (((Math.floor(i / 8) + i % 8) % 2) ? 'dark' : 'light');
    el.dataset.i = String(i);
    if (selected === i)
        el.classList.add('selected');
    const targets = legalSel.filter(m => m.t === i);
    if (targets.length)
        el.classList.add(targets[0].cap ? 'capture' : 'target');
    if (i === lastFrom || i === lastTo) {
        el.classList.add('last');
    }
    if (q) {
        const sp = document.createElement('span');
        sp.className = 'piece ' + (q.c === 'w' ? 'white' : 'black');
        sp.textContent = glyph(q);
        el.append(sp);
    }
    if (check(pos, pos.turn) && q?.t === 'K' && q.c === pos.turn)
        el.classList.add('check');
    const [x, y] = xy(i);
    if (x === 0) {
        let r = document.createElement('i');
        r.className = 'coord rank';
        r.textContent = String(8 - y);
        el.append(r);
    }
    if (y === 7) {
        let f = document.createElement('i');
        f.className = 'coord file';
        f.textContent = 'abcdefgh'[x];
        el.append(f);
    }
    b.append(el);
} const status = document.querySelector('#status'); status.textContent = result() || `${pos.turn === 'w' ? 'White' : 'Black'} to move${check(pos, pos.turn) ? ' — check' : ''}`; const ban = document.querySelector('#banner'); ban.textContent = result(); ban.classList.toggle('hidden', !result()); const ml = document.querySelector('#moves'); ml.innerHTML = ''; for (let i = 0; i < notations.length; i += 2) {
    const r = document.createElement('div');
    r.className = 'move-row';
    r.innerHTML = `<span class=num>${i / 2 + 1}.</span><span>${notations[i] || ''}</span><span>${notations[i + 1] || ''}</span>`;
    ml.append(r);
} ml.scrollTop = ml.scrollHeight; const caps = { w: [], b: [] }; history.forEach((h, idx) => { const before = h.b, after = idx < history.length - 1 ? history[idx + 1].b : pos.b; before.forEach((p, i) => { if (p && p.t !== 'K' && !after[i] && !(p.t === 'P' && Math.abs(i - (after.findIndex(x => x?.t === 'P' && x.c !== p.c))) === 8))
    caps[p.c].push(glyph(p)); }); }); (document.querySelector('#cap-white')).textContent = caps.b.join(''); (document.querySelector('#cap-black')).textContent = caps.w.join(''); }
function choose(from, to) { const ms = legalSel.filter(m => m.t === to); if (!ms.length)
    return; if (ms[0].promo) {
    const picker = document.querySelector('#promotion');
    const cell = document.querySelector('.sq[data-i="' + to + '"]');
    const r = cell.getBoundingClientRect();
    picker.style.left = r.left + 'px';
    picker.style.top = r.top + 'px';
    picker.classList.remove('hidden');
    picker.querySelectorAll('button').forEach(btn => { btn.onclick = () => { const m = ms.find(x => x.promo === btn.dataset.p); picker.classList.add('hidden'); if (m)
        commit(m); }; });
}
else
    commit(ms[0]); }
document.querySelector('#board').addEventListener('click', e => { const el = e.target.closest('.sq'); if (!el || busy)
    return; const i = Number(el.dataset.i); if (selected !== null && legalSel.some(m => m.t === i)) {
    choose(selected, i);
    return;
} if (pos.b[i]?.c === pos.turn) {
    const side = document.querySelector('#side').value;
    if (side === 'both' || side === pos.turn) {
        selected = i;
        legalSel = legal(pos).filter(m => m.f === i);
        render();
    }
}
else {
    selected = null;
    legalSel = [];
    render();
} });
document.querySelector('#board').addEventListener('pointerdown', e => { const el = e.target.closest('.sq'); if (el && pos.b[Number(el.dataset.i)]?.c === pos.turn)
    dragFrom = Number(el.dataset.i); });
document.addEventListener('pointerup', e => { if (dragFrom === null)
    return; const el = e.target.closest('.sq'); const f = dragFrom; dragFrom = null; if (el) {
    selected = f;
    legalSel = legal(pos).filter(m => m.f === f);
    choose(f, Number(el.dataset.i));
} });
document.querySelector('#flip').addEventListener('click', () => { flipped = !flipped; persist(); render(); });
document.querySelector('#newgame').addEventListener('click', () => { pos = initial(); history = []; notations = []; selected = null; persist(); render(); ai(); });
document.querySelector('#undo').addEventListener('click', () => { const side = document.querySelector('#side').value; let count = side === 'both' ? 1 : 2; while (count-- && history.length) {
    pos = history.pop();
    notations.pop();
} persist(); render(); });
document.querySelector('#mute').addEventListener('click', e => { muted = !muted; e.currentTarget.textContent = muted ? '♩ Sound off' : '♬ Sound on'; });
document.querySelector('#side').addEventListener('change', () => { render(); ai(); });
restore();
render();
ai();
window.game = { fen: () => pos.b.map((q, i) => q ? (q.c === 'w' ? q.t : q.t.toLowerCase()) : '').join('') + ' ' + pos.turn + ' ' + pos.castle + ' ' + (pos.ep === null ? '-' : sq(pos.ep)), legalMoves: () => legal(pos).map(m => sq(m.f) + sq(m.t) + (m.promo || '').toLowerCase()), move: (s) => { if (result())
        return false; const ms = legal(pos), m = ms.find(x => sq(x.f) + sq(x.t) + (x.promo || '').toLowerCase() === s.toLowerCase()); if (!m)
        return false; commit(m); return true; }, reset: () => { pos = initial(); history = []; notations = []; render(); }, state: () => JSON.parse(JSON.stringify(pos)) };
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-terra","provider":"openrouter","persona":"typescript","sessionId":"1e62ab12-a47b-4796-a3f3-de44f825a9e4","tokensIn":188565,"tokensOut":8681,"tokensTotal":197246,"turns":15,"toolCalls":22,"failedToolCalls":4,"timestamp":"2026-07-31T17:42:06.499Z"} -->