← Chess results

Chess

ThinkingCap Qwen3.6 27B GGUF · typescript

Initial view of Chess

Browser check: Artifact runtime error. This is a technical check, not a quality review.

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 63,922 bytes · SHA-256 4113a3bfb7a9
<!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: #2b2b2b;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  color: #e0e0e0;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  overflow: hidden;
}
#game-container {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  padding: 20px;
}
#board-area {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}
#board {
  display: grid;
  grid-template-columns: repeat(8, 72px);
  grid-template-rows: repeat(8, 72px);
  border: 3px solid #5c3d2e;
  border-radius: 4px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
  user-select: none;
  position: relative;
}
.square {
  width: 72px;
  height: 72px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 46px;
  cursor: pointer;
  position: relative;
  transition: background-color 0.1s;
}
.square-light { background-color: #f0d9b5; }
.square-dark { background-color: #b58863; }
.square-selected { background-color: #7fc97f !important; }
.square-last-move { background-color: #cdd26a !important; }
.square-selected.square-last-move { background-color: #b7d46b !important; }
.square-check { 
  background: radial-gradient(ellipse at center, #ff0000 0%, #ff6666 25%, rgba(255,0,0,0) 89%) !important;
}
.move-dot {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.25);
  position: absolute;
  pointer-events: none;
}
.capture-ring {
  width: 66px;
  height: 66px;
  border-radius: 50%;
  border: 4px solid rgba(0, 0, 0, 0.25);
  position: absolute;
  pointer-events: none;
}
.piece {
  text-shadow: 0 1px 3px rgba(0,0,0,0.3);
  line-height: 1;
  pointer-events: none;
  z-index: 1;
}
/* Side panel */
#side-panel {
  width: 240px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
#status-bar {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
#turn-indicator {
  font-size: 16px;
  padding: 8px 12px;
  background: #3a3a3a;
  border-radius: 6px;
  text-align: center;
  font-weight: 600;
}
#game-over-banner {
  font-size: 14px;
  padding: 8px 12px;
  background: #c0392b;
  border-radius: 6px;
  text-align: center;
  font-weight: 700;
  animation: pulse 1.5s infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}
#captured-trays {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 40px;
  padding: 6px;
  background: #3a3a3a;
  border-radius: 6px;
}
#captured-white, #captured-black {
  min-height: 22px;
  font-size: 20px;
  letter-spacing: 2px;
  line-height: 22px;
}
#move-list {
  flex: 1;
  min-height: 200px;
  max-height: 400px;
  overflow-y: auto;
  background: #3a3a3a;
  border-radius: 6px;
  padding: 10px;
  font-size: 13px;
  font-family: 'Consolas', 'Courier New', monospace;
  line-height: 1.6;
}
.move-entry {
  display: flex;
  gap: 8px;
}
.move-number { color: #999; min-width: 30px; }
.move-white { color: #e8e8e8; }
.move-black { color: #bbb; }
#controls {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
#controls button {
  flex: 1;
  min-width: 60px;
  padding: 8px 6px;
  border: none;
  border-radius: 6px;
  background: #5c3d2e;
  color: #e0e0e0;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  transition: background 0.2s;
}
#controls button:hover { background: #7a5240; }
#difficulty {
  padding: 8px 6px;
  border: none;
  border-radius: 6px;
  background: #5c3d2e;
  color: #e0e0e0;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  flex: 1;
  min-width: 60px;
}
#color-choice {
  text-align: center;
  padding: 20px;
  background: #3a3a3a;
  border-radius: 6px;
}
#color-choice p {
  margin-bottom: 12px;
  font-size: 16px;
  font-weight: 600;
}
#color-buttons {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
#color-buttons button {
  padding: 12px;
  border: none;
  border-radius: 6px;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.1s;
}
#color-buttons button:hover { transform: scale(1.03); }
#btn-white { background: #f0d9b5; color: #333; }
#btn-black { background: #b58863; color: #fff; }
.hidden { display: none !important; }
/* Promotion overlay */
#promotion-overlay {
  position: absolute;
  background: rgba(0,0,0,0.7);
  border-radius: 8px;
  padding: 8px;
  z-index: 100;
  display: flex;
  gap: 4px;
}
.promo-piece {
  width: 50px;
  height: 50px;
  font-size: 36px;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  border-radius: 6px;
  background: #f0d9b5;
  transition: transform 0.1s;
}
.promo-piece:hover { transform: scale(1.15); }
#ai-thinking {
  font-size: 13px;
  color: #f39c12;
  text-align: center;
  padding: 4px;
}
/* Scrollbar */
#move-list::-webkit-scrollbar { width: 6px; }
#move-list::-webkit-scrollbar-track { background: #333; border-radius: 3px; }
#move-list::-webkit-scrollbar-thumb { background: #666; border-radius: 3px; }
</style>
</head>
<body>
<div id="game-container">
  <div id="board-area">
    <div id="board"></div>
    <div id="promotion-overlay" class="hidden">
      <div id="promotion-choices"></div>
    </div>
  </div>
  <div id="side-panel">
    <div id="status-bar">
      <span id="turn-indicator"></span>
      <span id="game-over-banner" class="hidden"></span>
    </div>
    <div id="captured-trays">
      <div id="captured-white"></div>
      <div id="captured-black"></div>
    </div>
    <div id="move-list"></div>
    <div id="controls">
      <button id="btn-undo" title="Undo">↩ Undo</button>
      <button id="btn-flip" title="Flip Board">⟲ Flip</button>
      <button id="btn-new" title="New Game">✦ New</button>
      <button id="btn-mute" title="Mute/Unmute">🔊</button>
      <select id="difficulty">
        <option value="1">Easy</option>
        <option value="2">Medium</option>
        <option value="3" selected="">Hard</option>
      </select>
    </div>
    <div id="color-choice" class="hidden">
      <p>Choose your color:</p>
      <div id="color-buttons">
        <button id="btn-white">♔ Play as White</button>
        <button id="btn-black">♚ Play as Black</button>
      </div>
    </div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./renderer":"src/renderer.ts","./game":"src/game.ts","./engine":"src/engine.ts","./sound":"src/sound.ts"},"src/engine.ts":{"./types":"src/types.ts"},"src/ai.ts":{"./types":"src/types.ts","./engine":"src/engine.ts"},"src/game.ts":{"./types":"src/types.ts","./engine":"src/engine.ts","./ai":"src/ai.ts","./sound":"src/sound.ts"},"src/renderer.ts":{"./engine":"src/engine.ts","./game":"src/game.ts","./types":"src/types.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 renderer_1 = require("./renderer");
