← Chess results

Chess

thinkingcap qwen3.6 27b · 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 56,555 bytes · SHA-256 524cdbd4684d
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chess — Play Against AI</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
  background: #1a1a2e;
  color: #e0e0e0;
  font-family: 'Segoe UI', system-ui, sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  overflow: auto;
}
#game-container {
  display: flex;
  gap: 24px;
  padding: 20px;
  align-items: flex-start;
}
#board-area { position: relative; }
#side-panel {
  width: 260px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#status-bar {
  background: #16213e;
  padding: 12px 16px;
  border-radius: 8px;
  font-size: 15px;
  font-weight: 600;
  text-align: center;
}
#turn-indicator { color: #f0c040; }
.ctrl-btn, button, select {
  background: #0f3460;
  color: #e0e0e0;
  border: none;
  padding: 8px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
}
.ctrl-btn:hover, button:hover { background: #1a5276; }
select { appearance: auto; }
#controls { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
#move-list-container {
  background: #16213e;
  border-radius: 8px;
  padding: 10px;
  flex: 1;
  min-height: 120px;
  max-height: 340px;
  overflow-y: auto;
}
#move-list-container h3 { font-size: 13px; margin-bottom: 6px; color: #aaa; }
#move-list { font-family: 'Courier New', monospace; font-size: 13px; line-height: 1.7; }
.move-pair { display: flex; gap: 8px; }
.move-num { color: #666; min-width: 24px; text-align: right; }
.move-w, .move-b { min-width: 50px; }
#captured-white, #captured-black {
  font-size: 20px;
  min-height: 28px;
  padding: 4px 8px;
  background: #16213e;
  border-radius: 6px;
  letter-spacing: 2px;
}
#game-over-banner {
  background: #0f3460;
  padding: 14px;
  border-radius: 8px;
  text-align: center;
  font-weight: bold;
  font-size: 15px;
}
.hidden { display: none !important; }
#promotion-overlay {
  position: absolute;
  background: rgba(0,0,0,0.7);
  border-radius: 8px;
  padding: 6px;
  display: flex;
  gap: 4px;
  z-index: 10;
}
.promo-piece {
  width: 52px; height: 52px;
  font-size: 36px;
  text-align: center;
  line-height: 52px;
  cursor: pointer;
  border-radius: 4px;
  background: #16213e;
}
.promo-piece:hover { background: #0f3460; }
</style>
</head>
<body>
<div id="game-container">
  <div id="board-area">
    <canvas id="board" width="560" height="560"></canvas>
    <div id="promotion-overlay" class="hidden"></div>
  </div>
  <div id="side-panel">
    <div id="status-bar"><span id="turn-indicator">White to move</span></div>
    <div id="captured-white"></div>
    <div id="board-flip-btn" class="ctrl-btn">⟳ Flip Board</div>
    <div id="move-list-container">
      <h3>Moves</h3>
      <div id="move-list"></div>
    </div>
    <div id="captured-black"></div>
    <div id="controls">
      <select id="difficulty"><option value="1">Easy (Depth 1)</option><option value="2" selected="">Medium (Depth 2)</option><option value="3">Hard (Depth 3)</option></select>
      <button id="undo-btn">↩ Undo</button>
      <button id="new-game-btn">New Game</button>
      <button id="mute-btn">🔊 Mute</button>
    </div>
    <div id="game-over-banner" class="hidden"></div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./sound":"src/sound.ts","./ui":"src/ui.ts","./board":"src/board.ts"},"src/board.ts":{"./types":"src/types.ts"},"src/evaluation.ts":{"./types":"src/types.ts"},"src/ai.ts":{"./types":"src/types.ts","./board":"src/board.ts","./evaluation":"src/evaluation.ts"},"src/ui.ts":{"./types":"src/types.ts","./board":"src/board.ts","./ai":"src/ai.ts","./sound":"src/sound.ts"}};
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";
// ── Entry Point ──────────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
const sound_1 = require("./sound");
const ui_1 = require("./ui");
const board_1 = require("./board");
(0, sound_1.initSound)();
// Restore saved game or start fresh
const saved = (0, ui_1.loadFromStorage)();
if (saved) {
    (0, ui_1.setGameState)(saved);
}
else {
    (0, ui_1.setGameState)((0, board_1.newGameState)());
}
(0, ui_1.setupEvents)();
(0, ui_1.exposeDebug)();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ── Types & Constants for Chess Engine ───────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONFIG = exports.PIECE_NAMES = exports.PIECE_UNICODE = exports.PIECE_VALUES = exports.PST = void 0;
// Piece-square tables (from white's perspective, row 0 = rank 8)
exports.PST = {
    P: [
        0, 0, 0, 0, 0, 0, 0, 0,
        50, 50, 50, 50, 50, 50, 50, 50,
        10, 10, 20, 30, 30, 20, 10, 10,
        5, 5, 10, 25, 25, 10, 5, 5,
        0, 0, 0, 20, 20, 0, 0, 0,
        5, -5, -10, 0, 0, -10, -5, 5,
        5, 10, 10, -20, -20, 10, 10, 5,
        0, 0, 0, 0, 0, 0, 0, 0
    ],
    N: [
        -50, -40, -30, -30, -30, -30, -40, -50,
        -40, -20, 0, 0, 0, 0, -20, -40,
        -30, 0, 10, 15, 15, 10, 0, -30,
        -30, 5, 15, 20, 20, 15, 5, -30,
        -30, 0, 15, 20, 20, 15, 0, -30,
        -30, 5, 10, 15, 15, 10, 5, -30,
        -40, -20, 0, 5, 5, 0, -20, -40,
        -50, -40, -30, -30, -30, -30, -40, -50
    ],
    B: [
        -20, -10, -10, -10, -10, -10, -10, -20,
        -10, 0, 0, 0, 0, 0, 0, -10,
        -10, 0, 10, 10, 10, 10, 0, -10,
        -10, 5, 5, 10, 10, 5, 5, -10,
        -10, 0, 10, 10, 10, 10, 0, -10,
        -10, 10, 10, 10, 10, 10, 10, -10,
        -10, 5, 0, 0, 0, 0, 5, -10,
        -20, -10, -10, -10, -10, -10, -10, -20
    ],
    R: [
        0, 0, 0, 0, 0, 0, 0, 0,
        5, 10, 10, 10, 10, 10, 10, 5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        -5, 0, 0, 0, 0, 0, 0, -5,
        0, 0, 0, 5, 5, 0, 0, 0
    ],
    Q: [
        -20, -10, -10, -5, -5, -10, -10, -20,
        -10, 0, 0, 0, 0, 0, 0, -10,
        -10, 0, 5, 5, 5, 5, 0, -10,
        -5, 0, 5, 5, 5, 5, 0, -5,
        0, 0, 5, 5, 5, 5, 0, -5,
        -10, 5, 5, 5, 5, 5, 0, -10,
        -10, 0, 5, 0, 0, 0, 0, -10,
        -20, -10, -10, -5, -5, -10, -10, -20
    ],
    K: [
        -30, -40, -40, -50, -50, -40, -40, -30,
        -30, -40, -40, -50, -50, -40, -40, -30,
        -30, -40, -40, -50, -50, -40, -40, -30,
        -30, -40, -40, -50, -50, -40, -40, -30,
        -20, -30, -30, -40, -40, -30, -30, -20,
        -10, -20, -20, -20, -20, -20, -20, -10,
        20, 10, 0, 0, 0, 0, 10, 20,
        20, 30, 10, 0, 0, 10, 30, 20
    ]
};
exports.PIECE_VALUES = {
    P: 100, N: 320, B: 330, R: 500, Q: 900, K: 20000
};
// Unicode piece glyphs
exports.PIECE_UNICODE = {
    'wK': '♔', 'wQ': '♕', 'wR': '♖', 'wB': '♗', 'wN': '♘', 'wP': '♙',
    'bK': '♚', 'bQ': '♛', 'bR': '♜', 'bB': '♝', 'bN': '♞', 'bP': '♟'
};
exports.PIECE_NAMES = {
    K: 'K', Q: 'Q', R: 'R', B: 'B', N: 'N', P: ''
};
// Tunable constants
exports.CONFIG = {
    squareSize: 70,
    boardColors: { light: '#f0d9b5', dark: '#b58863' },
    highlightColor: 'rgba(255, 255, 0, 0.4)',
    lastMoveColor: 'rgba(155, 199, 0, 0.41)',
    checkGlowColor: 'rgba(255, 0, 0, 0.6)',
    legalDotRadius: 12,
    captureRingWidth: 4,
};
};

