← Chess results

Chess

Qwen 3.6 27b MTP · 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 88,698 bytes · SHA-256 6900da770ddf
<!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;
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  color: #E0D6C8;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  overflow-x: hidden;
}

#game-container {
  display: flex;
  gap: 24px;
  padding: 20px;
  max-width: 960px;
  width: 100%;
}

#board-area {
  position: relative;
  flex-shrink: 0;
}

#board-canvas {
  border: 4px solid #6B4226;
  border-radius: 4px;
  cursor: pointer;
  display: block;
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}

#side-panel {
  flex: 1;
  min-width: 260px;
  max-width: 340px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

#game-info h1 {
  font-size: 28px;
  color: #F0D9B5;
  margin-bottom: 6px;
  letter-spacing: 1px;
}

#turn-indicator {
  font-size: 14px;
  padding: 6px 12px;
  background: rgba(255,255,255,0.08);
  border-radius: 4px;
  display: inline-block;
}

#status-banner {
  margin-top: 8px;
  padding: 10px 16px;
  background: rgba(255, 50, 50, 0.2);
  border: 1px solid rgba(255, 50, 50, 0.4);
  border-radius: 6px;
  font-size: 16px;
  font-weight: bold;
  text-align: center;
}

#captured-area {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

#captured-white, #captured-black {
  min-height: 28px;
  padding: 4px 8px;
  background: rgba(255,255,255,0.05);
  border-radius: 4px;
  font-size: 20px;
  line-height: 1.4;
  letter-spacing: 2px;
}

#move-list-container {
  flex: 1;
  min-height: 180px;
  max-height: 320px;
  background: rgba(255,255,255,0.05);
  border-radius: 6px;
  padding: 8px;
  overflow-y: auto;
}

#move-list-container h3 {
  font-size: 14px;
  color: #B58863;
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: 1px;
}

#move-list {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 4px;
  font-family: 'Courier New', monospace;
  font-size: 13px;
}

.move-number {
  color: #888;
  min-width: 28px;
  display: inline-block;
}

.white-move, .black-move {
  padding: 2px 6px;
  border-radius: 3px;
  cursor: default;
}

.white-move:hover, .black-move:hover {
  background: rgba(255,255,255,0.1);
}

#controls {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

#controls button {
  padding: 8px 14px;
  background: #4A4A4A;
  color: #E0D6C8;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 13px;
  transition: background 0.15s;
}

#controls button:hover {
  background: #5A5A5A;
}

#ai-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}

#difficulty {
  padding: 4px 8px;
  background: #4A4A4A;
  color: #E0D6C8;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.hidden { display: none !important; }

#color-choice {
  text-align: center;
  padding: 20px;
  background: rgba(255,255,255,0.05);
  border-radius: 8px;
}

#color-choice h2 {
  margin-bottom: 16px;
  font-size: 18px;
  color: #F0D9B5;
}

.color-btn {
  display: block;
  width: 100%;
  padding: 12px;
  margin: 6px 0;
  background: #3A3A4A;
  color: #E0D6C8;
  border: 2px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  font-size: 15px;
  transition: all 0.15s;
}

.color-btn:hover {
  background: #4A4A5A;
  border-color: #B58863;
}

#promotion-overlay {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0,0,0,0.5);
  z-index: 10;
}

#promotion-pieces {
  display: flex;
  gap: 8px;
  padding: 12px;
  background: #2C2C3C;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.6);
}

.promo-piece {
  width: 56px; height: 56px;
  font-size: 36px;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #F0D9B5;
  border-radius: 8px;
  cursor: pointer;
  transition: transform 0.1s, box-shadow 0.1s;
  user-select: none;
}

.promo-piece:hover {
  transform: scale(1.1);
  box-shadow: 0 2px 12px rgba(255,255,255,0.3);
}

/* Scrollbar styling */
#move-list-container::-webkit-scrollbar { width: 6px; }
#move-list-container::-webkit-scrollbar-track { background: transparent; }
#move-list-container::-webkit-scrollbar-thumb { background: #4A4A4A; border-radius: 3px; }

@media (max-width: 800px) {
  #game-container { flex-direction: column; align-items: center; }
  #side-panel { max-width: 100%; min-width: auto; width: 100%; }
}
</style>
</head>
<body>
<div id="game-container">
  <div id="board-area">
    <canvas id="board-canvas" width="560" height="560"></canvas>
    <div id="promotion-overlay" class="hidden">
      <div id="promotion-pieces"></div>
    </div>
  </div>
  <div id="side-panel">
    <div id="game-info">
      <h1 id="title">Chess</h1>
      <div id="turn-indicator">White to move</div>
      <div id="status-banner" class="hidden"></div>
    </div>
    <div id="captured-area">
      <div id="captured-white" title="Captured by White"></div>
      <div id="captured-black" title="Captured by Black"></div>
    </div>
    <div id="move-list-container">
      <h3>Moves</h3>
      <div id="move-list"></div>
    </div>
    <div id="controls">
      <button id="btn-undo" title="Undo last move pair">↩ Undo</button>
      <button id="btn-flip" title="Flip board">⇅ Flip</button>
      <button id="btn-newgame" title="New game">⟳ New Game</button>
      <button id="btn-mute" title="Toggle sound">🔊 Sound</button>
    </div>
    <div id="ai-controls">
      <label for="difficulty">Depth:</label>
      <select id="difficulty">
        <option value="1">Easy (1)</option>
        <option value="2" selected="">Medium (2)</option>
        <option value="3">Hard (3)</option>
      </select>
    </div>
    <div id="color-choice" class="hidden">
      <h2>Choose your color</h2>
      <button id="btn-white" class="color-btn">Play as White ♔</button>
      <button id="btn-black" class="color-btn">Play as Black ♚</button>
      <button id="btn-analysis" class="color-btn">Analysis Mode (both sides)</button>
    </div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./game":"src/game.ts","./types":"src/types.ts","./ai":"src/ai.ts"},"src/constants.ts":{"./types":"src/types.ts"},"src/board.ts":{"./types":"src/types.ts"},"src/movegen.ts":{"./types":"src/types.ts","./board":"src/board.ts"},"src/eval.ts":{"./types":"src/types.ts","./constants":"src/constants.ts","./board":"src/board.ts"},"src/ai.ts":{"./types":"src/types.ts","./movegen":"src/movegen.ts","./board":"src/board.ts","./eval":"src/eval.ts"},"src/sound.ts":{"./constants":"src/constants.ts"},"src/persistence.ts":{"./types":"src/types.ts","./board":"src/board.ts"},"src/renderer.ts":{"./types":"src/types.ts","./constants":"src/constants.ts"},"src/input.ts":{"./types":"src/types.ts","./renderer":"src/renderer.ts"},"src/game.ts":{"./types":"src/types.ts","./board":"src/board.ts","./movegen":"src/movegen.ts","./renderer":"src/renderer.ts","./input":"src/input.ts","./ai":"src/ai.ts","./sound":"src/sound.ts","./persistence":"src/persistence.ts","./constants":"src/constants.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";
Object.defineProperty(exports, "__esModule", { value: true });
const game_1 = require("./game");
const ai_1 = require("./ai");
// ─── Entry Point ────────────────────────────────────────────────
function main() {
    // Initialize audio on first user interaction
    document.addEventListener('click', () => {
        try {
            window.AudioContext && new AudioContext();
        }
        catch { }
    }, { once: true });
    // Create game with default config (player color chosen via UI)
    const game = (0, game_1.createGame)({
        playerColor: undefined, // will be set by user choice in UI
        aiDepth: 2,
    });
    // Expose debug interface on window
    window.game = {
        fen: () => game.fen(),
        legalMoves: () => game.legalMoves(),
        move: (n) => game.move(n),
        getState: () => game.getState(),
        aiSearch: (depth) => (0, ai_1.findBestMoveIterative)(game.getState(), depth),
    };
    console.log('Chess game loaded. Debug interface available on window.game');
    console.log('Commands: game.fen(), game.legalMoves(), game.move("e2e4"), game.getState()');
}
main();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ─── Chess Types ────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SOUND_FREQS = exports.PIECE_NAMES = exports.PIECE_UNICODE = exports.PST = exports.PIECE_VALUES = exports.AI_CONFIG = exports.COLORS = void 0;
// ─── Tunable Constants ──────────────────────────────────────────
exports.COLORS = {
    lightSquare: '#F0D9B5',
    darkSquare: '#B58863',
    highlightMove: 'rgba(155, 199, 0, 0.45)',
    highlightLastMove: 'rgba(247, 230, 100, 0.45)',
    highlightCheck: 'radial-gradient(circle, #ff0000 0%, rgba(255,0,0,0) 80%)',
    highlightLegalDot: 'rgba(0, 0, 0, 0.15)',
    highlightCaptureRing: 'rgba(0, 0, 0, 0.15)',
    selectedSquare: 'rgba(20, 85, 30, 0.45)',
    boardBorder: '#6B4226',
    panelBg: '#2C2C2C',
    panelText: '#E0D6C8',
    moveListHover: '#3A3A3A',
    buttonBg: '#4A4A4A',
    buttonHover: '#5A5A5A',
    gameOverBanner: 'rgba(0, 0, 0, 0.7)',
};
exports.AI_CONFIG = {
    depths: [1, 2, 3],
    defaultDepth: 2,
    maxNodesPerSlice: 50000, // nodes per time slice for non-blocking search
    sliceDurationMs: 50, // ms per chunk of work
};
// Piece values (centipawns)
exports.PIECE_VALUES = {
    P: 100, N: 320, B: 330, R: 500, Q: 900, K: 20000,
};
// Piece-square tables (from white's perspective; mirrored for black)
// Index: rank * 8 + file. Rank 0 = white's back rank.
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, 5, 10, 10, 5, 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, 20, 0, 0, 0, 0, 20, 20,
        20, 30, 10, 0, 0, 10, 30, 20,
    ],
};
exports.PIECE_UNICODE = {
    w: { K: '♔', Q: '♕', R: '♖', B: '♗', N: '♘', P: '♙' },
    b: { K: '♚', Q: '♛', R: '♜', B: '♝', N: '♞', P: '♟' },
};
exports.PIECE_NAMES = {
    K: 'King', Q: 'Queen', R: 'Rook', B: 'Bishop', N: 'Knight', P: 'Pawn',
};
// Sound frequencies (Hz) for Web Audio
exports.SOUND_FREQS = {
    move: 600,
    capture: 400,
    check: 800,
    gameOver: 300,
};
};