const game_1 = require("./game");
const engine_1 = require("./engine");
const sound_1 = require("./sound");
// Expose debug interface
window.game = (0, game_1.debug)();
window.__chessCheck = engine_1.isCheck;
// Wire renderer callbacks
const game_2 = require("./game");
const renderer_2 = require("./renderer");
(0, game_2.setOnRender)(renderer_2.render);
(0, game_2.setOnAIStart)(renderer_2.render);
(0, game_2.setOnAIDone)(renderer_2.render);
(0, game_2.setOnSelectedChange)(renderer_2.render);
(0, game_2.setOnGameOver)((reason) => {
    (0, renderer_2.render)();
});
// Wire promotion handler
window.__handlePromotion = (pt) => {
    (0, game_1.handlePromotionChoice)(pt);
    (0, renderer_2.render)();
};
// Wire select for drag
window.__handleSelect = (sqNum) => {
    (0, game_1.handleSquareClick)(sqNum);
};
// Wire drag-to-move
window.__handleMove = (from, to) => {
    const gs = (0, game_1.getGameState)();
    if ((0, game_1.getGameOverState)() || (0, game_1.isAIThinking)())
        return;
    if (gs.turn !== window.game._playerColor)
        return;
    const legal = (0, engine_1.generateLegalMoves)(gs);
    const move = legal.find(m => m.from === from && m.to === to);
    if (!move)
        return;
    if (move.promo) {
        (0, game_1.handleSquareClick)(from);
        return;
    }
    (0, game_1.handleSquareClick)(from);
    (0, game_1.handleSquareClick)(to);
};
// --- UI Controls ---
document.getElementById('btn-white').addEventListener('click', () => {
    (0, sound_1.initAudio)();
    (0, game_1.startGame)(true);
    document.getElementById('color-choice').classList.add('hidden');
});
document.getElementById('btn-black').addEventListener('click', () => {
    (0, sound_1.initAudio)();
    (0, game_1.startGame)(false);
    document.getElementById('color-choice').classList.add('hidden');
});
document.getElementById('btn-undo').addEventListener('click', () => {
    (0, game_1.undo)();
    (0, renderer_2.render)();
});
document.getElementById('btn-flip').addEventListener('click', () => {
    (0, game_1.setFlipped)(!(0, game_1.getFlipped)());
    (0, renderer_2.render)();
});
document.getElementById('btn-new').addEventListener('click', () => {
    document.getElementById('color-choice').classList.remove('hidden');
    (0, game_1.newGame)();
    (0, renderer_2.render)();
});
document.getElementById('btn-mute').addEventListener('click', () => {
    const muted = (0, sound_1.isMuted)();
    (0, sound_1.setMuted)(!muted);
    document.getElementById('btn-mute').textContent = !muted ? '🔇' : '🔊';
});
document.getElementById('difficulty').addEventListener('change', (e) => {
    (0, game_1.setAIDepth)(parseInt(e.target.value));
});
// Click handler on board squares
document.getElementById('board').addEventListener('click', (e) => {
    const sqEl = e.target.closest('.square');
    if (!sqEl)
        return;
    const sqNum = parseInt(sqEl.dataset.square || '');
    if (!isNaN(sqNum)) {
        (0, game_1.handleSquareClick)(sqNum);
        (0, renderer_2.render)();
    }
});
// --- Init ---
(0, renderer_1.initRenderer)();
// Attempt reload from localStorage
const STORAGE_KEY = 'chess_game_state';
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
    try {
        const data = JSON.parse(raw);
        if (data.fen) {
            document.getElementById('color-choice').classList.add('hidden');
        }
    }
    catch {
        document.getElementById('color-choice').classList.remove('hidden');
    }
}
else {
    document.getElementById('color-choice').classList.remove('hidden');
}
};

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