// ── module: src/board.ts ──
__mods["src/board.ts"] = function (exports, require, module) {
"use strict";
// ── Chess Engine: Board representation & move generation ─────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.initialBoard = initialBoard;
exports.initialCastling = initialCastling;
exports.newGameState = newGameState;
exports.undoStateFrom = undoStateFrom;
exports.applyMove = applyMove;
exports.isInCheck = isInCheck;
exports.generateLegalMoves = generateLegalMoves;
exports.executeMove = executeMove;
exports.checkGameEnd = checkGameEnd;
exports.toAlgebraic = toAlgebraic;
exports.toFen = toFen;
exports.fromFen = fromFen;
const DIRS_N = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]];
const DIRS_B = [[-1, -1], [-1, 1], [1, -1], [1, 1]];
const DIRS_R = [[-1, 0], [1, 0], [0, -1], [0, 1]];
const DIRS_Q = [[-1, -1], [-1, 1], [1, -1], [1, 1], [-1, 0], [1, 0], [0, -1], [0, 1]];
function sq(r, c) { return r * 8 + c; }
function row(s) { return Math.floor(s / 8); }
function col(s) { return s % 8; }
function inBounds(r, c) { return r >= 0 && r < 8 && c >= 0 && c < 8; }
function initialBoard() {
    const b = new Array(64).fill(null);
    const backRow = ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'];
    for (let c = 0; c < 8; c++) {
        b[sq(0, c)] = { color: 'b', type: backRow[c] };
        b[sq(1, c)] = { color: 'b', type: 'P' };
        b[sq(6, c)] = { color: 'w', type: 'P' };
        b[sq(7, c)] = { color: 'w', type: backRow[c] };
    }
    return b;
}
function initialCastling() {
    return { wK: true, wQ: true, bK: true, bQ: true };
}
function newGameState() {
    return {
        board: initialBoard(),
        turn: 'w',
        castling: initialCastling(),
        enPassant: null,
        halfMoveClock: 0,
        fullMoveNumber: 1,
        moveHistory: [],
        gameOver: false,
    };
}
function cloneBoard(b) { return b.slice(); }
function cloneCastling(c) {
    return { wK: c.wK, wQ: c.wQ, bK: c.bK, bQ: c.bQ };
}
function undoStateFrom(gs) {
    return {
        board: cloneBoard(gs.board),
        turn: gs.turn,
        castling: cloneCastling(gs.castling),
        enPassant: gs.enPassant,
        halfMoveClock: gs.halfMoveClock,
        fullMoveNumber: gs.fullMoveNumber,
    };
}
function applyMove(board, move) {
    const nb = cloneBoard(board);
    const piece = nb[move.from];
    nb[move.to] = move.promotion ? { color: piece.color, type: move.promotion } : piece;
    nb[move.from] = null;
    if (move.enPassant) {
        const capRow = row(move.from);
        const capSq = sq(capRow, col(move.to));
        nb[capSq] = null;
    }
    if (move.castling === 'K') {
        const rFrom = sq(row(move.from), 7);
        const rTo = sq(row(move.from), 5);
        nb[rTo] = nb[rFrom];
        nb[rFrom] = null;
    }
    else if (move.castling === 'Q') {
        const rFrom = sq(row(move.from), 0);
        const rTo = sq(row(move.from), 3);
        nb[rTo] = nb[rFrom];
        nb[rFrom] = null;
    }
    return nb;
}
function findKing(board, color) {
    for (let i = 0; i < 64; i++) {
        const p = board[i];
        if (p && p.color === color && p.type === 'K')
            return i;
    }
    return -1;
}
function isAttacked(board, sqIdx, byColor) {
    const r = row(sqIdx), c = col(sqIdx);
    for (const [dr, dc] of DIRS_N) {
        const nr = r + dr, nc = c + dc;
        if (inBounds(nr, nc)) {
            const p = board[sq(nr, nc)];
            if (p && p.color === byColor && p.type === 'N')
                return true;
        }
    }
    const pawnDir = byColor === 'w' ? 1 : -1;
    for (const dc of [-1, 1]) {
        const pr = r + pawnDir, pc = c + dc;
        if (inBounds(pr, pc)) {
            const p = board[sq(pr, pc)];
            if (p && p.color === byColor && p.type === 'P')
                return true;
        }
    }
    for (const [dr, dc] of DIRS_Q) {
        const nr = r + dr, nc = c + dc;
        if (inBounds(nr, nc)) {
            const p = board[sq(nr, nc)];
            if (p && p.color === byColor && p.type === 'K')
                return true;
        }
    }
    for (const [dr, dc] of DIRS_B) {
        let nr = r + dr, nc = c + dc;
        while (inBounds(nr, nc)) {
            const p = board[sq(nr, nc)];
            if (p) {
                if (p.color === byColor && (p.type === 'B' || p.type === 'Q'))
                    return true;
                break;
            }
            nr += dr;
            nc += dc;
        }
    }
    for (const [dr, dc] of DIRS_R) {
        let nr = r + dr, nc = c + dc;
        while (inBounds(nr, nc)) {
            const p = board[sq(nr, nc)];
            if (p) {
                if (p.color === byColor && (p.type === 'R' || p.type === 'Q'))
                    return true;
                break;
            }
            nr += dr;
            nc += dc;
        }
    }
    return false;
}
function isInCheck(board, color) {
    const kingSq = findKing(board, color);
    if (kingSq === -1)
        return false;
    const enemy = color === 'w' ? 'b' : 'w';
    return isAttacked(board, kingSq, enemy);
}
function pseudoMoves(board, fromSq, castling, enPassant) {
    const piece = board[fromSq];
    const moves = [];
    const r = row(fromSq), c = col(fromSq);
    function addMove(to, extra) {
        moves.push({ from: fromSq, to, piece: piece.type, captured: board[to] ? board[to].type : null, ...extra });
    }
    switch (piece.type) {
        case 'P': {
            const dir = piece.color === 'w' ? -1 : 1;
            const startRow = piece.color === 'w' ? 6 : 1;
            const promoRow = piece.color === 'w' ? 0 : 7;
            const f1r = r + dir, f1c = c;
            if (inBounds(f1r, f1c) && !board[sq(f1r, f1c)]) {
                if (f1r === promoRow) {
                    for (const pt of ['Q', 'R', 'B', 'N'])
                        addMove(sq(f1r, f1c), { promotion: pt });
                }
                else {
                    addMove(sq(f1r, f1c));
                }
                const f2r = r + 2 * dir;
                if (r === startRow && !board[sq(f2r, f1c)]) {
                    addMove(sq(f2r, f1c));
                }
            }
            for (const dc of [-1, 1]) {
                const cr = r + dir, cc = c + dc;
                if (!inBounds(cr, cc))
                    continue;
                const csq = sq(cr, cc);
                if (board[csq] && board[csq].color !== piece.color) {
                    if (cr === promoRow) {
                        for (const pt of ['Q', 'R', 'B', 'N'])
                            addMove(csq, { promotion: pt });
                    }
                    else {
                        addMove(csq);
                    }
                }
            }
            // En passant - check if ep square is on adjacent file and in forward direction
            if (enPassant !== null) {
                const epR = row(enPassant), epC = col(enPassant);
                for (const dc of [-1, 1]) {
                    if (epC === c + dc && (epR - r) * dir > 0) {
                        addMove(enPassant, { enPassant: true, captured: 'P' });
                    }
                }
            }
            break;
        }
        case 'N': {
            for (const [dr, dc] of DIRS_N) {
                const nr = r + dr, nc = c + dc;
                if (inBounds(nr, nc)) {
                    const nsq = sq(nr, nc);
                    if (!board[nsq] || board[nsq].color !== piece.color)
                        addMove(nsq);
                }
            }
            break;
        }
        case 'B': {
            for (const [dr, dc] of DIRS_B) {
                let nr = r + dr, nc = c + dc;
                while (inBounds(nr, nc)) {
                    const nsq = sq(nr, nc);
                    if (!board[nsq]) {
                        addMove(nsq);
                    }
                    else {
                        if (board[nsq].color !== piece.color)
                            addMove(nsq);
                        break;
                    }
                    nr += dr;
                    nc += dc;
                }
            }
            break;
        }
        case 'R': {
            for (const [dr, dc] of DIRS_R) {
                let nr = r + dr, nc = c + dc;
                while (inBounds(nr, nc)) {
                    const nsq = sq(nr, nc);
                    if (!board[nsq]) {
                        addMove(nsq);
                    }
                    else {
                        if (board[nsq].color !== piece.color)
                            addMove(nsq);
                        break;
                    }
                    nr += dr;
                    nc += dc;
                }
            }
            break;
        }
        case 'Q': {
            for (const [dr, dc] of DIRS_Q) {
                let nr = r + dr, nc = c + dc;
                while (inBounds(nr, nc)) {
                    const nsq = sq(nr, nc);
                    if (!board[nsq]) {
                        addMove(nsq);
                    }
                    else {
                        if (board[nsq].color !== piece.color)
                            addMove(nsq);
                        break;
                    }
                    nr += dr;
                    nc += dc;
                }
            }
            break;
        }
        case 'K': {
            for (const [dr, dc] of DIRS_Q) {
                const nr = r + dr, nc = c + dc;
                if (inBounds(nr, nc)) {
                    const nsq = sq(nr, nc);
                    if (!board[nsq] || board[nsq].color !== piece.color)
                        addMove(nsq);
                }
            }
            const enemy = piece.color === 'w' ? 'b' : 'w';
            const kRight = piece.color === 'w' ? castling.wK : castling.bK;
            const qRight = piece.color === 'w' ? castling.wQ : castling.bQ;
            if (kRight && !board[sq(r, 5)] && !board[sq(r, 6)]) {
                if (!isAttacked(board, sq(r, 4), enemy) && !isAttacked(board, sq(r, 5), enemy) && !isAttacked(board, sq(r, 6), enemy)) {
                    addMove(sq(r, 6), { castling: 'K', captured: null });
                }
            }
            if (qRight && !board[sq(r, 1)] && !board[sq(r, 2)] && !board[sq(r, 3)]) {
                if (!isAttacked(board, sq(r, 4), enemy) && !isAttacked(board, sq(r, 3), enemy) && !isAttacked(board, sq(r, 2), enemy)) {
                    addMove(sq(r, 2), { castling: 'Q', captured: null });
                }
            }
            break;
        }
    }
    return moves;
}
function generateLegalMoves(board, turn, castling, enPassant) {
    const legal = [];
    for (let i = 0; i < 64; i++) {
        const p = board[i];
        if (!p || p.color !== turn)
            continue;
        const moves = pseudoMoves(board, i, castling, enPassant);
        for (const m of moves) {
            const nb = applyMove(board, m);
            if (!isInCheck(nb, turn)) {
                legal.push(m);
            }
        }
    }
    return legal;
}
function executeMove(gs, move) {
    const ng = { ...gs };
    ng.board = applyMove(gs.board, move);
    ng.turn = gs.turn === 'w' ? 'b' : 'w';
    const nc = cloneCastling(gs.castling);
    if (move.piece === 'K') {
        if (gs.turn === 'w') {
            nc.wK = false;
            nc.wQ = false;
        }
        else {
            nc.bK = false;
            nc.bQ = false;
        }
    }
    if (move.piece === 'R') {
        const mr = row(move.from), mc = col(move.from);
        if (gs.turn === 'w') {
            if (mr === 7 && mc === 0)
                nc.wQ = false;
            if (mr === 7 && mc === 7)
                nc.wK = false;
        }
        else {
            if (mr === 0 && mc === 0)
                nc.bQ = false;
            if (mr === 0 && mc === 7)
                nc.bK = false;
        }
    }
    const rToR = row(move.to), rToC = col(move.to);
    if (rToR === 0 && rToC === 0)
        nc.bQ = false;
    if (rToR === 0 && rToC === 7)
        nc.bK = false;
    if (rToR === 7 && rToC === 0)
        nc.wQ = false;
    if (rToR === 7 && rToC === 7)
        nc.wK = false;
    ng.castling = nc;
    ng.enPassant = null;
    if (move.piece === 'P' && Math.abs(row(move.to) - row(move.from)) === 2) {
        ng.enPassant = sq((row(move.from) + row(move.to)) / 2, col(move.from));
    }
    if (move.piece === 'P' || move.captured !== null && move.captured !== undefined) {
        ng.halfMoveClock = 0;
    }
    else {
        ng.halfMoveClock = gs.halfMoveClock + 1;
    }
    if (gs.turn === 'b')
        ng.fullMoveNumber = gs.fullMoveNumber + 1;
    ng.moveHistory.push(move);
    return ng;
}
function checkGameEnd(gs) {
    const legalMoves = generateLegalMoves(gs.board, gs.turn, gs.castling, gs.enPassant);
    if (legalMoves.length === 0) {
        if (isInCheck(gs.board, gs.turn)) {
            return { gameOver: true, result: gs.turn === 'w' ? 'Black wins by checkmate' : 'White wins by checkmate' };
        }
        else {
            return { gameOver: true, result: 'Draw by stalemate' };
        }
    }
    if (isInsufficientMaterial(gs.board)) {
        return { gameOver: true, result: 'Draw by insufficient material' };
    }
    if (gs.halfMoveClock >= 100) {
        return { gameOver: true, result: 'Draw by 50-move rule' };
    }
    return { gameOver: false };
}
function isInsufficientMaterial(board) {
    const pieces = [];
    for (const p of board) {
        if (p)
            pieces.push(p);
    }
    if (pieces.length === 2)
        return true;
    if (pieces.length === 3) {
        const nonKing = pieces.find(p => p.type !== 'K');
        if (nonKing && (nonKing.type === 'B' || nonKing.type === 'N'))
            return true;
    }
    return false;
}
function toAlgebraic(gs, move) {
    const fromR = row(move.from), fromC = col(move.from);
    const toR = row(move.to), toC = col(move.to);
    if (move.castling === 'K')
        return 'O-O';
    if (move.castling === 'Q')
        return 'O-O-O';
    let notation = '';
    if (move.piece !== 'P') {
        notation += move.piece;
        const others = [];
        for (let i = 0; i < 64; i++) {
            if (i === move.from)
                continue;
            const p = gs.board[i];
            if (!p || p.color !== gs.turn || p.type !== move.piece)
                continue;
            const pm = pseudoMoves(gs.board, i, gs.castling, gs.enPassant);
            if (pm.some(m => m.to === move.to))
                others.push(i);
        }
        if (others.length > 0) {
            const sameCol = others.some(i => col(i) === fromC);
            const sameRow = others.some(i => row(i) === fromR);
            if (!sameCol)
                notation += String.fromCharCode(97 + fromC);
            else if (!sameRow)
                notation += (8 - fromR);
            else
                notation += String.fromCharCode(97 + fromC) + (8 - fromR);
        }
    }
    if (move.captured !== null && move.captured !== undefined) {
        if (move.piece === 'P')
            notation += String.fromCharCode(97 + fromC);
        notation += 'x';
    }
    notation += String.fromCharCode(97 + toC) + (8 - toR);
    if (move.promotion)
        notation += '=' + move.promotion;
    return notation;
}
function toFen(gs) {
    let fen = '';
    for (let r = 0; r < 8; r++) {
        let empty = 0;
        for (let c = 0; c < 8; c++) {
            const p = gs.board[sq(r, c)];
            if (!p) {
                empty++;
            }
            else {
                if (empty > 0) {
                    fen += empty;
                    empty = 0;
                }
                fen += (p.color === 'w' ? p.type : p.type.toLowerCase());
            }
        }
        if (empty > 0)
            fen += empty;
        if (r < 7)
            fen += '/';
    }
    fen += ' ' + gs.turn;
    let cast = '';
    if (gs.castling.wK)
        cast += 'K';
    if (gs.castling.wQ)
        cast += 'Q';
    if (gs.castling.bK)
        cast += 'k';
    if (gs.castling.bQ)
        cast += 'q';
    fen += ' ' + (cast || '-');
    if (gs.enPassant !== null) {
        fen += ' ' + String.fromCharCode(97 + col(gs.enPassant)) + (8 - row(gs.enPassant));
    }
    else {
        fen += ' -';
    }
    fen += ' ' + gs.halfMoveClock + ' ' + gs.fullMoveNumber;
    return fen;
}
function fromFen(fen) {
    const parts = fen.split(' ');
    const boardStr = parts[0];
    const turn = parts[1];
    const castlingStr = parts[2];
    const epStr = parts[3];
    const b = new Array(64).fill(null);
    let sqIdx = 0;
    for (const ch of boardStr) {
        if (ch === '/')
            continue;
        if (ch >= '1' && ch <= '8') {
            sqIdx += parseInt(ch);
        }
        else {
            const color = ch === ch.toUpperCase() ? 'w' : 'b';
            b[sqIdx] = { color, type: ch.toUpperCase() };
            sqIdx++;
        }
    }
    const cr = { wK: false, wQ: false, bK: false, bQ: false };
    if (castlingStr.includes('K'))
        cr.wK = true;
    if (castlingStr.includes('Q'))
        cr.wQ = true;
    if (castlingStr.includes('k'))
        cr.bK = true;
    if (castlingStr.includes('q'))
        cr.bQ = true;
    let ep = null;
    if (epStr !== '-') {
        const c = epStr.charCodeAt(0) - 97;
        const r = 8 - parseInt(epStr[1]);
        ep = sq(r, c);
    }
    return {
        board: b, turn, castling: cr, enPassant: ep,
        halfMoveClock: parseInt(parts[4]) || 0,
        fullMoveNumber: parseInt(parts[5]) || 1,
        moveHistory: [], gameOver: false,
    };
}
};