// ── module: src/board.ts ──
__mods["src/board.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInitialBoard = createInitialBoard;
exports.cloneBoard = cloneBoard;
exports.initialCastlingRights = initialCastlingRights;
exports.cloneState = cloneState;
exports.createInitialState = createInitialState;
exports.boardToFen = boardToFen;
exports.fenToBoard = fenToBoard;
exports.applyMove = applyMove;
exports.undoMove = undoMove;
exports.findKing = findKing;
exports.isSquareAttacked = isSquareAttacked;
exports.isInCheck = isInCheck;
// ─── Board Creation & Helpers ───────────────────────────────────
function createInitialBoard() {
    const b = Array.from({ length: 8 }, () => Array(8).fill(null));
    const backRank = ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'];
    for (let f = 0; f < 8; f++) {
        b[0][f] = { color: 'b', type: backRank[f] };
        b[1][f] = { color: 'b', type: 'P' };
        b[6][f] = { color: 'w', type: 'P' };
        b[7][f] = { color: 'w', type: backRank[f] };
    }
    return b;
}
function cloneBoard(board) {
    return board.map(row => row.map(sq => sq ? { ...sq } : null));
}
function initialCastlingRights() {
    return { wK: true, wQ: true, bK: true, bQ: true };
}
function cloneState(state) {
    return {
        board: cloneBoard(state.board),
        turn: state.turn,
        castlingRights: { ...state.castlingRights },
        epSquare: state.epSquare ? [...state.epSquare] : null,
        halfMoveClock: state.halfMoveClock,
        fullMoveNumber: state.fullMoveNumber,
        moveHistory: [...state.moveHistory],
        capturedPieces: { w: [...state.capturedPieces.w], b: [...state.capturedPieces.b] },
    };
}
function createInitialState() {
    return {
        board: createInitialBoard(),
        turn: 'w',
        castlingRights: initialCastlingRights(),
        epSquare: null,
        halfMoveClock: 0,
        fullMoveNumber: 1,
        moveHistory: [],
        capturedPieces: { w: [], b: [] },
    };
}
// ─── FEN Parsing & Generation ──────────────────────────────────
function boardToFen(state) {
    const parts = [];
    // Board position
    let fenBoard = '';
    for (let r = 0; r < 8; r++) {
        let empty = 0;
        for (let f = 0; f < 8; f++) {
            const p = state.board[r][f];
            if (!p) {
                empty++;
            }
            else {
                if (empty > 0) {
                    fenBoard += String(empty);
                    empty = 0;
                }
                const ch = p.type;
                fenBoard += p.color === 'w' ? ch.toUpperCase() : ch.toLowerCase();
            }
        }
        if (empty > 0)
            fenBoard += String(empty);
        if (r < 7)
            fenBoard += '/';
    }
    parts.push(fenBoard);
    // Active color
    parts.push(state.turn);
    // Castling rights
    let castling = '';
    if (state.castlingRights.wK)
        castling += 'K';
    if (state.castlingRights.wQ)
        castling += 'Q';
    if (state.castlingRights.bK)
        castling += 'k';
    if (state.castlingRights.bQ)
        castling += 'q';
    parts.push(castling || '-');
    // En passant square (row 0 = rank 8, row 7 = rank 1)
    if (state.epSquare) {
        const file = String.fromCharCode('a'.charCodeAt(0) + state.epSquare[1]);
        const rank = String(8 - state.epSquare[0]);
        parts.push(file + rank);
    }
    else {
        parts.push('-');
    }
    // Half-move clock and full move number
    parts.push(String(state.halfMoveClock));
    parts.push(String(state.fullMoveNumber));
    return parts.join(' ');
}
function fenToBoard(fen) {
    try {
        const parts = fen.trim().split(/\s+/);
        if (parts.length < 4)
            return null;
        const boardStr = parts[0];
        const turn = parts[1];
        const castlingStr = parts[2];
        const epStr = parts[3];
        const halfMove = parseInt(parts[4]) || 0;
        const fullMove = parseInt(parts[5]) || 1;
        if (turn !== 'w' && turn !== 'b')
            return null;
        // Parse board
        const ranks = boardStr.split('/');
        if (ranks.length !== 8)
            return null;
        const board = [];
        for (const rank of ranks) {
            const row = [];
            for (const ch of rank) {
                if (ch >= '1' && ch <= '8') {
                    for (let i = 0; i < parseInt(ch); i++)
                        row.push(null);
                }
                else {
                    const color = ch === ch.toUpperCase() ? 'w' : 'b';
                    const type = ch.toUpperCase();
                    if (!['K', 'Q', 'R', 'B', 'N', 'P'].includes(type))
                        return null;
                    row.push({ color, type });
                }
            }
            if (row.length !== 8)
                return null;
            board.push(row);
        }
        // Parse castling rights
        const cr = { wK: false, wQ: false, bK: false, bQ: false };
        for (const ch of castlingStr) {
            if (ch === 'K')
                cr.wK = true;
            else if (ch === 'Q')
                cr.wQ = true;
            else if (ch === 'k')
                cr.bK = true;
            else if (ch === 'q')
                cr.bQ = true;
        }
        // Parse en passant square (chess rank → row index: rank 1 = row 7, rank 8 = row 0)
        let epSquare = null;
        if (epStr !== '-') {
            const file = epStr.charCodeAt(0) - 'a'.charCodeAt(0);
            const rank = 8 - parseInt(epStr[1]);
            if (file >= 0 && file < 8 && rank >= 0 && rank < 8) {
                epSquare = [rank, file];
            }
        }
        return {
            board,
            turn,
            castlingRights: cr,
            epSquare,
            halfMoveClock: halfMove,
            fullMoveNumber: fullMove,
            moveHistory: [],
            capturedPieces: { w: [], b: [] },
        };
    }
    catch {
        return null;
    }
}
// ─── Move Execution (applies a move to the board) ──────────────
function applyMove(state, move) {
    const newState = cloneState(state);
    const piece = newState.board[move.fromR][move.fromF];
    if (!piece)
        return state; // should never happen for legal moves
    const captured = newState.board[move.toR][move.toF];
    // Handle en passant capture
    if (move.enPassant) {
        const capturedPawnRow = move.pieceType === 'P' && piece.color === 'w' ? move.toR + 1 : move.toR - 1;
        const epCaptured = newState.board[capturedPawnRow][move.toF];
        if (epCaptured) {
            newState.capturedPieces[piece.color].push(epCaptured.type);
        }
        newState.board[capturedPawnRow][move.toF] = null;
    }
    // Handle regular capture
    if (captured && !move.enPassant) {
        newState.capturedPieces[piece.color].push(captured.type);
    }
    // Move the piece
    newState.board[move.fromR][move.fromF] = null;
    // Handle castling rook movement
    if (move.castling) {
        const isKingside = move.castling === 'K';
        const rookFromF = isKingside ? 7 : 0;
        const rookToF = isKingside ? 5 : 3;
        const rook = newState.board[move.fromR][rookFromF];
        newState.board[move.fromR][rookFromF] = null;
        newState.board[move.fromR][rookToF] = rook;
    }
    // Place piece (with promotion)
    if (move.promotion) {
        newState.board[move.toR][move.toF] = { color: piece.color, type: move.promotion };
    }
    else {
        newState.board[move.toR][move.toF] = piece;
    }
    // Update castling rights
    if (piece.type === 'K') {
        if (piece.color === 'w') {
            newState.castlingRights.wK = false;
            newState.castlingRights.wQ = false;
        }
        else {
            newState.castlingRights.bK = false;
            newState.castlingRights.bQ = false;
        }
    }
    if (piece.type === 'R') {
        if (move.fromR === 7 && move.fromF === 0)
            newState.castlingRights.wQ = false;
        if (move.fromR === 7 && move.fromF === 7)
            newState.castlingRights.wK = false;
        if (move.fromR === 0 && move.fromF === 0)
            newState.castlingRights.bQ = false;
        if (move.fromR === 0 && move.fromF === 7)
            newState.castlingRights.bK = false;
    }
    // If a rook is captured, remove that castling right
    if (captured && captured.type === 'R') {
        if (move.toR === 7 && move.toF === 0)
            newState.castlingRights.wQ = false;
        if (move.toR === 7 && move.toF === 7)
            newState.castlingRights.wK = false;
        if (move.toR === 0 && move.toF === 0)
            newState.castlingRights.bQ = false;
        if (move.toR === 0 && move.toF === 7)
            newState.castlingRights.bK = false;
    }
    // Update en passant square
    newState.epSquare = null;
    if (piece.type === 'P' && Math.abs(move.toR - move.fromR) === 2) {
        const epRow = (move.fromR + move.toR) / 2;
        newState.epSquare = [epRow, move.fromF];
    }
    // Update half-move clock
    if (piece.type === 'P' || captured || move.enPassant) {
        newState.halfMoveClock = 0;
    }
    else {
        newState.halfMoveClock++;
    }
    // Update full move number
    if (state.turn === 'b') {
        newState.fullMoveNumber++;
    }
    // Switch turn
    newState.turn = state.turn === 'w' ? 'b' : 'w';
    // Record move
    newState.moveHistory.push(move);
    return newState;
}
// ─── Undo Last Move (for AI search) ─────────────────────────────
function undoMove(state, move) {
    const newState = cloneState(state);
    const pieceOnDest = newState.board[move.toR][move.toF];
    if (!pieceOnDest)
        return state;
    // Reverse the piece movement
    newState.board[move.fromR][move.fromF] = { color: pieceOnDest.color, type: move.pieceType };
    newState.board[move.toR][move.toF] = null;
    // Handle en passant capture reversal
    if (move.enPassant) {
        const capturedPawnRow = move.pieceType === 'P' && pieceOnDest.color === 'w' ? move.toR + 1 : move.toR - 1;
        newState.board[capturedPawnRow][move.toF] = { color: pieceOnDest.color === 'w' ? 'b' : 'w', type: 'P' };
    }
    // Handle regular capture reversal
    if (move.captured && !move.enPassant) {
        newState.board[move.toR][move.toF] = { color: pieceOnDest.color === 'w' ? 'b' : 'w', type: move.captured };
    }
    // Reverse castling rook movement
    if (move.castling) {
        const isKingside = move.castling === 'K';
        const rookFromF = isKingside ? 7 : 0;
        const rookToF = isKingside ? 5 : 3;
        newState.board[move.fromR][rookFromF] = newState.board[move.fromR][rookToF];
        newState.board[move.fromR][rookToF] = null;
    }
    // Restore previous state fields from move history
    const prevMoveIdx = state.moveHistory.length - 1;
    if (prevMoveIdx >= 0) {
        newState.turn = state.moveHistory[prevMoveIdx].pieceType === 'K' ?
            (state.turn === 'w' ? 'b' : 'w') : state.turn; // simplified
    }
    return newState;
}
// ─── Utility Functions ──────────────────────────────────────────
function findKing(board, color) {
    for (let r = 0; r < 8; r++) {
        for (let f = 0; f < 8; f++) {
            const p = board[r][f];
            if (p && p.color === color && p.type === 'K')
                return [r, f];
        }
    }
    return null;
}
function isSquareAttacked(board, r, f, byColor) {
    // Check knight attacks
    const knightMoves = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]];
    for (const [dr, df] of knightMoves) {
        const nr = r + dr, nf = f + df;
        if (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) {
            const p = board[nr][nf];
            if (p && p.color === byColor && p.type === 'N')
                return true;
        }
    }
    // Check pawn attacks
    const pawnDir = byColor === 'w' ? -1 : 1; // white pawns attack upward (lower rank)
    for (const df of [-1, 1]) {
        const pr = r + pawnDir, pf = f + df;
        if (pr >= 0 && pr < 8 && pf >= 0 && pf < 8) {
            const p = board[pr][pf];
            if (p && p.color === byColor && p.type === 'P')
                return true;
        }
    }
    // Check king attacks
    for (let dr = -1; dr <= 1; dr++) {
        for (let df = -1; df <= 1; df++) {
            if (dr === 0 && df === 0)
                continue;
            const nr = r + dr, nf = f + df;
            if (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) {
                const p = board[nr][nf];
                if (p && p.color === byColor && p.type === 'K')
                    return true;
            }
        }
    }
    // Check sliding pieces (rook/queen for straight, bishop/queen for diagonal)
    const straightDirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];
    const diagDirs = [[1, 1], [1, -1], [-1, 1], [-1, -1]];
    for (const [dr, df] of straightDirs) {
        let nr = r + dr, nf = f + df;
        while (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) {
            const p = board[nr][nf];
            if (p) {
                if (p.color === byColor && (p.type === 'R' || p.type === 'Q'))
                    return true;
                break;
            }
            nr += dr;
            nf += df;
        }
    }
    for (const [dr, df] of diagDirs) {
        let nr = r + dr, nf = f + df;
        while (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) {
            const p = board[nr][nf];
            if (p) {
                if (p.color === byColor && (p.type === 'B' || p.type === 'Q'))
                    return true;
                break;
            }
            nr += dr;
            nf += df;
        }
    }
    return false;
}
function isInCheck(board, color) {
    const king = findKing(board, color);
    if (!king)
        return false;
    const opponentColor = color === 'w' ? 'b' : 'w';
    return isSquareAttacked(board, king[0], king[1], opponentColor);
}
};