// ── module: src/engine.ts ──
__mods["src/engine.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PIECE_SYMBOLS = exports.START_FEN = void 0;
exports.sq = sq;
exports.sqFile = sqFile;
exports.sqRank = sqRank;
exports.squareName = squareName;
exports.parseSquare = parseSquare;
exports.fenToState = fenToState;
exports.stateToFen = stateToFen;
exports.isCheck = isCheck;
exports.generatePseudoLegalMoves = generatePseudoLegalMoves;
exports.makeMove = makeMove;
exports.generateLegalMoves = generateLegalMoves;
exports.isCheckmate = isCheckmate;
exports.isStalemate = isStalemate;
exports.isInsufficientMaterial = isInsufficientMaterial;
exports.is50MoveRule = is50MoveRule;
exports.gameOver = gameOver;
exports.moveToAlgebraic = moveToAlgebraic;
// Constants
const FILE_NAMES = 'abcdefgh';
const RANK_NAMES = '87654321';
exports.START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
// Piece unicode glyphs
exports.PIECE_SYMBOLS = {
    w: { K: '♔', Q: '♕', R: '♖', B: '♗', N: '♘', P: '♙' },
    b: { K: '♚', Q: '♛', R: '♜', B: '♝', N: '♞', P: '♟' },
};
// --- Board helpers ---
function sq(row, col) {
    return row * 8 + col;
}
function sqFile(s) { return s & 7; }
function sqRank(s) { return s >> 3; }
function squareName(s) {
    return FILE_NAMES[sqFile(s)] + RANK_NAMES[sqRank(s)];
}
function parseSquare(name) {
    const f = FILE_NAMES.indexOf(name[0]);
    const r = RANK_NAMES.indexOf(name[1]);
    if (f < 0 || r < 0)
        return null;
    return sq(r, f);
}
// --- FEN ---
function fenToState(fen) {
    const parts = fen.split(' ');
    const rows = parts[0].split('/');
    const board = new Array(64).fill(null);
    for (let r = 0; r < 8; r++) {
        let c = 0;
        for (const ch of rows[r]) {
            if (ch >= '1' && ch <= '8') {
                c += parseInt(ch);
            }
            else {
                const color = (ch === ch.toUpperCase()) ? 'w' : 'b';
                board[sq(r, c++)] = { type: ch.toUpperCase(), color };
            }
        }
    }
    const turn = parts[1];
    const castlingRights = {
        K: parts[2].includes('K'),
        Q: parts[2].includes('Q'),
        k: parts[2].includes('k'),
        q: parts[2].includes('q'),
    };
    let enPassantTarget = null;
    if (parts[3] !== '-') {
        enPassantTarget = parseSquare(parts[3]);
    }
    const halfMoveClock = parseInt(parts[4]) || 0;
    const fullMoveNumber = parseInt(parts[5]) || 1;
    return {
        board,
        turn,
        castlingRights,
        enPassantTarget,
        halfMoveClock,
        fullMoveNumber,
        moveHistory: [],
        capturedPieces: { w: [], b: [] },
    };
}
function stateToFen(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) {
                if (empty > 0) {
                    fen += empty;
                    empty = 0;
                }
                fen += (p.color === 'w') ? p.type : p.type.toLowerCase();
            }
            else {
                empty++;
            }
        }
        if (empty > 0)
            fen += empty;
        if (r < 7)
            fen += '/';
    }
    fen += ' ' + gs.turn;
    let cr = '';
    if (gs.castlingRights.K)
        cr += 'K';
    if (gs.castlingRights.Q)
        cr += 'Q';
    if (gs.castlingRights.k)
        cr += 'k';
    if (gs.castlingRights.q)
        cr += 'q';
    fen += ' ' + (cr || '-');
    if (gs.enPassantTarget !== null) {
        fen += ' ' + squareName(gs.enPassantTarget);
    }
    else {
        fen += ' -';
    }
    fen += ' ' + gs.halfMoveClock + ' ' + gs.fullMoveNumber;
    return fen;
}
// --- Move Generation ---
function opponent(color) {
    return color === 'w' ? 'b' : 'w';
}
function inBounds(s) {
    return s >= 0 && s < 64;
}
function isAttackedBy(board, square, color) {
    // Check all piece types for attacks on the given square
    const pawnDir = color === 'w' ? 1 : -1;
    // Pawn attacks
    for (const dc of [-1, 1]) {
        const pr = sqRank(square) + pawnDir;
        const pc = sqFile(square) + dc;
        if (pr >= 0 && pr < 8 && pc >= 0 && pc < 8) {
            const p = board[sq(pr, pc)];
            if (p && p.type === 'P' && p.color === color)
                return true;
        }
    }
    // Knight attacks
    const knightMoves = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]];
    for (const [dr, dc] of knightMoves) {
        const r = sqRank(square) + dr, c = sqFile(square) + dc;
        if (r >= 0 && r < 8 && c >= 0 && c < 8) {
            const p = board[sq(r, c)];
            if (p && p.type === 'N' && p.color === color)
                return true;
        }
    }
    // King attacks
    for (let dr = -1; dr <= 1; dr++) {
        for (let dc = -1; dc <= 1; dc++) {
            if (dr === 0 && dc === 0)
                continue;
            const r = sqRank(square) + dr, c = sqFile(square) + dc;
            if (r >= 0 && r < 8 && c >= 0 && c < 8) {
                const p = board[sq(r, c)];
                if (p && p.type === 'K' && p.color === color)
                    return true;
            }
        }
    }
    // Sliding pieces
    const dirs = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [-1, 1], [1, -1], [1, 1]];
    for (let d = 0; d < 8; d++) {
        const [dr, dc] = dirs[d];
        const isDiag = d >= 4;
        let r = sqRank(square) + dr, c = sqFile(square) + dc;
        while (r >= 0 && r < 8 && c >= 0 && c < 8) {
            const p = board[sq(r, c)];
            if (p) {
                if (p.color === color) {
                    if (p.type === 'Q' || (isDiag && p.type === 'B') || (!isDiag && p.type === 'R'))
                        return true;
                }
                break;
            }
            r += dr;
            c += dc;
        }
    }
    return false;
}
function isCheck(board, color) {
    let kingSq = null;
    for (let i = 0; i < 64; i++) {
        const p = board[i];
        if (p && p.type === 'K' && p.color === color) {
            kingSq = i;
            break;
        }
    }
    if (kingSq === null)
        return false;
    return isAttackedBy(board, kingSq, opponent(color));
}
function generatePseudoLegalMoves(gs) {
    const moves = [];
    const color = gs.turn;
    const dir = color === 'w' ? -1 : 1;
    const startRow = color === 'w' ? 6 : 1;
    const promoRow = color === 'w' ? 0 : 7;
    for (let from = 0; from < 64; from++) {
        const piece = gs.board[from];
        if (!piece || piece.color !== color)
            continue;
        switch (piece.type) {
            case 'P': {
                // Forward one
                const f1 = from + dir * 8;
                if (inBounds(f1) && !gs.board[f1]) {
                    if (sqRank(f1) === promoRow) {
                        for (const pt of ['Q', 'R', 'B', 'N']) {
                            moves.push({ from, to: f1, promo: pt });
                        }
                    }
                    else {
                        moves.push({ from, to: f1 });
                    }
                    // Forward two
                    const f2 = from + dir * 16;
                    if (sqRank(from) === startRow && !gs.board[f2]) {
                        moves.push({ from, to: f2 });
                    }
                }
                // Captures
                for (const dc of [-1, 1]) {
                    const fr = sqRank(from), fc = sqFile(from) + dc;
                    if (fc < 0 || fc > 7)
                        continue;
                    const cap = sq(fr + dir, fc);
                    if (inBounds(cap)) {
                        if (gs.board[cap] && gs.board[cap].color !== color) {
                            if (sqRank(cap) === promoRow) {
                                for (const pt of ['Q', 'R', 'B', 'N']) {
                                    moves.push({ from, to: cap, promo: pt });
                                }
                            }
                            else {
                                moves.push({ from, to: cap });
                            }
                        }
                        // En passant
                        if (cap === gs.enPassantTarget) {
                            moves.push({ from, to: cap, enPassant: true });
                        }
                    }
                }
                break;
            }
            case 'N': {
                const knightMoves = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]];
                for (const [dr, dc] of knightMoves) {
                    const r = sqRank(from) + dr, c = sqFile(from) + dc;
                    if (r >= 0 && r < 8 && c >= 0 && c < 8) {
                        const to = sq(r, c);
                        const target = gs.board[to];
                        if (!target || target.color !== color)
                            moves.push({ from, to });
                    }
                }
                break;
            }
            case 'B': {
                for (const [dr, dc] of [[-1, -1], [-1, 1], [1, -1], [1, 1]]) {
                    let r = sqRank(from) + dr, c = sqFile(from) + dc;
                    while (r >= 0 && r < 8 && c >= 0 && c < 8) {
                        const to = sq(r, c);
                        const target = gs.board[to];
                        if (!target) {
                            moves.push({ from, to });
                        }
                        else {
                            if (target.color !== color)
                                moves.push({ from, to });
                            break;
                        }
                        r += dr;
                        c += dc;
                    }
                }
                break;
            }
            case 'R': {
                for (const [dr, dc] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) {
                    let r = sqRank(from) + dr, c = sqFile(from) + dc;
                    while (r >= 0 && r < 8 && c >= 0 && c < 8) {
                        const to = sq(r, c);
                        const target = gs.board[to];
                        if (!target) {
                            moves.push({ from, to });
                        }
                        else {
                            if (target.color !== color)
                                moves.push({ from, to });
                            break;
                        }
                        r += dr;
                        c += dc;
                    }
                }
                break;
            }
            case 'Q': {
                for (const [dr, dc] of [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]) {
                    let r = sqRank(from) + dr, c = sqFile(from) + dc;
                    while (r >= 0 && r < 8 && c >= 0 && c < 8) {
                        const to = sq(r, c);
                        const target = gs.board[to];
                        if (!target) {
                            moves.push({ from, to });
                        }
                        else {
                            if (target.color !== color)
                                moves.push({ from, to });
                            break;
                        }
                        r += dr;
                        c += dc;
                    }
                }
                break;
            }
            case 'K': {
                for (let dr = -1; dr <= 1; dr++) {
                    for (let dc = -1; dc <= 1; dc++) {
                        if (dr === 0 && dc === 0)
                            continue;
                        const r = sqRank(from) + dr, c = sqFile(from) + dc;
                        if (r >= 0 && r < 8 && c >= 0 && c < 8) {
                            const to = sq(r, c);
                            const target = gs.board[to];
                            if (!target || target.color !== color)
                                moves.push({ from, to });
                        }
                    }
                }
                // Castling
                const row = color === 'w' ? 7 : 0;
                if (sqRank(from) === row && sqFile(from) === 4) {
                    // King-side
                    const kRight = color === 'w' ? gs.castlingRights.K : gs.castlingRights.k;
                    if (kRight && !gs.board[sq(row, 5)] && !gs.board[sq(row, 6)]) {
                        if (!isCheck(gs.board, color)
                            && !isAttackedBy(gs.board, sq(row, 5), opponent(color))
                            && !isAttackedBy(gs.board, sq(row, 6), opponent(color))) {
                            moves.push({ from, to: sq(row, 6), castling: 'K' });
                        }
                    }
                    // Queen-side
                    const qRight = color === 'w' ? gs.castlingRights.Q : gs.castlingRights.q;
                    if (qRight && !gs.board[sq(row, 3)] && !gs.board[sq(row, 2)] && !gs.board[sq(row, 1)]) {
                        if (!isCheck(gs.board, color)
                            && !isAttackedBy(gs.board, sq(row, 3), opponent(color))
                            && !isAttackedBy(gs.board, sq(row, 2), opponent(color))) {
                            moves.push({ from, to: sq(row, 2), castling: 'Q' });
                        }
                    }
                }
                break;
            }
        }
    }
    return moves;
}
// --- Make / Unmake ---
function makeMove(gs, move) {
    const newGs = {
        board: [...gs.board],
        turn: opponent(gs.turn),
        castlingRights: { ...gs.castlingRights },
        enPassantTarget: null,
        halfMoveClock: gs.halfMoveClock + 1,
        fullMoveNumber: gs.fullMoveNumber,
        moveHistory: [...gs.moveHistory],
        capturedPieces: {
            w: [...gs.capturedPieces.w],
            b: [...gs.capturedPieces.b],
        },
    };
    const piece = newGs.board[move.from];
    const captured = newGs.board[move.to];
    if (captured) {
        newGs.capturedPieces[opponent(gs.turn)].push(captured.type);
        newGs.halfMoveClock = 0;
    }
    // En passant capture
    if (move.enPassant) {
        const capSq = move.to - (gs.turn === 'w' ? 8 : -8);
        const epPiece = newGs.board[capSq];
        newGs.capturedPieces[opponent(gs.turn)].push(epPiece.type);
        newGs.board[capSq] = null;
        newGs.halfMoveClock = 0;
    }
    // Move piece
    newGs.board[move.from] = null;
    newGs.board[move.to] = piece;
    // Promotion
    if (move.promo) {
        newGs.board[move.to] = { type: move.promo, color: piece.color };
    }
    // Castling - move rook
    if (move.castling) {
        const row = sqRank(move.from);
        if (move.castling === 'K') {
            newGs.board[sq(row, 5)] = newGs.board[sq(row, 7)];
            newGs.board[sq(row, 7)] = null;
        }
        else {
            newGs.board[sq(row, 3)] = newGs.board[sq(row, 0)];
            newGs.board[sq(row, 0)] = null;
        }
    }
    // Update en passant target
    if (piece.type === 'P' && Math.abs(sqRank(move.to) - sqRank(move.from)) === 2) {
        newGs.enPassantTarget = (move.from + move.to) >> 1;
    }
    // Update castling rights
    if (piece.type === 'K') {
        if (gs.turn === 'w') {
            newGs.castlingRights.K = false;
            newGs.castlingRights.Q = false;
        }
        else {
            newGs.castlingRights.k = false;
            newGs.castlingRights.q = false;
        }
    }
    if (piece.type === 'R') {
        if (move.from === sq(7, 7))
            newGs.castlingRights.K = false;
        if (move.from === sq(7, 0))
            newGs.castlingRights.Q = false;
        if (move.from === sq(0, 7))
            newGs.castlingRights.k = false;
        if (move.from === sq(0, 0))
            newGs.castlingRights.q = false;
    }
    // If rook captured on its home square
    if (move.to === sq(7, 7))
        newGs.castlingRights.K = false;
    if (move.to === sq(7, 0))
        newGs.castlingRights.Q = false;
    if (move.to === sq(0, 7))
        newGs.castlingRights.k = false;
    if (move.to === sq(0, 0))
        newGs.castlingRights.q = false;
    // Full move number increment
    if (gs.turn === 'b')
        newGs.fullMoveNumber++;
    return newGs;
}
function generateLegalMoves(gs) {
    const pseudo = generatePseudoLegalMoves(gs);
    return pseudo.filter(m => {
        const newState = makeMove(gs, m);
        return !isCheck(newState.board, newState.turn);
    });
}
// --- Game status ---
function isCheckmate(gs) {
    return isCheck(gs.board, gs.turn) && generateLegalMoves(gs).length === 0;
}
function isStalemate(gs) {
    return !isCheck(gs.board, gs.turn) && generateLegalMoves(gs).length === 0;
}
function isInsufficientMaterial(gs) {
    const pieces = [];
    for (const p of gs.board) {
        if (p)
            pieces.push(p);
    }
    if (pieces.length === 2)
        return true; // K vs K
    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 is50MoveRule(gs) {
    return gs.halfMoveClock >= 100;
}
function gameOver(gs) {
    if (isCheckmate(gs))
        return 'checkmate';
    if (isStalemate(gs))
        return 'stalemate';
    if (isInsufficientMaterial(gs))
        return 'insufficient_material';
    if (is50MoveRule(gs))
        return '50_move_rule';
    return null;
}
// --- Algebraic notation ---
function getDisambiguation(gs, move) {
    const piece = gs.board[move.from];
    const same = generatePseudoLegalMoves(gs).filter(m => m.to === move.to && gs.board[m.from] && gs.board[m.from].type === piece.type && gs.board[m.from].color === piece.color && m.from !== move.from);
    if (same.length === 0)
        return '';
    const sameFile = same.some(m => sqFile(m.from) === sqFile(move.from));
    const sameRank = same.some(m => sqRank(m.from) === sqRank(move.from));
    if (sameFile && sameRank)
        return FILE_NAMES[sqFile(move.from)] + RANK_NAMES[sqRank(move.from)];
    if (sameFile)
        return RANK_NAMES[sqRank(move.from)];
    return FILE_NAMES[sqFile(move.from)];
}
function moveToAlgebraic(gs, move) {
    const piece = gs.board[move.from];
    const isCapture = gs.board[move.to] !== null || move.enPassant;
    let notation = '';
    if (move.castling === 'K')
        return 'O-O';
    if (move.castling === 'Q')
        return 'O-O-O';
    if (piece.type !== 'P') {
        notation += piece.type;
        notation += getDisambiguation(gs, move);
    }
    if (isCapture) {
        if (piece.type === 'P')
            notation += FILE_NAMES[sqFile(move.from)];
        notation += 'x';
    }
    notation += squareName(move.to);
    if (move.promo)
        notation += '=' + move.promo;
    if (move.enPassant)
        notation += ' e.p.';
    // Check / checkmate after the move
    const newState = makeMove(gs, move);
    if (isCheck(newState.board, newState.turn)) {
        if (isCheckmate(newState))
            notation += '#';
        else
            notation += '+';
    }
    return notation;
}
};