// ── module: src/evaluation.ts ──
__mods["src/evaluation.ts"] = function (exports, require, module) {
"use strict";
// ── Position Evaluation ──────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluate = evaluate;
exports.orderMoves = orderMoves;
const types_1 = require("./types");
function row(s) { return Math.floor(s / 8); }
function evaluate(board, turn) {
    let score = 0;
    for (let i = 0; i < 64; i++) {
        const p = board[i];
        if (!p)
            continue;
        const val = types_1.PIECE_VALUES[p.type];
        const pstIdx = p.color === 'w' ? i : 63 - i; // mirror for black
        const pstVal = types_1.PST[p.type][pstIdx];
        if (p.color === turn) {
            score += val + pstVal;
        }
        else {
            score -= val + pstVal;
        }
    }
    return score;
}
// Move ordering: captures first, then by SEE-like heuristic
function orderMoves(moves) {
    const ordered = [...moves];
    ordered.sort((a, b) => {
        // Captures first
        const aCap = a.captured ? types_1.PIECE_VALUES[a.captured] : 0;
        const bCap = b.captured ? types_1.PIECE_VALUES[b.captured] : 0;
        if (aCap !== bCap)
            return bCap - aCap;
        // Promotions next
        if (a.promotion && !b.promotion)
            return -1;
        if (!a.promotion && b.promotion)
            return 1;
        return 0;
    });
    return ordered;
}
};