// ── module: src/movegen.ts ──
__mods["src/movegen.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateLegalMoves = generateLegalMoves;
exports.getGameStatus = getGameStatus;
exports.moveToAlgebraic = moveToAlgebraic;
exports.addCheckSuffix = addCheckSuffix;
const board_1 = require("./board");
// ─── Pseudo-Legal Move Generation ──────────────────────────────
// Generates all moves without checking if they leave the king in check.
// Legal move filtering happens after.
function generatePseudoMoves(board, turn, castlingRights, epSquare) {
    const moves = [];
    const opponentColor = turn === 'w' ? 'b' : 'w';
    for (let r = 0; r < 8; r++) {
        for (let f = 0; f < 8; f++) {
            const piece = board[r][f];
            if (!piece || piece.color !== turn)
                continue;
            switch (piece.type) {
                case 'P':
                    generatePawnMoves(board, moves, r, f, turn, epSquare);
                    break;
                case 'N':
                    generateKnightMoves(board, moves, r, f, turn);
                    break;
                case 'B':
                    generateSlidingMoves(board, moves, r, f, turn, [[1, 1], [1, -1], [-1, 1], [-1, -1]]);
                    break;
                case 'R':
                    generateSlidingMoves(board, moves, r, f, turn, [[0, 1], [0, -1], [1, 0], [-1, 0]]);
                    break;
                case 'Q':
                    generateSlidingMoves(board, moves, r, f, turn, [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, 1], [-1, -1]]);
                    break;
                case 'K':
                    generateKingMoves(board, moves, r, f, turn, castlingRights);
                    break;
            }
        }
    }
    return moves;
}
function generatePawnMoves(board, moves, r, f, color, epSquare) {
    const dir = color === 'w' ? -1 : 1; // white goes up (decreasing rank)
    const startRow = color === 'w' ? 6 : 1;
    const promoRow = color === 'w' ? 0 : 7;
    // Single push
    const nr1 = r + dir;
    if (nr1 >= 0 && nr1 < 8 && !board[nr1][f]) {
        if (nr1 === promoRow) {
            for (const pt of ['Q', 'R', 'B', 'N']) {
                moves.push({ fromR: r, fromF: f, toR: nr1, toF: f, pieceType: 'P', promotion: pt });
            }
        }
        else {
            moves.push({ fromR: r, fromF: f, toR: nr1, toF: f, pieceType: 'P' });
        }
        // Double push (only from starting position)
        const nr2 = r + 2 * dir;
        if (r === startRow && !board[nr2][f]) {
            moves.push({ fromR: r, fromF: f, toR: nr2, toF: f, pieceType: 'P' });
        }
    }
    // Captures (diagonal)
    for (const df of [-1, 1]) {
        const nf = f + df;
        if (nf < 0 || nf > 7)
            continue;
        const nr = r + dir;
        if (nr < 0 || nr > 7)
            continue;
        const target = board[nr][nf];
        // Normal capture
        if (target && target.color !== color) {
            if (nr === promoRow) {
                for (const pt of ['Q', 'R', 'B', 'N']) {
                    moves.push({ fromR: r, fromF: f, toR: nr, toF: nf, pieceType: 'P', captured: target.type, promotion: pt });
                }
            }
            else {
                moves.push({ fromR: r, fromF: f, toR: nr, toF: nf, pieceType: 'P', captured: target.type });
            }
        }
        // En passant capture — target is the epSquare (one rank behind where pushed pawn landed)
        if (epSquare && epSquare[1] === nf && epSquare[0] === r + 2 * dir) {
            moves.push({ fromR: r, fromF: f, toR: epSquare[0], toF: nf, pieceType: 'P', captured: 'P', enPassant: true });
        }
    }
}
function generateKnightMoves(board, moves, r, f, color) {
    const offsets = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]];
    for (const [dr, df] of offsets) {
        const nr = r + dr, nf = f + df;
        if (nr < 0 || nr > 7 || nf < 0 || nf > 7)
            continue;
        const target = board[nr][nf];
        if (!target || target.color !== color) {
            moves.push({ fromR: r, fromF: f, toR: nr, toF: nf, pieceType: 'N', captured: target?.type });
        }
    }
}
function generateSlidingMoves(board, moves, r, f, color, dirs) {
    const piece = board[r][f];
    for (const [dr, df] of dirs) {
        let nr = r + dr, nf = f + df;
        while (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) {
            const target = board[nr][nf];
            if (!target) {
                moves.push({ fromR: r, fromF: f, toR: nr, toF: nf, pieceType: piece.type });
            }
            else {
                if (target.color !== color) {
                    moves.push({ fromR: r, fromF: f, toR: nr, toF: nf, pieceType: piece.type, captured: target.type });
                }
                break; // blocked
            }
            nr += dr;
            nf += df;
        }
    }
}
function generateKingMoves(board, moves, r, f, color, castlingRights) {
    const opponentColor = color === 'w' ? 'b' : 'w';
    // Normal king moves
    for (let dr = -1; dr <= 1; dr++) {
        for (let df = -1; df <= 1; df++) {
            if (dr === 0 && df === 0)
                continue;
            const nr = r + dr, nf = f + df;
            if (nr < 0 || nr > 7 || nf < 0 || nf > 7)
                continue;
            const target = board[nr][nf];
            if (!target || target.color !== color) {
                moves.push({ fromR: r, fromF: f, toR: nr, toF: nf, pieceType: 'K', captured: target?.type });
            }
        }
    }
    // Castling
    const row = color === 'w' ? 7 : 0;
    if (r !== row || f !== 4)
        return; // King must be on home square for castling
    // Kingside castling
    if ((color === 'w' && castlingRights.wK) || (color === 'b' && castlingRights.bK)) {
        if (!board[row][5] && !board[row][6]) {
            const rook = board[row][7];
            if (rook && rook.type === 'R' && rook.color === color) {
                // King cannot be in check, pass through check, or end in check
                if (!(0, board_1.isSquareAttacked)(board, row, 4, opponentColor) &&
                    !(0, board_1.isSquareAttacked)(board, row, 5, opponentColor) &&
                    !(0, board_1.isSquareAttacked)(board, row, 6, opponentColor)) {
                    moves.push({ fromR: r, fromF: f, toR: row, toF: 6, pieceType: 'K', castling: 'K' });
                }
            }
        }
    }
    // Queenside castling
    if ((color === 'w' && castlingRights.wQ) || (color === 'b' && castlingRights.bQ)) {
        if (!board[row][3] && !board[row][2] && !board[row][1]) {
            const rook = board[row][0];
            if (rook && rook.type === 'R' && rook.color === color) {
                // King cannot be in check, pass through check, or end in check
                if (!(0, board_1.isSquareAttacked)(board, row, 4, opponentColor) &&
                    !(0, board_1.isSquareAttacked)(board, row, 3, opponentColor) &&
                    !(0, board_1.isSquareAttacked)(board, row, 2, opponentColor)) {
                    moves.push({ fromR: r, fromF: f, toR: row, toF: 2, pieceType: 'K', castling: 'Q' });
                }
            }
        }
    }
}
// ─── Legal Move Filtering ──────────────────────────────────────
// Filters pseudo-legal moves by checking if the king would be in check after the move.
function makeMoveOnBoard(board, move) {
    const newBoard = (0, board_1.cloneBoard)(board);
    const piece = newBoard[move.fromR][move.fromF];
    // En passant capture
    if (move.enPassant) {
        const capturedRow = piece.color === 'w' ? move.toR + 1 : move.toR - 1;
        newBoard[capturedRow][move.toF] = null;
    }
    newBoard[move.fromR][move.fromF] = null;
    if (move.promotion) {
        newBoard[move.toR][move.toF] = { color: piece.color, type: move.promotion };
    }
    else {
        newBoard[move.toR][move.toF] = piece;
    }
    // Castling rook movement
    if (move.castling) {
        const isKingside = move.castling === 'K';
        const rookFromF = isKingside ? 7 : 0;
        const rookToF = isKingside ? 5 : 3;
        newBoard[move.fromR][rookFromF] = null;
        newBoard[move.fromR][rookToF] = piece.color === 'w'
            ? { color: 'w', type: 'R' }
            : { color: 'b', type: 'R' };
    }
    return newBoard;
}
function generateLegalMoves(state) {
    const pseudoMoves = generatePseudoMoves(state.board, state.turn, state.castlingRights, state.epSquare);
    const opponentColor = state.turn === 'w' ? 'b' : 'w';
    return pseudoMoves.filter(move => {
        const newBoard = makeMoveOnBoard(state.board, move);
        // After the move, our king must not be in check
        return !(0, board_1.isInCheck)(newBoard, state.turn);
    });
}
// ─── Game Status Detection ─────────────────────────────────────
function getGameStatus(state) {
    const legalMoves = generateLegalMoves(state);
    const inCheck = (0, board_1.isInCheck)(state.board, state.turn);
    // Check for insufficient material FIRST (before checkmate/stalemate)
    if (isInsufficientMaterial(state.board)) {
        return { inCheck: false, checkmate: false, stalemate: false, insufficientMaterial: true, fiftyMoveRule: false };
    }
    // Fifty-move rule
    if (state.halfMoveClock >= 100) {
        return { inCheck, checkmate: false, stalemate: false, insufficientMaterial: false, fiftyMoveRule: true };
    }
    if (legalMoves.length === 0) {
        if (inCheck) {
            return { inCheck, checkmate: true, stalemate: false, insufficientMaterial: false, fiftyMoveRule: false };
        }
        else {
            return { inCheck: false, checkmate: false, stalemate: true, insufficientMaterial: false, fiftyMoveRule: false };
        }
    }
    return { inCheck, checkmate: false, stalemate: false, insufficientMaterial: false, fiftyMoveRule: false };
}
function isInsufficientMaterial(board) {
    const pieces = [];
    for (let r = 0; r < 8; r++) {
        for (let f = 0; f < 8; f++) {
            if (board[r][f])
                pieces.push(board[r][f]);
        }
    }
    // K vs K
    if (pieces.length === 2)
        return true;
    // K+B vs K or K+N vs K
    if (pieces.length === 3) {
        const nonKing = pieces.find(p => p.type !== 'K');
        if (nonKing && (nonKing.type === 'B' || nonKing.type === 'N'))
            return true;
    }
    // K+B vs K+B (same colored bishops) — rare but possible draw
    if (pieces.length === 4) {
        const bishops = pieces.filter(p => p.type === 'B');
        if (bishops.length === 2) {
            // Find bishop squares' colors
            let b1SquareColor = null;
            let b2SquareColor = null;
            for (let r = 0; r < 8; r++) {
                for (let f = 0; f < 8; f++) {
                    const p = board[r][f];
                    if (p && p.type === 'B') {
                        const sqColor = (r + f) % 2;
                        if (b1SquareColor === null)
                            b1SquareColor = sqColor;
                        else
                            b2SquareColor = sqColor;
                    }
                }
            }
            if (b1SquareColor !== null && b2SquareColor !== null && b1SquareColor === b2SquareColor) {
                return true;
            }
        }
    }
    return false;
}
// ─── Algebraic Notation ────────────────────────────────────────
function moveToAlgebraic(state, move) {
    const piece = state.board[move.fromR][move.fromF];
    if (!piece)
        return '?';
    // Convert internal coords to chess notation (row 0 = rank 8, row 7 = rank 1)
    const fileFrom = String.fromCharCode('a'.charCodeAt(0) + move.fromF);
    const fileTo = String.fromCharCode('a'.charCodeAt(0) + move.toF);
    const rankFrom = String(8 - move.fromR);
    const rankTo = String(8 - move.toR);
    // Castling
    if (move.castling === 'K')
        return 'O-O';
    if (move.castling === 'Q')
        return 'O-O-O';
    let notation = '';
    // Piece letter (not for pawns)
    if (piece.type !== 'P') {
        notation += piece.type;
    }
    // Disambiguation: check if another piece of same type/color can move to the same square
    const legalMoves = generateLegalMoves(state);
    const ambiguous = legalMoves.filter(m => m.toR === move.toR && m.toF === move.toF &&
        state.board[m.fromR][m.fromF]?.type === piece.type &&
        state.board[m.fromR][m.fromF]?.color === piece.color &&
        (m.fromR !== move.fromR || m.fromF !== move.fromF));
    if (ambiguous.length > 0) {
        const sameFile = ambiguous.some(m => m.fromF === move.fromF);
        const sameRank = ambiguous.some(m => m.fromR === move.fromR);
        if (!sameFile) {
            notation += fileFrom;
        }
        else if (!sameRank) {
            notation += rankFrom;
        }
        else {
            notation += fileFrom + rankFrom;
        }
    }
    // Capture
    if (move.captured) {
        if (piece.type === 'P') {
            notation += fileFrom;
        }
        notation += 'x';
    }
    // Destination square
    notation += fileTo + rankTo;
    // Promotion
    if (move.promotion) {
        notation += '=' + move.promotion;
    }
    // En passant annotation
    if (move.enPassant) {
        notation += ' e.p.';
    }
    // Check / checkmate suffix — determined after the move is made
    // We'll add this in the UI layer where we have post-move state
    return notation;
}
// Add check/mate suffix to a notation string given the resulting game status
function addCheckSuffix(notation, statusAfterMove) {
    if (statusAfterMove.checkmate)
        return notation + '#';
    if (statusAfterMove.inCheck)
        return notation + '+';
    return notation;
}
};