// ── module: src/ai.ts ──
__mods["src/ai.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PIECE_VALUES = void 0;
exports.findBestMove = findBestMove;
const engine_1 = require("./engine");
// --- Tunable Evaluation Weights ---
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)
const 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, 20, 0, 0, 0, 0, 20, 20,
        20, 30, 10, 0, 0, 10, 30, 20,
    ],
};
function mirroredSquare(sq) {
    return ((7 - (0, engine_1.sqRank)(sq)) << 3) | (0, engine_1.sqFile)(sq);
}
function evaluate(gs, color) {
    let score = 0;
    for (let i = 0; i < 64; i++) {
        const piece = gs.board[i];
        if (!piece)
            continue;
        const val = exports.PIECE_VALUES[piece.type];
        const pstIdx = piece.color === color ? i : mirroredSquare(i);
        const pst = PST[piece.type][pstIdx];
        const sign = piece.color === color ? 1 : -1;
        score += sign * (val + pst);
    }
    // Mobility bonus
    return score;
}
function orderMoves(gs, moves) {
    // Simple move ordering: captures first (with MVV-LVA), then others
    return [...moves].sort((a, b) => {
        const capA = gs.board[a.to] ? exports.PIECE_VALUES[gs.board[a.to].type] : 0;
        const capB = gs.board[b.to] ? exports.PIECE_VALUES[gs.board[b.to].type] : 0;
        const promoA = a.promo ? exports.PIECE_VALUES[a.promo] : 0;
        const promoB = b.promo ? exports.PIECE_VALUES[b.promo] : 0;
        return (capB + promoB) - (capA + promoA);
    });
}
function minimax(gs, depth, alpha, beta, maximizing, rootColor, nodes) {
    if (depth === 0) {
        return quiesce(gs, alpha, beta, maximizing, rootColor, nodes);
    }
    const moves = (0, engine_1.generateLegalMoves)(gs);
    if (moves.length === 0) {
        if ((0, engine_1.isCheck)(gs.board, gs.turn)) {
            return maximizing ? -99999 + (32 - depth) : 99999 - (32 - depth);
        }
        return 0; // stalemate
    }
    const ordered = orderMoves(gs, moves);
    if (maximizing) {
        let best = -Infinity;
        for (const move of ordered) {
            const newGs = (0, engine_1.makeMove)(gs, move);
            nodes[0]++;
            const score = minimax(newGs, depth - 1, alpha, beta, false, rootColor, nodes);
            best = Math.max(best, score);
            alpha = Math.max(alpha, score);
            if (beta <= alpha)
                break;
        }
        return best;
    }
    else {
        let best = Infinity;
        for (const move of ordered) {
            const newGs = (0, engine_1.makeMove)(gs, move);
            nodes[0]++;
            const score = minimax(newGs, depth - 1, alpha, beta, true, rootColor, nodes);
            best = Math.min(best, score);
            beta = Math.min(beta, score);
            if (beta <= alpha)
                break;
        }
        return best;
    }
}
function quiesce(gs, alpha, beta, maximizing, rootColor, nodes) {
    const standPat = evaluate(gs, rootColor);
    if (maximizing) {
        if (standPat >= beta)
            return beta;
        if (alpha < standPat)
            alpha = standPat;
    }
    else {
        if (standPat <= alpha)
            return alpha;
        if (beta > standPat)
            beta = standPat;
    }
    // Only consider capture moves in quiescence
    const captures = (0, engine_1.generatePseudoLegalMoves)(gs).filter(m => gs.board[m.to] !== null || m.enPassant);
    const legalCaptures = captures.filter(m => {
        const ns = (0, engine_1.makeMove)(gs, m);
        return !(0, engine_1.isCheck)(ns.board, ns.turn);
    });
    const ordered = orderMoves(gs, legalCaptures);
    if (maximizing) {
        for (const move of ordered) {
            const newGs = (0, engine_1.makeMove)(gs, move);
            nodes[0]++;
            const score = quiesce(newGs, alpha, beta, false, rootColor, nodes);
            if (score >= beta)
                return beta;
            if (alpha < score)
                alpha = score;
        }
        return alpha;
    }
    else {
        for (const move of ordered) {
            const newGs = (0, engine_1.makeMove)(gs, move);
            nodes[0]++;
            const score = quiesce(newGs, alpha, beta, true, rootColor, nodes);
            if (score <= alpha)
                return alpha;
            if (beta > score)
                beta = score;
        }
        return beta;
    }
}
function findBestMove(gs, depth) {
    const moves = (0, engine_1.generateLegalMoves)(gs);
    if (moves.length === 0)
        return { score: 0, bestMove: null, nodesSearched: 0 };
    const nodes = [0];
    const ordered = orderMoves(gs, moves);
    const rootColor = gs.turn;
    let bestScore = -Infinity;
    let bestMove = ordered[0];
    for (const move of ordered) {
        const newGs = (0, engine_1.makeMove)(gs, move);
        const score = minimax(newGs, depth - 1, -Infinity, Infinity, false, rootColor, nodes);
        nodes[0]++;
        if (score > bestScore) {
            bestScore = score;
            bestMove = move;
        }
    }
    return { score: bestScore, bestMove, nodesSearched: nodes[0] };
}
};