// ── module: src/ai.ts ──
__mods["src/ai.ts"] = function (exports, require, module) {
"use strict";
// ── AI: Minimax with Alpha-Beta Pruning ──────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.findBestMove = findBestMove;
const board_1 = require("./board");
const evaluation_1 = require("./evaluation");
const INF = 100000;
function cloneCastling(c) {
    return { wK: c.wK, wQ: c.wQ, bK: c.bK, bQ: c.bQ };
}
function searchStep(state, depth, alpha, beta, maximizing) {
    if (depth === 0)
        return quiesce(state, alpha, beta, maximizing, 4);
    const moves = (0, board_1.generateLegalMoves)(state.board, state.turn, state.castling, state.enPassant);
    if (moves.length === 0) {
        if ((0, board_1.isInCheck)(state.board, state.turn)) {
            return maximizing ? -INF + (100 - depth) : INF - (100 - depth); // mate closer is better/worse
        }
        return 0; // stalemate
    }
    const ordered = (0, evaluation_1.orderMoves)(moves);
    if (maximizing) {
        let best = -INF;
        for (const move of ordered) {
            const nb = (0, board_1.applyMove)(state.board, move);
            const nc = cloneCastling(state.castling);
            // Update castling rights
            if (move.piece === 'K') {
                if (state.turn === 'w') {
                    nc.wK = false;
                    nc.wQ = false;
                }
                else {
                    nc.bK = false;
                    nc.bQ = false;
                }
            }
            const val = searchStep({ board: nb, turn: state.turn === 'w' ? 'b' : 'w', castling: nc, enPassant: null }, depth - 1, alpha, beta, false);
            best = Math.max(best, val);
            alpha = Math.max(alpha, val);
            if (beta <= alpha)
                break;
        }
        return best;
    }
    else {
        let best = INF;
        for (const move of ordered) {
            const nb = (0, board_1.applyMove)(state.board, move);
            const nc = cloneCastling(state.castling);
            if (move.piece === 'K') {
                if (state.turn === 'w') {
                    nc.wK = false;
                    nc.wQ = false;
                }
                else {
                    nc.bK = false;
                    nc.bQ = false;
                }
            }
            const val = searchStep({ board: nb, turn: state.turn === 'w' ? 'b' : 'w', castling: nc, enPassant: null }, depth - 1, alpha, beta, true);
            best = Math.min(best, val);
            beta = Math.min(beta, val);
            if (beta <= alpha)
                break;
        }
        return best;
    }
}
// Quiescence search to avoid horizon effect
function quiesce(state, alpha, beta, maximizing, depth) {
    const standPat = (0, evaluation_1.evaluate)(state.board, state.turn);
    if (depth === 0)
        return standPat;
    if (maximizing) {
        if (standPat >= beta)
            return beta;
        if (standPat > alpha)
            alpha = standPat;
        // Only consider captures
        const moves = (0, board_1.generateLegalMoves)(state.board, state.turn, state.castling, state.enPassant);
        const captures = moves.filter(m => m.captured !== null && m.captured !== undefined);
        const ordered = (0, evaluation_1.orderMoves)(captures);
        for (const move of ordered) {
            const nb = (0, board_1.applyMove)(state.board, move);
            const nc = cloneCastling(state.castling);
            if (move.piece === 'K') {
                if (state.turn === 'w') {
                    nc.wK = false;
                    nc.wQ = false;
                }
                else {
                    nc.bK = false;
                    nc.bQ = false;
                }
            }
            const val = quiesce({ board: nb, turn: state.turn === 'w' ? 'b' : 'w', castling: nc, enPassant: null }, alpha, beta, false, depth - 1);
            if (val >= beta)
                return beta;
            if (val > alpha)
                alpha = val;
        }
        return alpha;
    }
    else {
        if (standPat <= alpha)
            return alpha;
        if (standPat < beta)
            beta = standPat;
        const moves = (0, board_1.generateLegalMoves)(state.board, state.turn, state.castling, state.enPassant);
        const captures = moves.filter(m => m.captured !== null && m.captured !== undefined);
        const ordered = (0, evaluation_1.orderMoves)(captures);
        for (const move of ordered) {
            const nb = (0, board_1.applyMove)(state.board, move);
            const nc = cloneCastling(state.castling);
            if (move.piece === 'K') {
                if (state.turn === 'w') {
                    nc.wK = false;
                    nc.wQ = false;
                }
                else {
                    nc.bK = false;
                    nc.bQ = false;
                }
            }
            const val = quiesce({ board: nb, turn: state.turn === 'w' ? 'b' : 'w', castling: nc, enPassant: null }, alpha, beta, true, depth - 1);
            if (val <= alpha)
                return alpha;
            if (val < beta)
                beta = val;
        }
        return beta;
    }
}
function findBestMove(board, turn, castling, enPassant, depth) {
    const moves = (0, board_1.generateLegalMoves)(board, turn, castling, enPassant);
    if (moves.length === 0)
        return null;
    const ordered = (0, evaluation_1.orderMoves)(moves);
    let bestMove = null;
    let bestScore = -INF;
    for (const move of ordered) {
        const nb = (0, board_1.applyMove)(board, move);
        const nc = cloneCastling(castling);
        if (move.piece === 'K') {
            if (turn === 'w') {
                nc.wK = false;
                nc.wQ = false;
            }
            else {
                nc.bK = false;
                nc.bQ = false;
            }
        }
        const val = searchStep({ board: nb, turn: turn === 'w' ? 'b' : 'w', castling: nc, enPassant: null }, depth - 1, -INF, INF, false);
        if (val > bestScore) {
            bestScore = val;
            bestMove = move;
        }
    }
    return bestMove;
}
};