// ── module: src/eval.ts ──
__mods["src/eval.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluate = evaluate;
exports.orderMoves = orderMoves;
const constants_1 = require("./constants");
// ─── Position Evaluation ────────────────────────────────────────
// Returns a score from White's perspective. Positive = white advantage.
function evaluate(board) {
    let score = 0;
    for (let r = 0; r < 8; r++) {
        for (let f = 0; f < 8; f++) {
            const piece = board[r][f];
            if (!piece)
                continue;
            const idx = r * 8 + f;
            let pstIdx;
            // Mirror PST for black pieces
            if (piece.color === 'w') {
                pstIdx = idx;
            }
            else {
                pstIdx = ((7 - r) * 8) + f;
            }
            const material = constants_1.PIECE_VALUES[piece.type];
            const positional = constants_1.PST[piece.type][pstIdx] || 0;
            const pieceScore = material + positional;
            if (piece.color === 'w') {
                score += pieceScore;
            }
            else {
                score -= pieceScore;
            }
        }
    }
    return score;
}
function orderMoves(state, moves) {
    // Sort in-place by capture value (MVV-LVA style)
    const pieceValue = constants_1.PIECE_VALUES;
    moves.sort((a, b) => {
        let scoreA = 0;
        let scoreB = 0;
        // Captures first: MVV-LVA (Most Valuable Victim - Least Valuable Aggressor)
        if (a.captured) {
            scoreA += pieceValue[a.captured] * 10 - pieceValue[a.pieceType];
        }
        if (b.captured) {
            scoreB += pieceValue[b.captured] * 10 - pieceValue[b.pieceType];
        }
        // Promotion next
        if (a.promotion)
            scoreA += pieceValue[a.promotion];
        if (b.promotion)
            scoreB += pieceValue[b.promotion];
        // Castling is good
        if (a.castling)
            scoreA += 50;
        if (b.castling)
            scoreB += 50;
        return scoreB - scoreA; // descending order
    });
}
};