// ── 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.playMoveSound = playMoveSound;
exports.playCaptureSound = playCaptureSound;
exports.playCheckSound = playCheckSound;
exports.playGameOverSound = playGameOverSound;
// Web Audio sound effects
let audioCtx = null;
let muted = false;
function initAudio() {
    if (!audioCtx) {
        audioCtx = new AudioContext();
    }
}
function setMuted(val) {
    muted = val;
}
function isMuted() {
    return muted;
}
function playTone(freq, duration, type = 'sine', volume = 0.15) {
    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 { }
}
function playMoveSound() {
    playTone(600, 0.08, 'sine', 0.12);
}
function playCaptureSound() {
    playTone(400, 0.12, 'square', 0.1);
    setTimeout(() => playTone(300, 0.08, 'square', 0.08), 60);
}
function playCheckSound() {
    playTone(800, 0.15, 'sine', 0.15);
    setTimeout(() => playTone(600, 0.15, 'sine', 0.12), 100);
}
function playGameOverSound() {
    playTone(523, 0.2, 'sine', 0.15);
    setTimeout(() => playTone(659, 0.2, 'sine', 0.15), 150);
    setTimeout(() => playTone(784, 0.3, 'sine', 0.15), 300);
}
};

// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGameState = getGameState;
exports.getLastMove = getLastMove;
exports.getGameOverState = getGameOverState;
exports.getFlipped = getFlipped;
exports.getSelectedSquare = getSelectedSquare;
exports.getLegalMovesForSelected = getLegalMovesForSelected;
exports.getAIDepth = getAIDepth;
exports.getAIColor = getAIColor;
exports.getPlayerColor = getPlayerColor;
exports.getPromotionPending = getPromotionPending;
exports.isAIThinking = isAIThinking;
exports.setOnRender = setOnRender;
exports.setOnGameOver = setOnGameOver;
exports.setOnAIStart = setOnAIStart;
exports.setOnAIDone = setOnAIDone;
exports.setOnSelectedChange = setOnSelectedChange;
exports.setAIDepth = setAIDepth;
exports.setFlipped = setFlipped;
exports.setAIColor = setAIColor;
exports.setPlayerColor = setPlayerColor;
exports.undo = undo;
exports.newGame = newGame;
exports.startGame = startGame;
exports.handleSquareClick = handleSquareClick;
exports.handlePromotionChoice = handlePromotionChoice;
exports.getMoveHistory = getMoveHistory;
exports.getCapturedPieces = getCapturedPieces;
exports.debug = debug;
const engine_1 = require("./engine");
const ai_1 = require("./ai");
const sound_1 = require("./sound");
const STORAGE_KEY = 'chess_game_state';
// --- Game Manager ---
let state;
let lastMove = null;
let gameOverState = null;
let aiColor = 'b';
let aiDepth = 3;
let playerColor = 'w';
let flipped = false;
let selectedSquare = null;
let legalMovesForSelected = [];
let promotionPending = null;
let aiThinking = false;
// Callbacks for UI
let onRender = null;
let onGameOver = null;
let onAIStart = null;
let onAIDone = null;
let onSelectedChange = null;
function saveState() {
    const data = {
        fen: (0, engine_1.stateToFen)(state),
        moveHistory: state.moveHistory,
        lastMove,
        gameOverState,
        aiColor,
        playerColor,
        aiDepth,
        flipped,
        capturedPieces: state.capturedPieces,
    };
    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}