// ── module: src/sound.ts ──
__mods["src/sound.ts"] = function (exports, require, module) {
"use strict";
// ── Sound Effects via Web Audio API ───────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.initSound = initSound;
exports.playMove = playMove;
exports.playCapture = playCapture;
exports.playCheck = playCheck;
exports.playGameOver = playGameOver;
exports.setMute = setMute;
exports.isMuted = isMuted;
let audioCtx = null;
let muted = false;
function initSound() {
    try {
        audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    }
    catch { /* no audio */ }
}
function playTone(freq, duration, vol, type = 'sine') {
    if (!audioCtx || muted)
        return;
    try {
        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();
        osc.type = type;
        osc.frequency.value = freq;
        gain.gain.setValueAtTime(vol, audioCtx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
        osc.connect(gain);
        gain.connect(audioCtx.destination);
        osc.start();
        osc.stop(audioCtx.currentTime + duration);
    }
    catch { /* ignore */ }
}
function playMove() {
    playTone(600, 0.08, 0.15);
}
function playCapture() {
    playTone(300, 0.12, 0.2);
    setTimeout(() => playTone(200, 0.1, 0.15), 40);
}
function playCheck() {
    playTone(800, 0.15, 0.2, 'square');
    setTimeout(() => playTone(600, 0.15, 0.15, 'square'), 80);
}
function playGameOver() {
    playTone(440, 0.3, 0.2);
    setTimeout(() => playTone(550, 0.3, 0.2), 150);
    setTimeout(() => playTone(660, 0.4, 0.2), 300);
}
function setMute(m) { muted = m; }
function isMuted() { return muted; }
};

// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
// ── UI: Rendering & Interaction ──────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.render = render;
exports.updateMoveList = updateMoveList;
exports.setAIDepth = setAIDepth;
exports.hideGameOver = hideGameOver;
exports.loadFromStorage = loadFromStorage;
exports.setGameState = setGameState;
exports.getGameState = getGameState;
exports.setLastMove = setLastMove;
exports.setPlayerColor = setPlayerColor;
exports.setAnalysisMode = setAnalysisMode;
exports.setupEvents = setupEvents;
exports.exposeDebug = exposeDebug;
const types_1 = require("./types");
const board_1 = require("./board");
const ai_1 = require("./ai");
const sound_1 = require("./sound");
function sq(r, c) { return r * 8 + c; }
function row(s) { return Math.floor(s / 8); }
function col(s) { return s % 8; }
const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');
const SS = types_1.CONFIG.squareSize;
// ── State ────────────────────────────────────────────────────────────
let gs = null;
let selectedSquare = null;
let legalMovesForSelected = [];
let lastMove = null;
let flipped = false;
let pendingPromotion = null;
let playerColor = 'w';
let analysisMode = false;
let aiDepth = 2;
// ── Rendering ────────────────────────────────────────────────────────
function render() {
    if (!gs)
        return;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (let r = 0; r < 8; r++) {
        for (let c = 0; c < 8; c++) {
            const x = c * SS, y = r * SS;
            ctx.fillStyle = (r + c) % 2 === 0 ? types_1.CONFIG.boardColors.light : types_1.CONFIG.boardColors.dark;
            ctx.fillRect(x, y, SS, SS);
        }
    }
    if (lastMove) {
        ctx.fillStyle = types_1.CONFIG.lastMoveColor;
        ctx.fillRect(col(lastMove.from) * SS, row(lastMove.from) * SS, SS, SS);
        ctx.fillRect(col(lastMove.to) * SS, row(lastMove.to) * SS, SS, SS);
    }
    if ((0, board_1.isInCheck)(gs.board, gs.turn)) {
        for (let i = 0; i < 64; i++) {
            const p = gs.board[i];
            if (p && p.color === gs.turn && p.type === 'K') {
                ctx.fillStyle = types_1.CONFIG.checkGlowColor;
                ctx.fillRect(col(i) * SS, row(i) * SS, SS, SS);
            }
        }
    }
    for (const m of legalMovesForSelected) {
        const mr = row(m.to), mc = col(m.to);
        if (gs.board[m.to]) {
            ctx.strokeStyle = 'rgba(255,80,80,0.7)';
            ctx.lineWidth = types_1.CONFIG.captureRingWidth;
            ctx.beginPath();
            ctx.arc(mc * SS + SS / 2, mr * SS + SS / 2, SS / 2 - 4, 0, Math.PI * 2);
            ctx.stroke();
        }
        else {
            ctx.fillStyle = 'rgba(0,0,0,0.25)';
            ctx.beginPath();
            ctx.arc(mc * SS + SS / 2, mr * SS + SS / 2, types_1.CONFIG.legalDotRadius, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    for (let r = 0; r < 8; r++) {
        for (let c = 0; c < 8; c++) {
            const p = gs.board[sq(r, c)];
            if (!p)
                continue;
            const glyph = types_1.PIECE_UNICODE[p.color + p.type];
            const x = c * SS + SS / 2, y = r * SS + SS / 2;
            ctx.fillStyle = 'rgba(0,0,0,0.15)';
            ctx.font = `${SS - 8}px serif`;
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(glyph, x + 2, y + 3);
            ctx.fillStyle = p.color === 'w' ? '#fff' : '#1a1a1a';
            ctx.strokeStyle = p.color === 'w' ? '#333' : '#ddd';
            ctx.lineWidth = 0.5;
            ctx.fillText(glyph, x, y);
            ctx.strokeText(glyph, x, y);
        }
    }
    if (selectedSquare !== null) {
        ctx.fillStyle = types_1.CONFIG.highlightColor;
        ctx.fillRect(col(selectedSquare) * SS, row(selectedSquare) * SS, SS, SS);
    }
    // Coordinate labels
    ctx.font = '10px sans-serif';
    for (let i = 0; i < 8; i++) {
        ctx.fillStyle = i % 2 === 0 ? types_1.CONFIG.boardColors.dark : types_1.CONFIG.boardColors.light;
        ctx.fillText(String.fromCharCode(97 + i), i * SS + SS / 2, canvas.height - 6);
        ctx.fillText(String(8 - i), 8, i * SS + SS / 2);
    }
    updateStatus();
}
function updateStatus() {
    if (!gs)
        return;
    const indicator = document.getElementById('turn-indicator');
    if (gs.gameOver) {
        indicator.textContent = gs.result || 'Game Over';
    }
    else {
        const name = gs.turn === 'w' ? 'White' : 'Black';
        const chk = (0, board_1.isInCheck)(gs.board, gs.turn);
        indicator.textContent = `${name} to move${chk ? ' (check!)' : ''}`;
    }
    updateCapturedTrays();
}
function updateCapturedTrays() {
    if (!gs)
        return;
    const wTray = document.getElementById('captured-white');
    const bTray = document.getElementById('captured-black');
    let capW = [];
    let capB = [];
    for (let i = 0; i < gs.moveHistory.length; i++) {
        const m = gs.moveHistory[i];
        if (m.captured !== null && m.captured !== undefined) {
            // Even-indexed moves are white's, odd-indexed are black's
            if (i % 2 === 0)
                capW.push(m.captured);
            else
                capB.push(m.captured);
        }
    }
    const order = ['Q', 'R', 'B', 'N', 'P'];
    wTray.textContent = capW.sort((a, b) => order.indexOf(a) - order.indexOf(b))
        .map(t => types_1.PIECE_UNICODE['b' + t]).join('');
    bTray.textContent = capB.sort((a, b) => order.indexOf(a) - order.indexOf(b))
        .map(t => types_1.PIECE_UNICODE['w' + t]).join('');
}
// ── Move List ────────────────────────────────────────────────────────
function updateMoveList() {
    const container = document.getElementById('move-list');
    container.innerHTML = '';
    if (!gs)
        return;
    for (let i = 0; i < gs.moveHistory.length; i += 2) {
        const pair = document.createElement('div');
        pair.className = 'move-pair';
        const numSpan = document.createElement('span');
        numSpan.className = 'move-num';
        numSpan.textContent = (Math.floor(i / 2) + 1) + '.';
        pair.appendChild(numSpan);
        // White move
        const wm = gs.moveHistory[i];
        let wNotation = (0, board_1.toAlgebraic)(gs, wm);
        {
            const afterW = replayMoves(i + 1);
            if ((0, board_1.isInCheck)(afterW.board, 'b')) {
                const bLegal = (0, board_1.generateLegalMoves)(afterW.board, 'b', afterW.castling, afterW.enPassant);
                wNotation += bLegal.length === 0 ? '#' : '+';
            }
        }
        const wSpan = document.createElement('span');
        wSpan.className = 'move-w';
        wSpan.textContent = wNotation;
        pair.appendChild(wSpan);
        // Black move
        if (i + 1 < gs.moveHistory.length) {
            const bm = gs.moveHistory[i + 1];
            let bNotation = (0, board_1.toAlgebraic)(gs, bm);
            {
                const afterB = replayMoves(i + 2);
                if ((0, board_1.isInCheck)(afterB.board, 'w')) {
                    const wLegal = (0, board_1.generateLegalMoves)(afterB.board, 'w', afterB.castling, afterB.enPassant);
                    bNotation += wLegal.length === 0 ? '#' : '+';
                }
            }
            const bSpan = document.createElement('span');
            bSpan.className = 'move-b';
            bSpan.textContent = bNotation;
            pair.appendChild(bSpan);
        }
        container.appendChild(pair);
    }
    container.scrollTop = container.scrollHeight;
}
function replayMoves(count) {
    if (!gs)
        return (0, board_1.newGameState)();
    const state = (0, board_1.newGameState)();
    for (let i = 0; i < count && i < gs.moveHistory.length; i++) {
        state.board = (0, board_1.applyMove)(state.board, gs.moveHistory[i]);
        state.turn = state.turn === 'w' ? 'b' : 'w';
    }
    return state;
}
// ── Interaction ──────────────────────────────────────────────────────
let dragState = null;
function getSquareFromEvent(e) {
    const rect = canvas.getBoundingClientRect();
    let cx, cy;
    if ('touches' in e && 'changedTouches' in e) {
        const te = e;
        const touches = te.changedTouches.length > 0 ? te.changedTouches : te.touches;
        if (touches.length === 0)
            return null;
        cx = touches[0].clientX;
        cy = touches[0].clientY;
    }
    else {
        cx = e.clientX;
        cy = e.clientY;
    }
    const x = (cx - rect.left) / rect.width * canvas.width;
    const y = (cy - rect.top) / rect.height * canvas.height;
    const c = Math.floor(x / SS), r = Math.floor(y / SS);
    if (r < 0 || r > 7 || c < 0 || c > 7)
        return null;
    return flipped ? sq(7 - r, 7 - c) : sq(r, c);
}
function handleSquareClick(square) {
    if (!gs || gs.gameOver || pendingPromotion)
        return;
    if (gs.turn !== playerColor && !analysisMode)
        return;
    const piece = gs.board[square];
    if (selectedSquare !== null) {
        const matchingMove = legalMovesForSelected.find(m => m.to === square);
        if (matchingMove) {
            if (matchingMove.promotion) {
                pendingPromotion = { from: selectedSquare, to: square };
                showPromotionPicker(matchingMove, square);
                return;
            }
            makePlayerMove(matchingMove);
            return;
        }
        if (piece && piece.color === gs.turn) {
            selectPiece(square);
            return;
        }
        selectedSquare = null;
        legalMovesForSelected = [];
        render();
        return;
    }
    if (piece && piece.color === gs.turn)
        selectPiece(square);
}
function selectPiece(square) {
    if (!gs)
        return;
    selectedSquare = square;
    legalMovesForSelected = (0, board_1.generateLegalMoves)(gs.board, gs.turn, gs.castling, gs.enPassant)
        .filter(m => m.from === square);
    render();
}
function makePlayerMove(move) {
    if (!gs)
        return;
    selectedSquare = null;
    legalMovesForSelected = [];
    lastMove = move;
    if (move.captured !== null && move.captured !== undefined)
        (0, sound_1.playCapture)();
    else
        (0, sound_1.playMove)();
    gs = (0, board_1.executeMove)(gs, move);
    const endResult = (0, board_1.checkGameEnd)(gs);
    if (endResult.gameOver) {
        gs.gameOver = true;
        gs.result = endResult.result;
        render();
        updateMoveList();
        showGameOver(endResult.result);
        (0, sound_1.playGameOver)();
        saveToStorage();
        return;
    }
    if ((0, board_1.isInCheck)(gs.board, gs.turn))
        (0, sound_1.playCheck)();
    render();
    updateMoveList();
    saveToStorage();
    if (!analysisMode && gs.turn !== playerColor) {
        setTimeout(() => makeAIMove(), 50);
    }
}
// ── Promotion Picker ─────────────────────────────────────────────────
function showPromotionPicker(move, toSquare) {
    const overlay = document.getElementById('promotion-overlay');
    overlay.innerHTML = '';
    overlay.classList.remove('hidden');
    const tr = row(toSquare), tc = col(toSquare);
    const dr = flipped ? 7 - tr : tr;
    const dc = flipped ? 7 - tc : tc;
    overlay.style.left = (dc * SS) + 'px';
    overlay.style.top = (dr * SS) + 'px';
    for (const pt of ['Q', 'R', 'B', 'N']) {
        const btn = document.createElement('div');
        btn.className = 'promo-piece';
        btn.textContent = types_1.PIECE_UNICODE[gs.turn + pt];
        btn.addEventListener('click', () => {
            overlay.classList.add('hidden');
            pendingPromotion = null;
            move.promotion = pt;
            makePlayerMove(move);
        });
        overlay.appendChild(btn);
    }
}
// ── AI Move ──────────────────────────────────────────────────────────
function setAIDepth(d) { aiDepth = d; }
function makeAIMove() {
    if (!gs || gs.gameOver)
        return;
    const move = (0, ai_1.findBestMove)(gs.board, gs.turn, gs.castling, gs.enPassant, aiDepth);
    if (!move)
        return;
    lastMove = move;
    if (move.captured !== null && move.captured !== undefined)
        (0, sound_1.playCapture)();
    else
        (0, sound_1.playMove)();
    gs = (0, board_1.executeMove)(gs, move);
    const endResult = (0, board_1.checkGameEnd)(gs);
    if (endResult.gameOver) {
        gs.gameOver = true;
        gs.result = endResult.result;
        render();
        updateMoveList();
        showGameOver(endResult.result);
        (0, sound_1.playGameOver)();
        saveToStorage();
        return;
    }
    if ((0, board_1.isInCheck)(gs.board, gs.turn))
        (0, sound_1.playCheck)();
    render();
    updateMoveList();
    saveToStorage();
}
// ── Game Over Banner ─────────────────────────────────────────────────
function showGameOver(result) {
    const banner = document.getElementById('game-over-banner');
    banner.textContent = result;
    banner.classList.remove('hidden');
}
function hideGameOver() {
    document.getElementById('game-over-banner').classList.add('hidden');
}
// ── Persistence ──────────────────────────────────────────────────────
const STORAGE_KEY = 'chess-game-state';
function saveToStorage() {
    if (!gs)
        return;
    try {
        localStorage.setItem(STORAGE_KEY, JSON.stringify({
            board: gs.board, turn: gs.turn, castling: gs.castling,
            enPassant: gs.enPassant, halfMoveClock: gs.halfMoveClock,
            fullMoveNumber: gs.fullMoveNumber, moveHistory: gs.moveHistory,
            gameOver: gs.gameOver, result: gs.result,
        }));
    }
    catch { /* ignore */ }
}
function loadFromStorage() {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (!raw)
            return null;
        const d = JSON.parse(raw);
        return {
            board: d.board, turn: d.turn, castling: d.castling, enPassant: d.enPassant,
            halfMoveClock: d.halfMoveClock || 0, fullMoveNumber: d.fullMoveNumber || 1,
            moveHistory: d.moveHistory || [], gameOver: !!d.gameOver, result: d.result,
        };
    }
    catch {
        return null;
    }
}
// ── Public API ───────────────────────────────────────────────────────
function setGameState(state) {
    gs = state;
    selectedSquare = null;
    legalMovesForSelected = [];
    pendingPromotion = null;
    render();
    updateMoveList();
}
function getGameState() { return gs; }
function setLastMove(m) { lastMove = m; }
function setPlayerColor(c) { playerColor = c; }
function setAnalysisMode(m) { analysisMode = m; }
// ── Event Setup ──────────────────────────────────────────────────────
function setupEvents() {
    canvas.addEventListener('click', (e) => { const s = getSquareFromEvent(e); if (s !== null)
        handleSquareClick(s); });
    canvas.addEventListener('mousedown', (e) => {
        const s = getSquareFromEvent(e);
        if (!gs || gs.gameOver || !s)
            return;
        if (gs.turn !== playerColor && !analysisMode)
            return;
        const p = gs.board[s];
        if (!p || p.color !== gs.turn)
            return;
        dragState = { from: s };
        selectPiece(s);
    });
    canvas.addEventListener('mouseup', (e) => {
        if (!dragState || !gs) {
            dragState = null;
            return;
        }
        const s = getSquareFromEvent(e);
        if (s !== null && s !== dragState.from) {
            const m = legalMovesForSelected.find(mv => mv.to === s);
            if (m) {
                if (m.promotion) {
                    pendingPromotion = { from: dragState.from, to: s };
                    showPromotionPicker(m, s);
                }
                else
                    makePlayerMove(m);
            }
        }
        dragState = null;
    });
    canvas.addEventListener('touchstart', (e) => {
        e.preventDefault();
        const s = getSquareFromEvent(e);
        if (!gs || gs.gameOver || !s)
            return;
        if (gs.turn !== playerColor && !analysisMode)
            return;
        const p = gs.board[s];
        if (!p || p.color !== gs.turn)
            return;
        dragState = { from: s };
        selectPiece(s);
    }, { passive: false });
    canvas.addEventListener('touchend', (e) => {
        e.preventDefault();
        if (!dragState || !gs) {
            dragState = null;
            return;
        }
        const s = getSquareFromEvent(e);
        if (s !== null && s !== dragState.from) {
            const m = legalMovesForSelected.find(mv => mv.to === s);
            if (m) {
                if (m.promotion) {
                    pendingPromotion = { from: dragState.from, to: s };
                    showPromotionPicker(m, s);
                }
                else
                    makePlayerMove(m);
            }
        }
        dragState = null;
    }, { passive: false });
    document.getElementById('board-flip-btn').addEventListener('click', () => { flipped = !flipped; render(); });
    document.getElementById('undo-btn').addEventListener('click', () => {
        if (!gs || gs.moveHistory.length < 2)
            return;
        const newHist = gs.moveHistory.slice(0, -2);
        const fresh = (0, board_1.newGameState)();
        for (const m of newHist) {
            fresh.board = (0, board_1.applyMove)(fresh.board, m);
            fresh.turn = fresh.turn === 'w' ? 'b' : 'w';
        }
        gs.moveHistory = newHist;
        gs.board = fresh.board;
        gs.turn = fresh.turn;
        gs.gameOver = false;
        delete gs.result;
        lastMove = newHist.length > 0 ? newHist[newHist.length - 1] : null;
        selectedSquare = null;
        legalMovesForSelected = [];
        hideGameOver();
        render();
        updateMoveList();
        saveToStorage();
    });
    document.getElementById('new-game-btn').addEventListener('click', () => {
        gs = (0, board_1.newGameState)();
        lastMove = null;
        selectedSquare = null;
        legalMovesForSelected = [];
        hideGameOver();
        render();
        updateMoveList();
        saveToStorage();
        if (!analysisMode && playerColor === 'b')
            setTimeout(() => makeAIMove(), 100);
    });
    const muteBtn = document.getElementById('mute-btn');
    muteBtn.addEventListener('click', () => {
        (0, sound_1.setMute)(!(0, sound_1.isMuted)());
        muteBtn.textContent = (0, sound_1.isMuted)() ? '🔇 Unmute' : '🔊 Mute';
    });
    document.getElementById('difficulty').addEventListener('change', (e) => {
        setAIDepth(parseInt(e.target.value));
    });
}
// ── Debug Interface ──────────────────────────────────────────────────
function exposeDebug() {
    window.game = {
        fen: () => gs ? (0, board_1.toFen)(gs) : null,
        legalMoves: () => { if (!gs)
            return []; return (0, board_1.generateLegalMoves)(gs.board, gs.turn, gs.castling, gs.enPassant); },
        move: (notation) => {
            const fc = notation.charCodeAt(0) - 97, fr = 8 - parseInt(notation[1]);
            const tc = notation.charCodeAt(2) - 97, tr = 8 - parseInt(notation[3]);
            if (!gs || gs.gameOver)
                return false;
            const moves = (0, board_1.generateLegalMoves)(gs.board, gs.turn, gs.castling, gs.enPassant);
            const mv = moves.find(m => m.from === sq(fr, fc) && m.to === sq(tr, tc));
            if (!mv)
                return false;
            makePlayerMove(mv);
            return true;
        },
        state: () => gs,
    };
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>