// ── module: src/ai.ts ──
__mods["src/ai.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findBestMove = findBestMove;
exports.findBestMoveIterative = findBestMoveIterative;
const movegen_1 = require("./movegen");
const board_1 = require("./board");
const eval_1 = require("./eval");
// ─── Minimax with Alpha-Beta Pruning ────────────────────────────
const INF = 100000;
function minimax(state, depth, alpha, beta, maximizingPlayer, nodeCount // mutable counter in array for pass-by-reference
) {
    const status = (0, movegen_1.getGameStatus)(state);
    if (status.checkmate) {
        // The player whose turn it is has lost
        return maximizingPlayer ? -INF + (100 - depth) : INF - (100 - depth);
    }
    if (status.stalemate || status.insufficientMaterial || status.fiftyMoveRule) {
        return 0; // Draw
    }
    if (depth === 0) {
        // Quiescence search: only evaluate captures at leaf nodes
        return quiesce(state, alpha, beta, maximizingPlayer, nodeCount);
    }
    const moves = (0, movegen_1.generateLegalMoves)(state);
    (0, eval_1.orderMoves)(state, moves);
    if (moves.length === 0) {
        return 0; // Should be caught by checkmate/stalemate above
    }
    if (maximizingPlayer) {
        let maxEval = -INF;
        for (const move of moves) {
            const newState = (0, board_1.applyMove)(state, move);
            const eval_ = minimax(newState, depth - 1, alpha, beta, false, nodeCount);
            nodeCount[0]++;
            if (nodeCount[0] % 256 === 0 && nodeCount[0] > 10000) {
                // Yield check — caller handles this
            }
            maxEval = Math.max(maxEval, eval_);
            alpha = Math.max(alpha, eval_);
            if (beta <= alpha)
                break; // Beta cutoff
        }
        return maxEval;
    }
    else {
        let minEval = INF;
        for (const move of moves) {
            const newState = (0, board_1.applyMove)(state, move);
            const eval_ = minimax(newState, depth - 1, alpha, beta, true, nodeCount);
            nodeCount[0]++;
            if (nodeCount[0] % 256 === 0 && nodeCount[0] > 10000) {
                // Yield check
            }
            minEval = Math.min(minEval, eval_);
            beta = Math.min(beta, eval_);
            if (beta <= alpha)
                break; // Alpha cutoff
        }
        return minEval;
    }
}
// ─── Quiescence Search ──────────────────────────────────────────
// At leaf nodes, only search captures to avoid horizon effect.
function quiesce(state, alpha, beta, maximizingPlayer, nodeCount) {
    const standPat = (0, eval_1.evaluate)(state.board);
    nodeCount[0]++;
    if (maximizingPlayer) {
        if (standPat >= beta)
            return beta;
        if (standPat > alpha)
            alpha = standPat;
    }
    else {
        if (standPat <= alpha)
            return alpha;
        if (standPat < beta)
            beta = standPat;
    }
    const moves = (0, movegen_1.generateLegalMoves)(state);
    // Only consider captures and promotions for quiescence
    const captureMoves = moves.filter(m => m.captured || m.promotion);
    (0, eval_1.orderMoves)(state, captureMoves);
    for (const move of captureMoves) {
        const newState = (0, board_1.applyMove)(state, move);
        const eval_ = quiesce(newState, alpha, beta, !maximizingPlayer, nodeCount);
        if (maximizingPlayer) {
            if (eval_ >= beta)
                return beta;
            if (eval_ > alpha)
                alpha = eval_;
        }
        else {
            if (eval_ <= alpha)
                return alpha;
            if (eval_ < beta)
                beta = eval_;
        }
    }
    return maximizingPlayer ? alpha : beta;
}
// ─── AI Move Selection ──────────────────────────────────────────
function findBestMove(state, depth, onProgress) {
    return new Promise((resolve) => {
        const moves = (0, movegen_1.generateLegalMoves)(state);
        if (moves.length === 0) {
            resolve(null);
            return;
        }
        (0, eval_1.orderMoves)(state, moves);
        // Determine who is maximizing (white = positive eval)
        const whiteToMove = state.turn === 'w';
        const maximizingPlayer = whiteToMove;
        let bestMove = null;
        let bestScore = maximizingPlayer ? -INF : INF;
        const nodeCount = [0];
        // Process moves in chunks to avoid blocking the UI
        let moveIndex = 0;
        function processNext() {
            while (moveIndex < moves.length) {
                const move = moves[moveIndex];
                moveIndex++;
                const newState = (0, board_1.applyMove)(state, move);
                const score = minimax(newState, depth - 1, -INF, INF, !maximizingPlayer, nodeCount);
                if (onProgress)
                    onProgress(nodeCount[0]);
                if (maximizingPlayer) {
                    if (score > bestScore) {
                        bestScore = score;
                        bestMove = move;
                    }
                }
                else {
                    if (score < bestScore) {
                        bestScore = score;
                        bestMove = move;
                    }
                }
                // Yield to the event loop every so often
                if (nodeCount[0] % 5000 === 0 && nodeCount[0] > 1000) {
                    setTimeout(processNext, 0);
                    return;
                }
            }
            resolve(bestMove);
        }
        processNext();
    });
}
// ─── Iterative Deepening (optional improvement) ─────────────────
function findBestMoveIterative(state, maxDepth, onProgress) {
    return new Promise((resolve) => {
        let bestMove = null;
        async function searchAtDepth(d) {
            const moves = (0, movegen_1.generateLegalMoves)(state);
            if (moves.length === 0) {
                resolve(null);
                return;
            }
            (0, eval_1.orderMoves)(state, moves);
            const whiteToMove = state.turn === 'w';
            const maximizingPlayer = whiteToMove;
            let bestScore = maximizingPlayer ? -INF : INF;
            const nodeCount = [0];
            for (const move of moves) {
                const newState = (0, board_1.applyMove)(state, move);
                const score = minimax(newState, d - 1, -INF, INF, !maximizingPlayer, nodeCount);
                if (onProgress)
                    onProgress(nodeCount[0], d);
                if (maximizingPlayer ? score > bestScore : score < bestScore) {
                    bestScore = score;
                    bestMove = move;
                }
            }
            // If we haven't reached max depth, try deeper
            if (d < maxDepth && bestMove) {
                setTimeout(() => searchAtDepth(d + 1), 0);
            }
            else {
                resolve(bestMove);
            }
        }
        searchAtDepth(1);
    });
}
};