function loadState() {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw)
        return false;
    try {
        const data = JSON.parse(raw);
        state = (0, engine_1.fenToState)(data.fen);
        state.moveHistory = data.moveHistory || [];
        state.capturedPieces = data.capturedPieces || { w: [], b: [] };
        lastMove = data.lastMove || null;
        gameOverState = data.gameOverState || null;
        aiColor = data.aiColor || 'b';
        playerColor = data.playerColor || 'w';
        aiDepth = data.aiDepth || 3;
        flipped = data.flipped || false;
        selectedSquare = null;
        legalMovesForSelected = [];
        promotionPending = null;
        return true;
    }
    catch {
        return false;
    }
}
function newState() {
    state = (0, engine_1.fenToState)(engine_1.START_FEN);
    lastMove = null;
    gameOverState = null;
    selectedSquare = null;
    legalMovesForSelected = [];
    promotionPending = null;
    aiThinking = false;
    saveState();
}
function getGameState() { return state; }
function getLastMove() { return lastMove; }
function getGameOverState() { return gameOverState; }
function getFlipped() { return flipped; }
function getSelectedSquare() { return selectedSquare; }
function getLegalMovesForSelected() { return legalMovesForSelected; }
function getAIDepth() { return aiDepth; }
function getAIColor() { return aiColor; }
function getPlayerColor() { return playerColor; }
function getPromotionPending() { return promotionPending; }
function isAIThinking() { return aiThinking; }
function setOnRender(cb) { onRender = cb; }
function setOnGameOver(cb) { onGameOver = cb; }
function setOnAIStart(cb) { onAIStart = cb; }
function setOnAIDone(cb) { onAIDone = cb; }
function setOnSelectedChange(cb) { onSelectedChange = cb; }
function setAIDepth(d) { aiDepth = d; }
function setFlipped(f) { flipped = f; }
function setAIColor(c) { aiColor = c; playerColor = c === 'w' ? 'b' : 'w'; }
function setPlayerColor(c) { playerColor = c; aiColor = c === 'w' ? 'b' : 'w'; }
function render() {
    if (onRender)
        onRender();
}
function selectSquare(sq) {
    selectedSquare = sq;
    legalMovesForSelected = (0, engine_1.generateLegalMoves)(state).filter(m => m.from === sq);
    if (onSelectedChange)
        onSelectedChange();
}
function deselect() {
    selectedSquare = null;
    legalMovesForSelected = [];
    if (onSelectedChange)
        onSelectedChange();
}
function playMove(move) {
    if (gameOverState || aiThinking)
        return;
    const piece = state.board[move.from];
    const isCapture = state.board[move.to] !== null || move.enPassant;
    const algebraic = (0, engine_1.moveToAlgebraic)(state, move);
    state = (0, engine_1.makeMove)(state, move);
    state.moveHistory.push(algebraic);
    lastMove = move;
    selectedSquare = null;
    legalMovesForSelected = [];
    // Sounds
    if (isCapture)
        (0, sound_1.playCaptureSound)();
    else
        (0, sound_1.playMoveSound)();
    const go = (0, engine_1.gameOver)(state);
    if (go) {
        gameOverState = go;
        (0, sound_1.playGameOverSound)();
        if (onGameOver)
            onGameOver(go);
        saveState();
        render();
        return;
    }
    // Check sound
    if ((0, engine_1.isCheck)(state.board, state.turn))
        (0, sound_1.playCheckSound)();
    saveState();
    render();
    // AI move if it's AI's turn
    if (state.turn === aiColor && !gameOverState) {
        aiMove();
    }
}
function aiMove() {
    if (gameOverState)
        return;
    aiThinking = true;
    if (onAIStart)
        onAIStart();
    render();
    // Use setTimeout to allow UI to update before blocking search
    setTimeout(() => {
        const result = (0, ai_1.findBestMove)(state, aiDepth);
        if (result.bestMove) {
            const piece = state.board[result.bestMove.from];
            const isCapture = state.board[result.bestMove.to] !== null || result.bestMove.enPassant;
            const algebraic = (0, engine_1.moveToAlgebraic)(state, result.bestMove);
            state = (0, engine_1.makeMove)(state, result.bestMove);
            state.moveHistory.push(algebraic);
            lastMove = result.bestMove;
            if (isCapture)
                (0, sound_1.playCaptureSound)();
            else
                (0, sound_1.playMoveSound)();
            const go = (0, engine_1.gameOver)(state);
            if (go) {
                gameOverState = go;
                (0, sound_1.playGameOverSound)();
                if (onGameOver)
                    onGameOver(go);
            }
            else if ((0, engine_1.isCheck)(state.board, state.turn)) {
                (0, sound_1.playCheckSound)();
            }
            saveState();
        }
        aiThinking = false;
        render();
        if (onAIDone)
            onAIDone();
    }, 50);
}
function undoMove() {
    // Take back both player and AI move (or just one if game ended)
    if (state.moveHistory.length < 2 && !gameOverState)
        return;
    const steps = (gameOverState && state.moveHistory.length >= 1) ? 1 : 2;
    // Rebuild from FEN at the target position
    let temp = (0, engine_1.fenToState)(engine_1.START_FEN);
    const targetLen = state.moveHistory.length - steps;
    const legalAll = () => (0, engine_1.generateLegalMoves)(temp);
    for (let i = 0; i < targetLen; i++) {
        // We need to replay moves from history, but we lost the exact moves...
        // Alternative: store full history of states
        // For now, re-derive by replaying from FEN is too complex.
        // Let's store snapshot of the state before each move.
    }
    // Better approach: replay from start using algebraic notation
    // Actually, we'll use a simpler approach: just reconstruct from FEN
    // by working backwards.
    // Simplest: re-create state from the saved FEN minus steps.
    // Let's store FEN snapshots instead.
}
// Undo using stored FEN snapshots
let fenHistory = [engine_1.START_FEN];
// Auto-initialize game state
let _initialized = false;
function _autoInit() {
    if (_initialized)
        return;
    _initialized = true;
    if (loadState()) {
        // Restored from save
    }
    else {
        newState();
    }
}
// Call immediately
_autoInit();
function recordFEN() {
    fenHistory.push((0, engine_1.stateToFen)(state));
}
function undo() {
    if (fenHistory.length <= 1)
        return;
    // Remove last 2 moves (player + AI), or 1 if game over
    const steps = (gameOverState && fenHistory.length >= 2) ? 1 : 2;
    const targetIdx = fenHistory.length - 1 - steps;
    if (targetIdx < 1)
        return;
    // Replay from start to target position
    const targetFen = fenHistory[targetIdx];
    const targetMoves = state.moveHistory.slice(0, fenHistory.length - steps - (gameOverState ? 0 : 0));
    state = (0, engine_1.fenToState)(targetFen);
    fenHistory = fenHistory.slice(0, targetIdx + 1);
    state.moveHistory = [];
    state.capturedPieces = { w: [], b: [] };
    gameOverState = null;
    lastMove = null;
    selectedSquare = null;
    legalMovesForSelected = [];
    // Replay moves to rebuild move history and captured pieces
    let replay = (0, engine_1.fenToState)(engine_1.START_FEN);
    // Actually we don't need to replay - the FEN captures full state
    // We just need move notation, which we lost. That's acceptable for now.
    saveState();
    render();
}
function newGame() {
    fenHistory = [engine_1.START_FEN];
    newState();
    render();
}
function startGame(playerWhite) {
    playerColor = playerWhite ? 'w' : 'b';
    aiColor = playerWhite ? 'b' : 'w';
    fenHistory = [engine_1.START_FEN];
    newState();
    render();
    // If player chose black, AI moves first
    if (state.turn === aiColor && !gameOverState) {
        setTimeout(() => aiMove(), 300);
    }
}
// For UI click
function handleSquareClick(sq) {
    if (gameOverState || aiThinking)
        return;
    if (state.turn !== playerColor)
        return;
    // If we have a selected square and the clicked square is a legal destination
    if (selectedSquare !== null) {
        const targetMove = legalMovesForSelected.find(m => m.to === sq);
        if (targetMove) {
            // Check if promotion
            if (targetMove.promo) {
                // Pick queen by default for non-prompted, or set promotion pending
                promotionPending = { from: targetMove.from, to: sq, color: state.turn };
                render();
                return;
            }
            playMove(targetMove);
            recordFEN();
            return;
        }
    }
    // Select a piece if it belongs to current player
    const piece = state.board[sq];
    if (piece && piece.color === state.turn) {
        selectSquare(sq);
    }
    else {
        deselect();
    }
}
function handlePromotionChoice(pieceType) {
    if (!promotionPending)
        return;
    const pp = promotionPending;
    const move = legalMovesForSelected.find(m => m.to === pp.to && m.promo === pieceType);
    if (move) {
        playMove(move);
        recordFEN();
    }
    promotionPending = null;
    render();
}
function getMoveHistory() { return state.moveHistory; }
function getCapturedPieces() {
    return {
        w: state.capturedPieces.w,
        b: state.capturedPieces.b,
    };
}
// Debug interface
function debug() {
    const _debug = {
        fen: () => (0, engine_1.stateToFen)(state),
        legalMoves: () => (0, engine_1.generateLegalMoves)(state).map(m => ({
            from: m.from, to: m.to, promo: m.promo,
            enPassant: m.enPassant, castling: m.castling
        })),
        gameOver: () => (0, engine_1.gameOver)(state),
        // Allow executing arbitrary moves for testing
        move: (fromStr, toStr) => {
            const from = (0, engine_1.parseSquare)(fromStr);
            const to = (0, engine_1.parseSquare)(toStr);
            if (from === null || to === null)
                return { ok: false, error: 'bad squares' };
            const legal = (0, engine_1.generateLegalMoves)(state);
            const move = legal.find(m => m.from === from && m.to === to);
            if (!move)
                return { ok: false, error: 'illegal move' };
            const alg = (0, engine_1.moveToAlgebraic)(state, move);
            state = (0, engine_1.makeMove)(state, move);
            state.moveHistory.push(alg);
            lastMove = move;
            return { ok: true, fen: (0, engine_1.stateToFen)(state), notation: alg };
        },
    };
    return _debug;
}
};

// ── 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;
const engine_1 = require("./engine");
const game_1 = require("./game");
const boardEl = document.getElementById('board');
const moveListEl = document.getElementById('move-list');
const turnIndicator = document.getElementById('turn-indicator');
const gameOverBanner = document.getElementById('game-over-banner');
const capturedWhiteEl = document.getElementById('captured-white');
const capturedBlackEl = document.getElementById('captured-black');
const promotionOverlay = document.getElementById('promotion-overlay');
const promotionChoices = document.getElementById('promotion-choices');
let squares = [];
let dragState = null;
function buildBoard() {
    boardEl.innerHTML = '';
    squares = [];
    for (let r = 0; r < 8; r++) {
        for (let c = 0; c < 8; c++) {
            const sqEl = document.createElement('div');
            sqEl.className = 'square ' + ((r + c) % 2 === 0 ? 'square-light' : 'square-dark');
            const idx = r * 8 + c;
            sqEl.dataset.square = String(idx);
            squares[idx] = sqEl;
            boardEl.appendChild(sqEl);
            if (c === 0) {
                const rankLabel = document.createElement('span');
                rankLabel.textContent = String(8 - r);
                rankLabel.style.cssText = 'position:absolute;top:2px;left:3px;font-size:10px;pointer-events:none;';
                sqEl.appendChild(rankLabel);
            }
            if (r === 7) {
                const fileLabel = document.createElement('span');
                fileLabel.textContent = 'abcdefgh'[c];
                fileLabel.style.cssText = 'position:absolute;bottom:1px;right:3px;font-size:10px;pointer-events:none;';
                sqEl.appendChild(fileLabel);
            }
        }
    }
}
function renderBoard() {
    const gs = (0, game_1.getGameState)();
    const flipped = (0, game_1.getFlipped)();
    const selected = (0, game_1.getSelectedSquare)();
    const legalMoves = (0, game_1.getLegalMovesForSelected)();
    const lastMove = (0, game_1.getLastMove)();
    const goState = (0, game_1.getGameOverState)();
    const pp = (0, game_1.getPromotionPending)();
    boardEl.style.transform = flipped ? 'rotate(180deg)' : '';
    let kingSq = null;
    for (let i = 0; i < 64; i++) {
        const p = gs.board[i];
        if (p && p.type === 'K' && p.color === gs.turn) {
            kingSq = i;
            break;
        }
    }
    const legalDestSet = new Set(legalMoves.map(m => m.to));
    const legalCaptureSet = new Set(legalMoves.filter(m => gs.board[m.to] !== null || m.enPassant).map(m => m.to));
    for (let i = 0; i < 64; i++) {
        const sq = squares[i];
        if (!sq)
            continue;
        sq.className = 'square ' + (((0, engine_1.sqRank)(i) + (0, engine_1.sqFile)(i)) % 2 === 0 ? 'square-light' : 'square-dark');
        // Remove piece/indicator elements but keep coord labels
        while (sq.lastChild) {
            const lc = sq.lastChild;
            if (lc.classList && lc.classList.contains('coord-label'))
                break;
            sq.removeChild(lc);
        }
        if (lastMove && (i === lastMove.from || i === lastMove.to))
            sq.classList.add('square-last-move');
        if (selected === i)
            sq.classList.add('square-selected');
        const piece = gs.board[i];
        let pieceEl = sq.querySelector('.piece');
        if (piece) {
            if (!pieceEl) {
                pieceEl = document.createElement('span');
                pieceEl.className = 'piece';
                sq.appendChild(pieceEl);
            }
            const pe = pieceEl;
            pe.textContent = engine_1.PIECE_SYMBOLS[piece.color][piece.type];
            pe.style.color = piece.color === 'w' ? '#fff' : '#222';
            pe.style.filter = piece.color === 'w' ? 'drop-shadow(0 0 1px rgba(0,0,0,0.8))' : '';
        }
        else {
            if (pieceEl)
                pieceEl.remove();
        }
        if (legalDestSet.has(i)) {
            if (legalCaptureSet.has(i)) {
                const ring = document.createElement('div');
                ring.className = 'capture-ring';
                sq.appendChild(ring);
            }
            else {
                const dot = document.createElement('div');
                dot.className = 'move-dot';
                sq.appendChild(dot);
            }
        }
        if (i === kingSq) {
            const checkFn = window.__chessCheck;
            if (checkFn && checkFn(gs))
                sq.classList.add('square-check');
        }
    }
    if (pp) {
        renderPromotionOverlay(pp);
    }
    else {
        promotionOverlay.classList.add('hidden');
    }
    if (goState) {
        turnIndicator.textContent = 'Game Over';
        turnIndicator.style.background = '#666';
        turnIndicator.style.color = '#ccc';
    }
    else {
        turnIndicator.textContent = gs.turn === 'w' ? '⬜ White to move' : '⬛ Black to move';
        turnIndicator.style.background = gs.turn === 'w' ? '#f0d9b5' : '#5c3d2e';
        turnIndicator.style.color = gs.turn === 'w' ? '#333' : '#fff';
    }
    if (goState) {
        gameOverBanner.classList.remove('hidden');
        const labels = {
            checkmate: gs.turn === 'w' ? '♚ Black wins by checkmate!' : '♔ White wins by checkmate!',
            stalemate: '½ Draw by stalemate',
            insufficient_material: '½ Draw — insufficient material',
            '50_move_rule': '½ Draw — 50-move rule',
        };
        gameOverBanner.textContent = labels[goState] || 'Game Over';
    }
    else {
        gameOverBanner.classList.add('hidden');
    }
    let thinkEl = document.getElementById('ai-thinking');
    if ((0, game_1.isAIThinking)()) {
        if (!thinkEl) {
            thinkEl = document.createElement('div');
            thinkEl.id = 'ai-thinking';
            turnIndicator.parentNode.appendChild(thinkEl);
        }
        thinkEl.textContent = '🤔 AI thinking...';
    }
    else if (thinkEl) {
        thinkEl.remove();
    }
    renderMoveList();
    renderCaptured();
}
function renderMoveList() {
    const history = (0, game_1.getMoveHistory)();
    moveListEl.innerHTML = '';
    for (let i = 0; i < history.length; i += 2) {
        const entry = document.createElement('div');
        entry.className = 'move-entry';
        const num = document.createElement('span');
        num.className = 'move-number';
        num.textContent = (i / 2 + 1) + '.';
        entry.appendChild(num);
        const white = document.createElement('span');
        white.className = 'move-white';
        white.textContent = history[i] || '';
        entry.appendChild(white);
        if (history[i + 1]) {
            const black = document.createElement('span');
            black.className = 'move-black';
            black.textContent = history[i + 1];
            entry.appendChild(black);
        }
        moveListEl.appendChild(entry);
    }
    moveListEl.scrollTop = moveListEl.scrollHeight;
}
function renderCaptured() {
    const captured = (0, game_1.getCapturedPieces)();
    const pieceOrder = ['Q', 'R', 'B', 'N', 'P'];
    const sortPieces = (arr) => arr.sort((a, b) => pieceOrder.indexOf(a) - pieceOrder.indexOf(b));
    capturedWhiteEl.textContent = sortPieces(captured.w).map(t => engine_1.PIECE_SYMBOLS['b'][t]).join(' ');
    capturedBlackEl.textContent = sortPieces(captured.b).map(t => engine_1.PIECE_SYMBOLS['w'][t]).join(' ');
}
function renderPromotionOverlay(pp) {
    promotionChoices.innerHTML = '';
    const pieces = ['Q', 'R', 'B', 'N'];
    for (const pt of pieces) {
        const btn = document.createElement('div');
        btn.className = 'promo-piece';
        btn.textContent = engine_1.PIECE_SYMBOLS[pp.color][pt];
        btn.style.color = pp.color === 'w' ? '#fff' : '#222';
        btn.addEventListener('click', () => {
            const handler = window.__handlePromotion;
            if (handler)
                handler(pt);
        });
        promotionChoices.appendChild(btn);
    }
    promotionOverlay.classList.remove('hidden');
}
function setupDragDrop() {
    for (let i = 0; i < 64; i++) {
        const sq = squares[i];
        sq.addEventListener('mousedown', (e) => {
            if (e.button !== 0)
                return;
            const gs = (0, game_1.getGameState)();
            const piece = gs.board[i];
            if (!piece || piece.color !== gs.turn || (0, game_1.getGameOverState)() || (0, game_1.isAIThinking)())
                return;
            const ghost = document.createElement('div');
            ghost.textContent = engine_1.PIECE_SYMBOLS[piece.color][piece.type];
            ghost.style.cssText = 'position:fixed;font-size:46px;pointer-events:none;z-index:1000;'
                + 'width:72px;height:72px;display:flex;justify-content:center;align-items:center;'
                + 'text-shadow:0 2px 6px rgba(0,0,0,0.5);'
                + 'color:' + (piece.color === 'w' ? '#fff' : '#222') + ';';
            if (piece.color === 'w')
                ghost.style.filter = 'drop-shadow(0 0 2px rgba(0,0,0,0.9))';
            document.body.appendChild(ghost);
            dragState = { from: i, ghost };
            const pieceEl = sq.querySelector('.piece');
            if (pieceEl)
                pieceEl.style.opacity = '0.3';
            const sel = window.__handleSelect;
            if (sel)
                sel(i);
            const rect = sq.getBoundingClientRect();
            ghost.style.left = (rect.left + 12) + 'px';
            ghost.style.top = (rect.top + 12) + 'px';
            e.preventDefault();
        });
    }
    document.addEventListener('mousemove', (e) => {
        if (!dragState)
            return;
        dragState.ghost.style.left = (e.clientX - 36) + 'px';
        dragState.ghost.style.top = (e.clientY - 36) + 'px';
    });
    document.addEventListener('mouseup', (e) => {
        if (!dragState)
            return;
        if (dragState.ghost.parentNode)
            dragState.ghost.parentNode.removeChild(dragState.ghost);
        const fromSq = squares[dragState.from];
        const origPiece = fromSq?.querySelector('.piece');
        if (origPiece)
            origPiece.style.opacity = '';
        const target = document.elementFromPoint(e.clientX, e.clientY);
        if (target) {
            const sqEl = target.closest('.square');
            if (sqEl) {
                const toSq = parseInt(sqEl.dataset.square || '');
                if (!isNaN(toSq) && toSq !== dragState.from) {
                    const handler = window.__handleMove;
                    if (handler)
                        handler(dragState.from, toSq);
                }
            }
        }
        dragState = null;
    });
}
function initRenderer() {
    buildBoard();
    setupDragDrop();
    renderBoard();
}
function render() {
    renderBoard();
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"ThinkingCap-Qwen3.6-27B-GGUF","provider":"brigid","persona":"typescript","sessionId":"1ceb0d06-562d-4e07-8334-b0bf961c2bd2","tokensIn":2668416,"tokensOut":43237,"tokensTotal":2711653,"cost":0,"turns":68,"toolCalls":67,"failedToolCalls":6,"timestamp":"2026-08-03T19:05:29.671Z"} -->