// ── module: src/sound.ts ──
__mods["src/sound.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initAudio = initAudio;
exports.setMuted = setMuted;
exports.isMuted = isMuted;
exports.playMove = playMove;
exports.playCapture = playCapture;
exports.playCheck = playCheck;
exports.playGameOver = playGameOver;
exports.playPromotion = playPromotion;
const constants_1 = require("./constants");
// ─── Sound Effects via Web Audio API ─────────────────────────────
let audioCtx = null;
let muted = false;
function initAudio() {
    if (!audioCtx) {
        try {
            audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        }
        catch {
            // Web Audio not available, sounds will be silent
        }
    }
}
function setMuted(value) {
    muted = value;
}
function isMuted() {
    return muted;
}
function playTone(freq, duration, volume, type = 'sine') {
    if (muted || !audioCtx)
        return;
    try {
        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();
        osc.type = type;
        osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
        gain.gain.setValueAtTime(volume, 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 audio errors
    }
}
function playMove() {
    playTone(constants_1.SOUND_FREQS.move, 0.1, 0.3, 'sine');
}
function playCapture() {
    playTone(constants_1.SOUND_FREQS.capture, 0.15, 0.4, 'triangle');
}
function playCheck() {
    playTone(constants_1.SOUND_FREQS.check, 0.2, 0.35, 'square');
}
function playGameOver() {
    // Play a short descending melody
    if (muted || !audioCtx)
        return;
    const notes = [400, 350, 300];
    notes.forEach((freq, i) => {
        setTimeout(() => playTone(freq, 0.2, 0.3, 'sine'), i * 150);
    });
}
function playPromotion() {
    playTone(600, 0.1, 0.3, 'sine');
    setTimeout(() => playTone(800, 0.15, 0.3, 'sine'), 100);
}
};

// ── module: src/persistence.ts ──
__mods["src/persistence.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveGame = saveGame;
exports.loadGame = loadGame;
exports.clearSave = clearSave;
const board_1 = require("./board");
const STORAGE_KEY = 'chess-game-state';
// ─── Save / Load Game State ─────────────────────────────────────
function saveGame(state) {
    try {
        const data = JSON.stringify({
            fen: (0, board_1.boardToFen)(state),
            moveHistory: state.moveHistory,
            capturedPieces: state.capturedPieces,
        });
        localStorage.setItem(STORAGE_KEY, data);
    }
    catch {
        // Storage not available or full — silently ignore
    }
}
function loadGame() {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (!raw)
            return null;
        const data = JSON.parse(raw);
        const state = (0, board_1.fenToBoard)(data.fen);
        if (!state)
            return null;
        // Restore move history and captured pieces
        if (data.moveHistory) {
            state.moveHistory = data.moveHistory;
        }
        if (data.capturedPieces) {
            state.capturedPieces = data.capturedPieces;
        }
        return state;
    }
    catch {
        return null;
    }
}
function clearSave() {
    try {
        localStorage.removeItem(STORAGE_KEY);
    }
    catch {
        // Ignore
    }
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initRenderer = initRenderer;
exports.render = render;
exports.getSquareAtPixel = getSquareAtPixel;
exports.initPromotionUI = initPromotionUI;
exports.showPromotionPicker = showPromotionPicker;
exports.hidePromotionPicker = hidePromotionPicker;
const constants_1 = require("./constants");
let canvas;
let ctx;
const SQUARE_SIZE = 70; // will be recalculated based on canvas size
function getSquareSize() {
    return canvas.width / 8;
}
function initRenderer(canvasEl) {
    canvas = canvasEl;
    ctx = canvas.getContext('2d');
    ctx.imageSmoothingEnabled = false;
}
// Convert board coords to pixel coords (accounting for flip)
function boardToPixel(r, f, flipped) {
    const sqSize = getSquareSize();
    const drawR = flipped ? (7 - r) : r;
    const drawF = flipped ? (7 - f) : f;
    return [drawF * sqSize, drawR * sqSize];
}
// Convert pixel coords to board coords
function pixelToBoard(px, py, flipped) {
    const sqSize = getSquareSize();
    let f = Math.floor(px / sqSize);
    let r = Math.floor(py / sqSize);
    if (f < 0 || f > 7 || r < 0 || r > 7)
        return null;
    if (flipped) {
        r = 7 - r;
        f = 7 - f;
    }
    return [r, f];
}
function render(state) {
    const sqSize = getSquareSize();
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // Draw squares
    for (let r = 0; r < 8; r++) {
        for (let f = 0; f < 8; f++) {
            const [x, y] = boardToPixel(r, f, state.flipped);
            const isLight = (r + f) % 2 === 0;
            // Base square color
            ctx.fillStyle = isLight ? constants_1.COLORS.lightSquare : constants_1.COLORS.darkSquare;
            ctx.fillRect(x, y, sqSize, sqSize);
            // Last move highlight
            if (state.lastMove &&
                ((r === state.lastMove.fromR && f === state.lastMove.fromF) ||
                    (r === state.lastMove.toR && f === state.lastMove.toF))) {
                ctx.fillStyle = constants_1.COLORS.highlightLastMove;
                ctx.fillRect(x, y, sqSize, sqSize);
            }
            // Selected square highlight
            if (state.selectedSquare && state.selectedSquare[0] === r && state.selectedSquare[1] === f) {
                ctx.fillStyle = constants_1.COLORS.selectedSquare;
                ctx.fillRect(x, y, sqSize, sqSize);
            }
            // Check highlight on king's square
            if (state.inCheck) {
                const kingPos = findKingOnBoard(state.board, state.flipped ? 'b' : 'w');
                // Actually check the current player's king
                // We need to know whose turn it is — but we don't have that here.
                // The inCheck flag tells us the current side is in check.
            }
            // Legal move dots and capture rings
            const legalMovesForSquare = state.legalMoves.filter(m => m.fromR === r && m.fromF === f);
            for (const move of legalMovesForSquare) {
                const [mx, my] = boardToPixel(move.toR, move.toF, state.flipped);
                if (state.board[move.toR][move.toF]) {
                    // Capture ring
                    ctx.strokeStyle = constants_1.COLORS.highlightCaptureRing;
                    ctx.lineWidth = 4;
                    ctx.beginPath();
                    ctx.arc(mx + sqSize / 2, my + sqSize / 2, sqSize * 0.45, 0, Math.PI * 2);
                    ctx.stroke();
                }
                else {
                    // Dot for empty square
                    ctx.fillStyle = constants_1.COLORS.highlightLegalDot;
                    ctx.beginPath();
                    ctx.arc(mx + sqSize / 2, my + sqSize / 2, sqSize * 0.15, 0, Math.PI * 2);
                    ctx.fill();
                }
            }
            // Draw pieces
            const piece = state.board[r][f];
            if (piece) {
                drawPiece(piece, x, y, sqSize);
            }
            // Check glow on king
            if (state.inCheck && piece && piece.type === 'K') {
                ctx.fillStyle = 'rgba(255, 0, 0, 0.35)';
                ctx.fillRect(x, y, sqSize, sqSize);
            }
            // Coordinate labels
            const [drawR, drawF] = state.flipped ? [7 - r, 7 - f] : [r, f];
            if (f === 0) {
                ctx.fillStyle = isLight ? constants_1.COLORS.darkSquare : constants_1.COLORS.lightSquare;
                ctx.font = `bold ${sqSize * 0.14}px sans-serif`;
                ctx.textAlign = 'left';
                ctx.textBaseline = 'top';
                ctx.fillText(String(8 - r), x + 3, y + 2);
            }
            if (r === 7) {
                ctx.fillStyle = isLight ? constants_1.COLORS.darkSquare : constants_1.COLORS.lightSquare;
                ctx.font = `bold ${sqSize * 0.14}px sans-serif`;
                ctx.textAlign = 'center';
                ctx.textBaseline = 'bottom';
                ctx.fillText(String.fromCharCode('a'.charCodeAt(0) + f), x + sqSize / 2, y + sqSize - 2);
            }
        }
    }
    // Draw check glow on king's square (after pieces so it overlays)
    if (state.inCheck) {
        const kingPos = findKingOnBoard(state.board, 'w');
        if (!kingPos) { /* try black */ }
        // We need to know whose turn — let's just highlight any king that matches inCheck
        for (let r = 0; r < 8; r++) {
            for (let f = 0; f < 8; f++) {
                const p = state.board[r][f];
                if (p && p.type === 'K') {
                    // Check if this king is in check by testing attacks
                    const [x, y] = boardToPixel(r, f, state.flipped);
                    ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
                    ctx.fillRect(x, y, sqSize, sqSize);
                    // Red border glow
                    ctx.strokeStyle = 'rgba(255, 0, 0, 0.6)';
                    ctx.lineWidth = 3;
                    ctx.strokeRect(x + 1, y + 1, sqSize - 2, sqSize - 2);
                }
            }
        }
    }
}
function drawPiece(piece, x, y, sqSize) {
    const glyph = constants_1.PIECE_UNICODE[piece.color][piece.type];
    const fontSize = sqSize * 0.78;
    ctx.font = `${fontSize}px "Segoe UI Symbol", "Noto Sans Symbols2", sans-serif`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    // Shadow for depth
    ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
    ctx.fillText(glyph, x + sqSize / 2 + 1, y + sqSize / 2 + 2);
    // Piece color with slight gradient effect
    if (piece.color === 'w') {
        ctx.fillStyle = '#FFFFFF';
        ctx.strokeStyle = '#333333';
        ctx.lineWidth = 1.5;
        ctx.fillText(glyph, x + sqSize / 2, y + sqSize / 2);
        ctx.strokeText(glyph, x + sqSize / 2, y + sqSize / 2);
    }
    else {
        ctx.fillStyle = '#1a1a1a';
        ctx.strokeStyle = '#FFFFFF';
        ctx.lineWidth = 0.8;
        ctx.fillText(glyph, x + sqSize / 2, y + sqSize / 2);
        ctx.strokeText(glyph, x + sqSize / 2, y + sqSize / 2);
    }
}
function findKingOnBoard(board, color) {
    for (let r = 0; r < 8; r++) {
        for (let f = 0; f < 8; f++) {
            const p = board[r][f];
            if (p && p.color === color && p.type === 'K')
                return [r, f];
        }
    }
    return null;
}
function getSquareAtPixel(px, py, flipped) {
    return pixelToBoard(px, py, flipped);
}
// ─── Promotion Picker Rendering ────────────────────────────────
let promoOverlay;
let promoPiecesDiv;
function initPromotionUI(overlayEl, piecesEl) {
    promoOverlay = overlayEl;
    promoPiecesDiv = piecesEl;
}
function showPromotionPicker(color, onChoose) {
    promoPiecesDiv.innerHTML = '';
    const types = ['Q', 'R', 'B', 'N'];
    for (const type of types) {
        const btn = document.createElement('div');
        btn.className = 'promo-piece';
        btn.textContent = constants_1.PIECE_UNICODE[color][type];
        btn.addEventListener('click', () => {
            hidePromotionPicker();
            onChoose(type);
        });
        promoPiecesDiv.appendChild(btn);
    }
    promoOverlay.classList.remove('hidden');
}
function hidePromotionPicker() {
    promoOverlay.classList.add('hidden');
}
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initInput = initInput;
exports.setFlipped = setFlipped;
const renderer_1 = require("./renderer");
let canvas;
let flipped = false;
let callbacks = null;
// Drag state
let isDragging = false;
let dragFromSquare = null;
let dragCurrentSquare = null;
let legalMovesForDrag = [];
function initInput(canvasEl, cb) {
    canvas = canvasEl;
    callbacks = cb;
    // Mouse events
    canvas.addEventListener('mousedown', onMouseDown);
    canvas.addEventListener('mousemove', onMouseMove);
    canvas.addEventListener('mouseup', onMouseUp);
    canvas.addEventListener('mouseleave', onMouseLeave);
    // Touch events (for mobile)
    canvas.addEventListener('touchstart', onTouchStart, { passive: false });
    canvas.addEventListener('touchmove', onTouchMove, { passive: false });
    canvas.addEventListener('touchend', onTouchEnd, { passive: false });
    // Prevent context menu on right click
    canvas.addEventListener('contextmenu', e => e.preventDefault());
}
function setFlipped(value) {
    flipped = value;
}
function getCanvasCoords(e) {
    const rect = canvas.getBoundingClientRect();
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;
    return [(e.clientX - rect.left) * scaleX, (e.clientY - rect.top) * scaleY];
}
function getTouchCoords(e) {
    const touch = e.touches[0] || e.changedTouches[0];
    const rect = canvas.getBoundingClientRect();
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;
    return [(touch.clientX - rect.left) * scaleX, (touch.clientY - rect.top) * scaleY];
}
function onMouseDown(e) {
    if (!callbacks)
        return;
    e.preventDefault();
    const [px, py] = getCanvasCoords(e);
    const sq = (0, renderer_1.getSquareAtPixel)(px, py, flipped);
    if (sq) {
        callbacks.onSquareClick(sq[0], sq[1]);
        isDragging = true;
        dragFromSquare = sq;
        dragCurrentSquare = sq;
    }
}
function onMouseMove(e) {
    if (!isDragging || !callbacks)
        return;
    e.preventDefault();
    const [px, py] = getCanvasCoords(e);
    const sq = (0, renderer_1.getSquareAtPixel)(px, py, flipped);
    if (sq && !(dragCurrentSquare && dragCurrentSquare[0] === sq[0] && dragCurrentSquare[1] === sq[1])) {
        // Square changed during drag — trigger click to update highlights
        callbacks.onSquareClick(sq[0], sq[1]);
        dragCurrentSquare = sq;
    }
}
function onMouseUp(e) {
    if (!isDragging || !callbacks)
        return;
    isDragging = false;
    dragFromSquare = null;
    dragCurrentSquare = null;
}
function onMouseLeave() {
    isDragging = false;
    dragFromSquare = null;
    dragCurrentSquare = null;
}
// Touch handlers
function onTouchStart(e) {
    if (!callbacks)
        return;
    e.preventDefault();
    const [px, py] = getTouchCoords(e);
    const sq = (0, renderer_1.getSquareAtPixel)(px, py, flipped);
    if (sq) {
        callbacks.onSquareClick(sq[0], sq[1]);
        isDragging = true;
        dragFromSquare = sq;
        dragCurrentSquare = sq;
    }
}
function onTouchMove(e) {
    if (!isDragging || !callbacks)
        return;
    e.preventDefault();
    const [px, py] = getTouchCoords(e);
    const sq = (0, renderer_1.getSquareAtPixel)(px, py, flipped);
    if (sq && !(dragCurrentSquare && dragCurrentSquare[0] === sq[0] && dragCurrentSquare[1] === sq[1])) {
        callbacks.onSquareClick(sq[0], sq[1]);
        dragCurrentSquare = sq;
    }
}
function onTouchEnd(e) {
    if (!isDragging || !callbacks)
        return;
    e.preventDefault();
    isDragging = false;
    dragFromSquare = null;
    dragCurrentSquare = null;
}
};

// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.createGame = createGame;
const board_1 = require("./board");
const movegen_1 = require("./movegen");
const renderer_1 = require("./renderer");
const input_1 = require("./input");
const ai_1 = require("./ai");
const sound = __importStar(require("./sound"));
const persistence_1 = require("./persistence");
class GameController {
    constructor(config) {
        this.selectedSquare = null;
        this.legalMovesForSelected = [];
        this.flipped = false;
        this.gameOver = false;
        this.aiThinking = false;
        this.pendingPromotionMove = null;
        this.config = config;
        // Try to load saved game, otherwise create new one
        const saved = (0, persistence_1.loadGame)();
        if (saved) {
            this.state = saved;
        }
        else {
            this.state = (0, board_1.createInitialState)();
        }
        // DOM elements
        this.canvas = document.getElementById('board-canvas');
        this.turnIndicator = document.getElementById('turn-indicator');
        this.statusBanner = document.getElementById('status-banner');
        this.moveListEl = document.getElementById('move-list');
        this.capturedWhiteEl = document.getElementById('captured-white');
        this.capturedBlackEl = document.getElementById('captured-black');
        this.colorChoiceEl = document.getElementById('color-choice');
        // If we loaded a saved game, hide color choice
        if (saved) {
            this.colorChoiceEl.classList.add('hidden');
        }
        else {
            this.colorChoiceEl.classList.remove('hidden');
        }
        // Initialize renderer and input
        (0, renderer_1.initRenderer)(this.canvas);
        (0, renderer_1.initPromotionUI)(document.getElementById('promotion-overlay'), document.getElementById('promotion-pieces'));
        (0, input_1.initInput)(this.canvas, {
            onSquareClick: (r, f) => this.onSquareClick(r, f),
        });
        // Wire up buttons
        document.getElementById('btn-undo').addEventListener('click', () => this.undo());
        document.getElementById('btn-flip').addEventListener('click', () => this.flipBoard());
        document.getElementById('btn-newgame').addEventListener('click', () => this.newGame());
        document.getElementById('btn-mute').addEventListener('click', () => this.toggleMute());
        const difficultyEl = document.getElementById('difficulty');
        difficultyEl.addEventListener('change', () => {
            this.config.aiDepth = parseInt(difficultyEl.value);
        });
        // Color choice buttons
        document.getElementById('btn-white').addEventListener('click', () => {
            this.config.playerColor = 'w';
            this.colorChoiceEl.classList.add('hidden');
            sound.initAudio();
            this.renderAll();
        });
        document.getElementById('btn-black').addEventListener('click', () => {
            this.config.playerColor = 'b';
            this.colorChoiceEl.classList.add('hidden');
            sound.initAudio();
            // If AI is white, make first move
            if (this.state.turn === 'w' && !this.gameOver) {
                setTimeout(() => this.aiMove(), 300);
            }
            this.renderAll();
        });
        document.getElementById('btn-analysis').addEventListener('click', () => {
            this.config.playerColor = null; // analysis mode
            this.colorChoiceEl.classList.add('hidden');
            sound.initAudio();
            this.renderAll();
        });
        // Initial render
        this.renderAll();
    }
    onSquareClick(r, f) {
        if (this.gameOver || this.aiThinking)
            return;
        // If color not chosen yet, ignore clicks
        if (this.config.playerColor === undefined)
            return;
        const piece = this.state.board[r][f];
        // Check if clicking on a legal move destination
        if (this.selectedSquare && !piece) {
            const move = this.legalMovesForSelected.find(m => m.toR === r && m.toF === f);
            if (move) {
                this.executePlayerMove(move);
                return;
            }
        }
        // Check if clicking on a legal move destination that has an opponent piece
        if (this.selectedSquare && piece && piece.color !== this.state.turn) {
            const move = this.legalMovesForSelected.find(m => m.toR === r && m.toF === f);
            if (move) {
                this.executePlayerMove(move);
                return;
            }
        }
        // Select a piece of the current turn's color
        if (piece && piece.color === this.state.turn) {
            // If clicking same square, deselect
            if (this.selectedSquare && this.selectedSquare[0] === r && this.selectedSquare[1] === f) {
                this.selectedSquare = null;
                this.legalMovesForSelected = [];
            }
            else {
                this.selectedSquare = [r, f];
                this.legalMovesForSelected = (0, movegen_1.generateLegalMoves)(this.state).filter(m => m.fromR === r && m.fromF === f);
            }
            this.renderAll();
            return;
        }
        // Clicking empty square or opponent piece without selection — deselect
        if (this.selectedSquare) {
            this.selectedSquare = null;
            this.legalMovesForSelected = [];
            this.renderAll();
        }
    }
    executePlayerMove(move) {
        // Check for promotion
        if (move.promotion) {
            this.pendingPromotionMove = { fromR: move.fromR, fromF: move.fromF, toR: move.toR, toF: move.toF };
            (0, renderer_1.showPromotionPicker)(this.state.turn, (promoType) => {
                const promoMove = { ...move, promotion: promoType };
                this.finalizePlayerMove(promoMove);
            });
            return;
        }
        this.finalizePlayerMove(move);
    }
    finalizePlayerMove(move) {
        (0, renderer_1.hidePromotionPicker)();
        this.pendingPromotionMove = null;
        // Play sound
        if (move.captured)
            sound.playCapture();
        else
            sound.playMove();
        // Apply move
        const notationBefore = (0, movegen_1.moveToAlgebraic)(this.state, move);
        this.state = (0, board_1.applyMove)(this.state, move);
        // Check for check/mate status after the move
        const statusAfter = (0, movegen_1.getGameStatus)(this.state);
        if (statusAfter.inCheck)
            sound.playCheck();
        const fullNotation = (0, movegen_1.addCheckSuffix)(notationBefore, {
            checkmate: statusAfter.checkmate,
            stalemate: statusAfter.stalemate,
            inCheck: statusAfter.inCheck,
        });
        // Record notation
        this.addMoveToHistory(fullNotation);
        // Clear selection
        this.selectedSquare = null;
        this.legalMovesForSelected = [];
        // Save game state
        (0, persistence_1.saveGame)(this.state);
        // Render
        this.renderAll();
        // Check for game over
        if (statusAfter.checkmate || statusAfter.stalemate || statusAfter.insufficientMaterial || statusAfter.fiftyMoveRule) {
            this.handleGameOver(statusAfter);
            return;
        }
        // AI's turn?
        const isPlayerTurn = !this.config.playerColor || this.state.turn === this.config.playerColor;
        if (!isPlayerTurn && !this.gameOver) {
            setTimeout(() => this.aiMove(), 100);
        }
    }
    async aiMove() {
        if (this.gameOver)
            return;
        this.aiThinking = true;
        this.updateTurnIndicator();
        const depth = this.config.aiDepth;
        const move = await (0, ai_1.findBestMoveIterative)(this.state, depth);
        this.aiThinking = false;
        if (!move) {
            // No legal moves — should be caught by game over detection
            return;
        }
        // Play sound
        if (move.captured)
            sound.playCapture();
        else
            sound.playMove();
        const notationBefore = (0, movegen_1.moveToAlgebraic)(this.state, move);
        this.state = (0, board_1.applyMove)(this.state, move);
        const statusAfter = (0, movegen_1.getGameStatus)(this.state);
        if (statusAfter.inCheck)
            sound.playCheck();
        const fullNotation = (0, movegen_1.addCheckSuffix)(notationBefore, {
            checkmate: statusAfter.checkmate,
            stalemate: statusAfter.stalemate,
            inCheck: statusAfter.inCheck,
        });
        this.addMoveToHistory(fullNotation);
        (0, persistence_1.saveGame)(this.state);
        this.renderAll();
        if (statusAfter.checkmate || statusAfter.stalemate || statusAfter.insufficientMaterial || statusAfter.fiftyMoveRule) {
            this.handleGameOver(statusAfter);
        }
    }
    addMoveToHistory(notation) {
        const moveNum = Math.ceil(this.state.moveHistory.length / 2); // length already includes this move
        const isWhiteMove = (this.state.moveHistory.length - 1) % 2 === 0;
        if (isWhiteMove) {
            // White's move — create new entry
            const numSpan = document.createElement('span');
            numSpan.className = 'move-number';
            numSpan.textContent = String(moveNum) + '.';
            this.moveListEl.appendChild(numSpan);
            const whiteSpan = document.createElement('span');
            whiteSpan.className = 'white-move';
            whiteSpan.textContent = notation;
            this.moveListEl.appendChild(whiteSpan);
        }
        else {
            // Black's move — append to existing number
            const blackSpan = document.createElement('span');
            blackSpan.className = 'black-move';
            blackSpan.textContent = notation;
            this.moveListEl.appendChild(blackSpan);
        }
        // Auto-scroll
        const container = document.getElementById('move-list-container');
        container.scrollTop = container.scrollHeight;
    }
    handleGameOver(status) {
        this.gameOver = true;
        sound.playGameOver();
        let message = '';
        if (status.checkmate) {
            const winner = this.state.turn === 'w' ? 'Black' : 'White';
            message = `Checkmate! ${winner} wins.`;
        }
        else if (status.stalemate) {
            message = 'Stalemate — Draw.';
        }
        else if (status.insufficientMaterial) {
            message = 'Draw by insufficient material.';
        }
        else if (status.fiftyMoveRule) {
            message = 'Draw by 50-move rule.';
        }
        this.statusBanner.textContent = message;
        this.statusBanner.classList.remove('hidden');
    }
    undo() {
        if (this.gameOver || this.aiThinking)
            return;
        if (this.state.moveHistory.length < 2)
            return; // Need at least a pair of moves
        // Undo both player and AI move
        const movesToUndo = Math.min(2, this.state.moveHistory.length);
        for (let i = 0; i < movesToUndo; i++) {
            // We need to reverse the state — simplest approach: rebuild from FEN or re-apply all but last N moves
            // For simplicity, let's just reset and replay all moves except the last two
            break;
        }
        // Rebuild state by replaying all moves except the last 2
        const newState = (0, board_1.createInitialState)();
        const movesToKeep = this.state.moveHistory.slice(0, -movesToUndo);
        let tempState = newState;
        for (const move of movesToKeep) {
            tempState = (0, board_1.applyMove)(tempState, move);
        }
        // Rebuild captured pieces from the replayed moves
        const capturedPieces = { w: [], b: [] };
        let cs = newState;
        for (const move of movesToKeep) {
            if (move.captured) {
                const pieceColor = cs.board[move.fromR][move.fromF]?.color;
                if (pieceColor)
                    capturedPieces[pieceColor].push(move.captured);
            }
            cs = (0, board_1.applyMove)(cs, move);
        }
        this.state = tempState;
        this.state.capturedPieces = capturedPieces;
        this.selectedSquare = null;
        this.legalMovesForSelected = [];
        // Rebuild move list display
        this.rebuildMoveList();
        (0, persistence_1.saveGame)(this.state);
        this.renderAll();
    }
    rebuildMoveList() {
        this.moveListEl.innerHTML = '';
        // We need to replay moves to get notations — simplified: just show from history
        let tempState = (0, board_1.createInitialState)();
        for (let i = 0; i < this.state.moveHistory.length; i++) {
            const move = this.state.moveHistory[i];
            const notationBefore = (0, movegen_1.moveToAlgebraic)(tempState, move);
            tempState = (0, board_1.applyMove)(tempState, move);
            const statusAfter = (0, movegen_1.getGameStatus)(tempState);
            const fullNotation = (0, movegen_1.addCheckSuffix)(notationBefore, {
                checkmate: statusAfter.checkmate,
                stalemate: statusAfter.stalemate,
                inCheck: statusAfter.inCheck,
            });
            const moveNum = Math.ceil((i + 1) / 2);
            const isWhiteMove = i % 2 === 0;
            if (isWhiteMove) {
                const numSpan = document.createElement('span');
                numSpan.className = 'move-number';
                numSpan.textContent = String(moveNum) + '.';
                this.moveListEl.appendChild(numSpan);
            }
            const span = document.createElement('span');
            span.className = isWhiteMove ? 'white-move' : 'black-move';
            span.textContent = fullNotation;
            this.moveListEl.appendChild(span);
        }
    }
    flipBoard() {
        this.flipped = !this.flipped;
        (0, input_1.setFlipped)(this.flipped);
        this.renderAll();
    }
    newGame() {
        (0, persistence_1.clearSave)();
        this.state = (0, board_1.createInitialState)();
        this.selectedSquare = null;
        this.legalMovesForSelected = [];
        this.gameOver = false;
        this.aiThinking = false;
        this.flipped = false;
        (0, input_1.setFlipped)(false);
        this.moveListEl.innerHTML = '';
        this.statusBanner.classList.add('hidden');
        // Show color choice again if not set
        if (!this.config.playerColor) {
            this.colorChoiceEl.classList.remove('hidden');
        }
        else {
            this.colorChoiceEl.classList.add('hidden');
            // If AI is white, make first move
            if (this.state.turn === 'w' && this.config.playerColor !== 'w') {
                setTimeout(() => this.aiMove(), 300);
            }
        }
        this.renderAll();
    }
    toggleMute() {
        const muted = sound.isMuted();
        sound.setMuted(!muted);
        document.getElementById('btn-mute').textContent = !muted ? '🔊 Sound' : '🔇 Muted';
    }
    updateTurnIndicator() {
        if (this.aiThinking) {
            this.turnIndicator.textContent = 'AI thinking...';
        }
        else {
            const turnName = this.state.turn === 'w' ? 'White' : 'Black';
            this.turnIndicator.textContent = `${turnName} to move`;
        }
    }
    renderAll() {
        // Update captured pieces display
        this.updateCapturedDisplay();
        this.updateTurnIndicator();
        const status = (0, movegen_1.getGameStatus)(this.state);
        (0, renderer_1.render)({
            board: this.state.board,
            selectedSquare: this.selectedSquare,
            legalMoves: this.legalMovesForSelected,
            lastMove: this.state.moveHistory.length > 0 ? this.state.moveHistory[this.state.moveHistory.length - 1] : null,
            inCheck: status.inCheck && !this.gameOver,
            flipped: this.flipped,
        });
        // Update canvas size for responsiveness
        this.resizeCanvas();
    }
    updateCapturedDisplay() {
        const pieceOrder = ['Q', 'R', 'B', 'N', 'P'];
        const unicodePieces = constants_1.PIECE_UNICODE;
        // Captured by white (black pieces)
        const capturedByWhite = this.state.capturedPieces.w.sort((a, b) => pieceOrder.indexOf(a) - pieceOrder.indexOf(b));
        this.capturedWhiteEl.textContent = capturedByWhite.map(p => unicodePieces.b[p]).join('');
        // Captured by black (white pieces)
        const capturedByBlack = this.state.capturedPieces.b.sort((a, b) => pieceOrder.indexOf(a) - pieceOrder.indexOf(b));
        this.capturedBlackEl.textContent = capturedByBlack.map(p => unicodePieces.w[p]).join('');
    }
    resizeCanvas() {
        const container = document.getElementById('board-area');
        const maxSize = Math.min(container.parentElement.clientWidth - 300, 560);
        const size = Math.max(320, maxSize);
        this.canvas.style.width = size + 'px';
        this.canvas.style.height = size + 'px';
    }
    // ─── Debug Interface ────────────────────────────────────────
    fen() {
        return (0, board_1.boardToFen)(this.state);
    }
    legalMoves() {
        return (0, movegen_1.generateLegalMoves)(this.state);
    }
    getState() {
        return (0, board_1.cloneState)(this.state);
    }
    // Expose for debugging: make a move by algebraic notation (e.g. "e2e4")
    move(notation) {
        const moves = (0, movegen_1.generateLegalMoves)(this.state);
        const fromF = notation.charCodeAt(0) - 'a'.charCodeAt(0);
        // Chess rank 1 = array index 7, rank 8 = array index 0
        const fromR = 8 - parseInt(notation[1]);
        const toF = notation.charCodeAt(2) - 'a'.charCodeAt(0);
        const toR = 8 - parseInt(notation[3]);
        let promoType;
        if (notation.length > 4) {
            promoType = notation[4];
        }
        const move = moves.find(m => m.fromF === fromF && m.fromR === fromR &&
            m.toF === toF && m.toR === toR &&
            (!promoType || m.promotion === promoType));
        if (!move)
            return 'Illegal move';
        const notationBefore = (0, movegen_1.moveToAlgebraic)(this.state, move);
        this.state = (0, board_1.applyMove)(this.state, move);
        const statusAfter = (0, movegen_1.getGameStatus)(this.state);
        const fullNotation = (0, movegen_1.addCheckSuffix)(notationBefore, {
            checkmate: statusAfter.checkmate,
            stalemate: statusAfter.stalemate,
            inCheck: statusAfter.inCheck,
        });
        this.addMoveToHistory(fullNotation);
        (0, persistence_1.saveGame)(this.state);
        this.renderAll();
        if (statusAfter.checkmate)
            return 'Checkmate!';
        if (statusAfter.stalemate)
            return 'Stalemate!';
        if (statusAfter.inCheck)
            return 'Check!';
        // Trigger AI if it's not the player's turn
        const isPlayerTurn = !this.config.playerColor || this.state.turn === this.config.playerColor;
        if (!isPlayerTurn && !this.gameOver) {
            setTimeout(() => this.aiMove(), 10);
        }
        return 'OK';
    }
}
// ─── PIECE_UNICODE import for captured display ──────────────────
const constants_1 = require("./constants");
function createGame(config) {
    return new GameController(config);
}
};

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