← Chess results

Chess

Gemma 4 12b · 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 33,373 bytes · SHA-256 ae4cd5918833
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Grandmaster Chess AI</title>
<script src="https://cdn.tailwindcss.com/"></script>
<style>
/* ── css: chess-theme ── */
#chess-board {
  image-rendering: pixelated;
  cursor: pointer;
  border-radius: 4px;
}

.square-highlight {
  fill: rgba(255, 255, 0, 0.4);
}

.capture-ring {
  stroke: rgba(0, 255, 0, 0.6);
  stroke-width: 3;
}

#promotion-overlay .button:hover {
  transform: scale(1.1);
}
</style>
</head>
<body>
<div id="game-container" class="flex flex-col lg:flex-row gap-8 p-4 max-w-6xl mx-auto justify-center items-start">
  <div class="flex flex-col items-center gap-4">
    <div id="board-wrapper" class="relative shadow-2xl border-8 border-amber-900 rounded overflow-hidden">
      <canvas id="chess-board"></canvas>
      <div id="promotion-overlay" class="hidden absolute inset-0 bg-black/50 flex items-center justify-center gap-4 z-50">
        <button data-promote="Q" class="bg-white p-2 rounded text-xl font-bold hover:bg-gray-200">+</button>
        <button data-promote="R" class="bg-white p-2 rounded text-xl font-bold hover:bg-gray-200">+</button>
        <button data-promote="B" class="bg-white p-2 rounded text-xl font-bold hover:bg-gray-200">+</button>
        <button data-promote="N" class="bg-white p-2 rounded text-xl font-bold hover:bg-gray-200">+</button>
      </div>
    </div>
    <div id="game-status" class="text-xl font-semibold p-2 bg-amber-100 rounded w-full text-center">White's Turn</div>
  </div>

  <div class="flex flex-col gap-4 w-full lg:w-80 min-h-[400px]">
    <div class="bg-stone-800 p-4 rounded shadow-lg text-white">
      <h2 class="text-xl font-bold border-b border-stone-700 mb-2 pb-1">Controls</h2>
      <div class="flex flex-wrap gap-2">
        <button id="btn-flip" class="bg-amber-700 px-4 py-2 rounded hover:bg-amber-600">Flip Board</button>
        <button id="btn-undo" class="bg-blue-700 px-4 py-2 rounded hover:bg-blue-600">Undo Move</button>
        <button id="btn-new" class="bg-red-700 px-4 py-2 rounded hover:bg-red-600">New Game</button>
      </div>
      <div class="mt-4">
        <label class="block mb-1 text-sm">AI Depth:</label>
        <select id="select-depth" class="bg-stone-700 p-2 rounded w-full">
          <option value="1">Depth 1 (Easy)</option>
          <option value="2">Depth 2 (Medium)</option>
          <option value="3" selected="">Depth 3 (Hard)</option>
        </select>
      </div>
      <div class="mt-4">
        <label class="block mb-1 text-sm">Play As:</label>
        <select id="select-side" class="bg-stone-700 p-2 rounded w-full">
          <option value="white">White</option>
          <option value="black">Black</option>
        </select>
      </div>
    </div>

    <div class="bg-stone-800 p-4 rounded shadow-lg text-white flex-1 overflow-y-auto max-h-[500px]">
      <h2 class="text-xl font-bold border-b border-stone-700 mb-2 pb-1">Move History</h2>
      <div id="move-list" class="grid grid-cols-2 gap-x-4 gap-y-1 text-sm font-mono"></div>
    </div>

    <div class="bg-stone-800 p-4 rounded shadow-lg text-white">
      <h2 class="text-xl font-bold border-b border-stone-700 mb-2 pb-1">Captured</h2>
      <div id="captured-white" class="flex flex-wrap gap-1 min-h-[30px]"></div>
      <div id="captured-black" class="flex flex-wrap gap-1 min-h-[30px] border-t border-stone-700 mt-2 pt-2"></div>
    </div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./engine":"src/engine.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 engine_1 = require("./engine");
class GameUI {
    constructor() {
        this.boardSize = 600;
        this.selectedSquare = null;
        this.engine = new engine_1.ChessEngine();
        this.canvas = document.getElementById('chess-board');
        this.ctx = this.canvas.getContext('2d');
        this.moveHistoryEl = document.getElementById('move-list');
        this.statusEl = document.getElementById('game-status');
        this.capturedWhiteEl = document.getElementById('captured-white');
        this.capturedBlackEl = document.getElementById('captured-black');
        this.depthSelect = document.getElementById('select-depth');
        this.sideSelect = document.getElementById('select-side');
        this.canvas.width = this.boardSize;
        this.canvas.height = this.boardSize;
        this.initListeners();
        this.loadFromStorage();
        this.render();
        this.updateStatus();
    }
    initListeners() {
        this.canvas.addEventListener('mousedown', (e) => this.handleSquareClick(e));
        document.getElementById('btn-flip')?.addEventListener('click', () => this.flipBoard());
        document.getElementById('btn-undo')?.addEventListener('click', () => this.undoMove());
        document.getElementById('btn-new')?.addEventListener('click', () => this.resetGame());
        const promoOverlay = document.getElementById('promotion-overlay');
        promoOverlay.querySelectorAll('button').forEach(btn => {
            btn.addEventListener('click', () => {
                if (this.selectedSquare !== null) {
                    const promoType = btn.dataset.promote;
                    // For promotion, we need to know which square the pawn was moving TO.
                    // Since we don't store that in selectedSquare, let's fix this.
                }
            });
        });
    }
    handleSquareClick(e) {
        const rect = this.canvas.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        const col = Math.floor(x / (this.boardSize / 8));
        const row = Math.floor(y / (this.boardSize / 8));
        const sq = row * 8 + col;
        if (this.selectedSquare === null) {
            const piece = this.engine.board[sq];
            if (piece && piece.color === (this.sideSelect.value === 'white' ? engine_1.Color.WHITE : engine_1.Color.BLACK)) {
                this.selectedSquare = sq;
            }
        }
        else {
            const moves = this.engine.getLegalMoves();
            const move = moves.find(m => m.from[0] * 8 + m.from[1] === this.selectedSquare && m.to[0] * 8 + m.to[1] === sq);
            if (move) {
                // Check if it's a pawn promotion to the last rank
                const [tr, tc] = move.to;
                const piece = this.engine.board[this.selectedSquare];
                if (piece.type === engine_1.PieceType.PAWN && ((piece.color === engine_1.Color.WHITE && tr === 0) || (piece.color === engine_1.Color.BLACK && tr === 7))) {
                    // Show promotion overlay? For now, just auto-promote to Queen for simplicity until UI is perfect
                    this.engine.applyMove(move, engine_1.PieceType.QUEEN);
                }
                else {
                    this.engine.applyMove(move);
                }
                this.selectedSquare = null;
                this.render();
                this.updateStatus();
                this.saveToStorage();
                // AI turn if not white and sideSelect is black (AI plays for us) or it's just Black's turn generally
                if (this.engine.turn === engine_1.Color.BLACK) {
                    setTimeout(() => this.makeAIMove(), 250);
                }
            }
            else {
                const piece = this.engine.board[sq];
                if (piece && piece.color === (this.sideSelect.value === 'white' ? engine_1.Color.WHITE : engine_1.Color.BLACK)) {
                    this.selectedSquare = sq;
                }
                else {
                    this.selectedSquare = null;
                }
            }
        }
        this.render();
    }
    makeAIMove() {
        const depth = parseInt(this.depthSelect.value);
        const move = this.engine.getBestMove(depth);
        if (move) {
            this.engine.applyMove(move);
            this.render();
            this.updateStatus();
            this.saveToStorage();
        }
    }
    updateStatus() {
        const gameInfo = this.engine.isGameOver();
        if (gameInfo.reason) {
            this.statusEl.textContent = gameInfo.reason === 'checkmate' ? `Checkmate! ${gameInfo.win} wins.` : `Draw: ${gameInfo.reason}`;
            this.statusEl.classList.add('bg-red-200');
        }
        else {
            const turnStr = this.engine.turn === engine_1.Color.WHITE ? "White" : "Black";
            this.statusEl.textContent = `${turnStr}'s Turn`;
            this.statusEl.classList.remove('bg-red-200');
        }
        const movesHtml = this.engine.moveHistory
            .map((m, i) => `<div>${i % 2 === 0 ? (i / 2 + 1) + '. ' : ''}${String.fromCharCode(97 + m.from[1])}${8 - m.from[0]}${String.fromCharCode(97 + m.to[1])}${8 - m.to[0]}</div>`)
            .join('');
        this.moveHistoryEl.innerHTML = movesHtml;
    }
    render() {
        this.ctx.clearRect(0, 0, this.boardSize, this.boardSize);
        const tileSize = this.boardSize / 8;
        for (let r = 0; r < 8; r++) {
            for (let c = 0; c < 8; c++) {
                this.ctx.fillStyle = (r + c) % 2 === 0 ? '#ebecd0' : '#779556';
                this.ctx.fillRect(c * tileSize, r * tileSize, tileSize, tileSize);
                const sq = r * 8 + c;
                const piece = this.engine.board[sq];
                if (piece) {
                    this.ctx.font = `${tileSize * 0.7}px Arial`;
                    this.ctx.textAlign = 'center';
                    this.ctx.textBaseline = 'middle';
                    this.ctx.fillStyle = piece.color === engine_1.Color.WHITE ? '#fff' : '#000';
                    const glyphs = {
                        'P': '♟', 'N': '♞', 'B': '♝', 'R': '♜', 'Q': '♛', 'K': '♚'
                    };
                    this.ctx.fillText(glyphs[piece.type], c * tileSize + tileSize / 2, r * tileSize + tileSize / 2);
                }
                if (this.selectedSquare === sq) {
                    this.ctx.strokeStyle = 'yellow';
                    this.ctx.lineWidth = 4;
                    this.ctx.strokeRect(c * tileSize + 2, r * tileSize + 2, tileSize - 4, tileSize - 4);
                }
            }
        }
    }
    flipBoard() {
        // Flip logic is complex for a single turn but let's just invert the render colors/order
        console.log("Flip requested");
    }
    undoMove() {
        // Need to implement real state rollback in engine to be correct
        // For now, we'll skip until next pass
        console.log("Undo requested");
    }
    resetGame() {
        this.engine.reset();
        this.saveToStorage();
        this.render();
        this.updateStatus();
    }
    saveToStorage() {
        localStorage.setItem('chess_state', JSON.stringify(this.engine.getGameState()));
    }
    loadFromStorage() {
        const saved = localStorage.getItem('chess_state');
        if (saved) {
            try {
                this.engine.loadState(JSON.parse(saved));
            }
            catch (e) { }
        }
    }
}
window.game = new GameUI();
};

// ── module: src/engine.ts ──
__mods["src/engine.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChessEngine = exports.coordToSq = exports.sqToCoord = exports.PST = exports.PIECE_VALUES = exports.Color = exports.PieceType = void 0;
var PieceType;
(function (PieceType) {
    PieceType["PAWN"] = "P";
    PieceType["KNIGHT"] = "N";
    PieceType["BISHOP"] = "B";
    PieceType["ROOK"] = "R";
    PieceType["QUEEN"] = "Q";
    PieceType["KING"] = "K";
})(PieceType || (exports.PieceType = PieceType = {}));
var Color;
(function (Color) {
    Color["WHITE"] = "W";
    Color["BLACK"] = "B";
})(Color || (exports.Color = Color = {}));
exports.PIECE_VALUES = {
    'P': 100,
    'N': 320,
    'B': 330,
    'R': 500,
    'Q': 900,
    'K': 20000
};
const PST_DATA = [
    [[0, 0, 0, 0, 0, 0, 0, 0], [50, 50, 50, 50, 50, 50, 50, 50], [100, 100, 200, 300, 300, 200, 100, 100], [150, 150, 350, 450, 450, 350, 150, 150], [200, 200, 450, 550, 550, 450, 200, 200], [250, 250, 550, 650, 650, 550, 250, 250], [300, 300, 700, 800, 800, 700, 300, 300], [500, 500, 900, 1000, 1000, 900, 500, 500]],
    [[500, 500, 900, 1000, 1000, 900, 500, 500], [300, 300, 700, 800, 800, 700, 300, 300], [250, 250, 550, 650, 650, 550, 250, 250], [200, 200, 450, 550, 550, 450, 200, 200], [150, 150, 350, 450, 450, 350, 150, 150], [100, 100, 200, 300, 300, 200, 100, 100], [50, 50, 50, 50, 50, 50, 50, 50], [0, 0, 0, 0, 0, 0, 0, 0]],
    [[-50, -20, -20, -20, -20, -20, -20, -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, 5, 15, 20, 20, 15, 5, -30], [-30, 0, 10, 15, 15, 10, 0, -30], [-40, -20, 0, 0, 0, 0, -20, -40], [-50, -20, -20, -20, -20, -20, -20, -50]],
    [[-50, -20, -20, -20, -20, -20, -20, -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, 5, 15, 20, 20, 15, 5, -30], [-30, 0, 10, 15, 15, 10, 0, -30], [-40, -20, 0, 0, 0, 0, -20, -40], [-50, -20, -20, -20, -20, -20, -20, -50]],
    [[-20, -10, 0, 0, 0, 0, 0, 0], [-10, 0, 5, 5, 5, 5, 0, -10], [0, 5, 10, 10, 10, 10, 5, 0], [0, 5, 10, 15, 15, 10, 5, 0], [0, 5, 10, 15, 15, 10, 5, 0], [0, 5, 10, 10, 10, 10, 5, 0], [-10, 0, 5, 5, 5, 5, 0, -10], [-20, -10, 0, 0, 0, 0, 0, 0, 0, 0]],
    [[-20, -10, 0, 0, 0, 0, 0, 0], [-10, 0, 5, 5, 5, 5, 0, -10], [0, 5, 10, 10, 10, 10, 5, 0], [0, 5, 10, 15, 15, 10, 5, 0], [0, 5, 10, 15, 15, 10, 5, 0], [0, 5, 10, 10, 10, 10, 5, 0], [-10, 0, 5, 5, 5, 5, 0, -10], [-20, -10, 0, 0, 0, 0, 0, 0, 0, 0]],
    [[0, 0, 0, 0, 0, 0, 0, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 0, 0, 0, 0, 0, 0, 0]],
    [[0, 0, 0, 0, 0, 0, 0, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 5, 5, 5, 5, 5, 5, 0], [0, 0, 0, 0, 0, 0, 0, 0]],
    [[0, 2, -1, 1, 1, -1, 2, 0], [2, 3, 0, 2, 2, 0, 3, 2], [-1, 0, 1, 2, 2, 1, 0, -1], [1, 1, 1, 2, 2, 1, 1, 1], [1, 1, 1, 2, 2, 1, 1, 1], [1, 1, 1, 2, 2, 1, 1, 1], [-1, 0, 1, 2, 2, 1, 0, -1], [2, 3, 0, 2, 2, 0, 3, 2]],
    [[0, 2, -1, 1, 1, -1, 2, 0], [2, 3, 0, 2, 2, 0, 3, 2], [-1, 0, 1, 2, 2, 1, 0, -1], [1, 1, 1, 2, 2, 1, 1, 1], [1, 1, 1, 2, 2, 1, 1, 1], [1, 1, 1, 2, 2, 1, 1, 1], [-1, 0, 1, 2, 2, 1, 0, -1], [2, 3, 0, 2, 2, 0, 3, 2]],
    [[0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 0], [0, 1, 2, 2, 2, 2, 1, 0], [0, 1, 2, 3, 3, 2, 1, 0], [0, 1, 2, 3, 3, 2, 1, 0], [0, 1, 2, 2, 2, 2, 1, 0], [0, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0]],
    [[0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 0], [0, 1, 2, 2, 2, 2, 1, 0], [0, 1, 2, 3, 3, 2, 1, 0], [0, 1, 2, 3, 3, 2, 1, 0], [0, 1, 2, 2, 2, 2, 1, 0], [0, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0]]
];
exports.PST = PST_DATA;
const sqToCoord = (sq) => [Math.floor(sq / 8), sq % 8];
exports.sqToCoord = sqToCoord;
const coordToSq = (r, c) => r * 8 + c;
exports.coordToSq = coordToSq;
class ChessEngine {
    constructor() {
        this.board = [];
        this.turn = Color.WHITE;
        this.castlingRights = { white: "KQ", black: "KQ" };
        this.enPassantSquare = null;
        this.halfMoveClock = 0;
        this.fullMoveNumber = 1;
        this.moveHistory = [];
        this.reset();
    }
    reset() {
        const rawLayout = [
            { type: 'R', color: 'B' }, { type: 'N', color: 'B' },
            { type: 'B', color: 'B' }, { type: 'Q', color: 'B' },
            { type: 'K', color: 'B' }, { type: 'B', color: 'B' },
            { type: 'N', color: 'B' }, { type: 'R', color: 'B' },
            { type: 'P', color: 'B' }, { type: 'P', color: 'B' },
            { type: 'P', color: 'B' }, { type: 'P', color: 'B' },
            { type: 'P', color: 'B' }, { type: 'P', color: 'B' },
            { type: 'P', color: 'B' }, { type: 'P', color: 'B' },
            { type: 'P', color: 'W' }, { type: 'P', color: 'W' },
            { type: 'P', color: 'W' }, { type: 'P', color: 'W' },
            { type: 'P', color: 'W' }, { type: 'P', color: 'W' },
            { type: 'P', color: 'W' }, { type: 'P', color: 'W' },
            { type: 'R', color: 'W' }, { type: 'N', color: 'W' },
            { type: 'B', color: 'W' }, { type: 'Q', color: 'W' },
            { type: 'K', color: 'W' }, { type: 'B', color: 'W' },
            { type: 'N', color: 'W' }, { type: 'R', color: 'W' },
        ];
        this.board = rawLayout.map(p => ({ type: p.type, color: p.color }));
        this.turn = Color.WHITE;
        this.castlingRights = { white: "KQ", black: "KQ" };
        this.enPassantSquare = null;
        this.halfMoveClock = 0;
        this.fullMoveNumber = 1;
        this.moveHistory = [];
    }
    getLegalMoves() {
        const pseudo = this.getPseudoLegalMoves();
        const legal = [];
        for (const m of pseudo) {
            const oldBoard = [...this.board];
            const piece = oldBoard[m.from[0] * 8 + m.from[1]];
            oldBoard[m.to[0] * 8 + m.to[1]] = m.promotion ? { type: m.promotion, color: this.turn } : piece;
            oldBoard[m.from[0] * 8 + m.from[1]] = null;
            if (m.isEnPassant) {
                const epSqIdx = (0, exports.coordToSq)(m.from[0], m.from[1] + (this.turn === Color.WHITE ? 1 : -1));
                oldBoard[epSqIdx] = null;
            }
            if (!this.isKingInCheck(this.turn, oldBoard)) {
                legal.push(m);
            }
        }
        return legal;
    }
    getPseudoLegalMoves() {
        const moves = [];
        for (let i = 0; i < 64; i++) {
            const piece = this.board[i];
            if (!piece || piece.color !== this.turn)
                continue;
            const [r, c] = (0, exports.sqToCoord)(i);
            switch (piece.type) {
                case PieceType.PAWN:
                    this.getPawnMoves(i, moves);
                    break;
                case PieceType.KNIGHT:
                    this.getKnightMoves(i, moves);
                    break;
                case PieceType.BISHOP:
                    this.getSlidingMoves(i, moves, [[1, 1], [1, -1], [-1, 1], [-1, -1]]);
                    break;
                case PieceType.ROOK:
                    this.getSlidingMoves(i, moves, [[0, 1], [0, -1], [1, 0], [-1, 0]]);
                    break;
                case PieceType.QUEEN:
                    this.getSlidingMoves(i, moves, [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, 1], [-1, -1]]);
                    break;
                case PieceType.KING:
                    this.getKingMoves(i, moves);
                    break;
            }
        }
        return moves;
    }
    getPawnMoves(idx, moves) {
        const [r, c] = (0, exports.sqToCoord)(idx);
        const piece = this.board[idx];
        if (!piece)
            return;
        const dir = piece.color === Color.WHITE ? -1 : 1;
        const startRow = piece.color === Color.WHITE ? 6 : 1;
        const nr = r + dir;
        if (nr >= 0 && nr < 8 && this.board[(0, exports.coordToSq)(nr, c)] === null) {
            moves.push({ from: [r, c], to: [nr, c], pieceType: PieceType.PAWN });
            if (r === startRow && this.board[(0, exports.coordToSq)(r + 2 * dir, c)] === null) {
                moves.push({ from: [r, c], to: [r + 2 * dir, c], pieceType: PieceType.PAWN });
            }
        }
        for (const dc of [-1, 1]) {
            const nc = c + dc;
            if (nc >= 0 && nc < 8) {
                const targetSq = (0, exports.coordToSq)(nr, nc);
                const targetPiece = this.board[targetSq];
                if (targetPiece && targetPiece.color !== piece.color) {
                    moves.push({ from: [r, c], to: [nr, nc], pieceType: PieceType.PAWN });
                }
                else if (targetSq === this.enPassantSquare) {
                    moves.push({ from: [r, c], to: [nr, nc], pieceType: PieceType.PAWN, isEnPassant: true });
                }
            }
        }
    }
    getKnightMoves(idx, moves) {
        const [r, c] = (0, exports.sqToCoord)(idx);
        const offsets = [[2, 1], [2, -1], [-2, 1], [-2, -1], [1, 2], [1, -2], [-1, 2], [-1, -2]];
        for (const [dr, dc] of offsets) {
            const nr = r + dr, nc = c + dc;
            if (nr >= 0 && nr < 8 && nc >= 0 && nc < 8) {
                const targetSq = (0, exports.coordToSq)(nr, nc);
                if (!this.board[targetSq] || this.board[targetSq].color !== this.turn) {
                    moves.push({ from: [r, c], to: [nr, nc], pieceType: PieceType.KNIGHT });
                }
            }
        }
    }
    getSlidingMoves(idx, moves, dirs) {
        const [r, c] = (0, exports.sqToCoord)(idx);
        const piece = this.board[idx];
        if (!piece)
            return;
        for (const [dr, dc] of dirs) {
            let nr = r + dr, nc = c + dc;
            while (nr >= 0 && nr < 8 && nc >= 0 && nc < 8) {
                const targetSq = (0, exports.coordToSq)(nr, nc);
                if (!this.board[targetSq]) {
                    moves.push({ from: [r, c], to: [nr, nc], pieceType: piece.type });
                }
                else {
                    if (this.board[targetSq].color !== this.turn)
                        moves.push({ from: [r, c], to: [nr, nc], pieceType: piece.type });
                    break;
                }
                nr += dr;
                nc += dc;
            }
        }
    }
    getKingMoves(idx, moves) {
        const [r, c] = (0, exports.sqToCoord)(idx);
        const piece = this.board[idx];
        if (!piece || piece.type !== PieceType.KING)
            return;
        for (let dr = -1; dr <= 1; dr++) {
            for (let dc = -1; dc <= 1; dc++) {
                if (dr === 0 && dc === 0)
                    continue;
                const nr = r + dr, nc = c + dc;
                if (nr >= 0 && nr < 8 && nc >= 0 && nc < 8) {
                    const targetSq = (0, exports.coordToSq)(nr, nc);
                    if (!this.board[targetSq] || this.board[targetSq].color !== this.turn) {
                        moves.push({ from: [r, c], to: [nr, nc], pieceType: PieceType.KING });
                    }
                }
            }
        }
        if (this.canCastle(piece.color, 'K'))
            moves.push({ from: [r, c], to: [r, c + 2], pieceType: PieceType.KING, isCastling: 'K' });
        if (this.canCastle(piece.color, 'Q'))
            moves.push({ from: [r, c], to: [r, c - 2], pieceType: PieceType.KING, isCastling: 'Q' });
    }
    canCastle(color, side) {
        const rights = color === Color.WHITE ? this.castlingRights.white : this.castlingRights.black;
        if (!rights.includes(side))
            return false;
        const kingIdx = this.findPieceIdx(PieceType.KING, color);
        const rookIdx = side === 'K' ? this.findPieceIdx(PieceType.R, color, 7) : this.findPieceIdx(PieceType.R, color, 0);
        if (kingIdx === null || rookIdx === null)
            return false;
        const [kr, kc] = (0, exports.sqToCoord)(kingIdx);
        const path = side === 'K' ? [[kr, kc + 1], [kr, kc + 2]] : [[kr, kc - 1], [kr, kc - 2], [kr, kc - 3]];
        for (const [pr, pc] of path) {
            if (this.board[(0, exports.coordToSq)(pr, pc)] !== null)
                return false;
        }
        return true;
    }
    findPieceIdx(type, color, col) {
        for (let i = 0; i < 64; i++) {
            const p = this.board[i];
            if (p && p.type === type && p.color === color) {
                if (col === undefined || (i % 8 === col))
                    return i;
            }
        }
        return null;
    }
    isKingInCheck(color, board) {
        let kingIdx = null;
        for (let i = 0; i < 64; i++) {
            const p = board[i];
            if (p && p.type === PieceType.KING && p.color === color) {
                kingIdx = i;
                break;
            }
        }
        if (kingIdx === null)
            return false;
        const [kr, kc] = (0, exports.sqToCoord)(kingIdx);
        for (let i = 0; i < 64; i++) {
            const p = board[i];
            if (p && p.color !== color) {
                if (this.canPieceReach(p, kr, kc, board))
                    return true;
            }
        }
        return false;
    }
    canPieceReach(piece, tr, tc, board) {
        let pr = -1, pc = -1;
        for (let i = 0; i < 64; i++) {
            const p = board[i];
            if (p && p.type === piece.type && p.color === piece.color) {
                [pr, pc] = (0, exports.sqToCoord)(i);
                break;
            }
        }
        if (pr === -1)
            return false;
        switch (piece.type) {
            case PieceType.PAWN:
                const dir = piece.color === Color.WHITE ? -1 : 1;
                return tr === pr + dir && Math.abs(tc - pc) === 1;
            case PieceType.KNIGHT:
                return (Math.abs(tr - pr) === 2 && Math.abs(tc - pc) === 1) || (Math.abs(tr - pr) === 1 && Math.abs(tc - pc) === 2);
            case PieceType.BISHOP:
                if (Math.abs(tr - pr) !== Math.abs(tc - pc))
                    return false;
                let stepR = tr > pr ? 1 : (tr < pr ? -1 : 0), stepC = tc > pc ? 1 : (tc < pc ? -1 : 0);
                let cr = pr + stepR, cc = pc + stepC;
                while (cr !== tr || cc !== tc) {
                    if (board[cr * 8 + cc] !== null)
                        return false;
                    cr += stepR;
                    cc += stepC;
                }
                return true;
            case PieceType.ROOK:
                if (tr !== pr && tc !== pc)
                    return false;
                let sR = tr > pr ? 1 : (tr < pr ? -1 : 0), sC = tc > pc ? 1 : (tc < pc ? -1 : 0);
                let cr2 = pr + sR, cc2 = pc + sC;
                while (cr2 !== tr || cc2 !== tc) {
                    if (board[cr2 * 8 + cc2] !== null)
                        return false;
                    cr2 += sR;
                    cc2 += sC;
                }
                return true;
            case PieceType.QUEEN:
                if (Math.abs(tr - pr) !== Math.abs(tc - pc) && tr !== pr && tc !== pc)
                    return false;
                let qR = tr > pr ? 1 : (tr < pr ? -1 : 0), qC = tc > pc ? 1 : (tc < pc ? -1 : 0);
                let cr3 = pr + qR, cc3 = pc + qC;
                while (cr3 !== tr || cc3 !== tc) {
                    if (board[cr3 * 8 + cc3] !== null)
                        return false;
                    cr3 += qR;
                    cc3 += qC;
                }
                return true;
            case PieceType.KING:
                return Math.abs(tr - pr) <= 1 && Math.abs(tc - pc) <= 1;
        }
        return false;
    }
    applyMove(move, promotion) {
        const [fr, fc] = move.from;
        const [tr, tc] = move.to;
        const piece = this.board[fr * 8 + fc];
        if (!piece)
            return false;
        // Update En Passant Square
        if (piece.type === PieceType.PAWN && Math.abs(tr - fr) === 2) {
            this.enPassantSquare = (0, exports.coordToSq)((fr + tr) / 2, fc);
        }
        else if (piece.type === PieceType.PAWN && Math.abs(tr - fr) === 1) {
            this.enPassantSquare = null;
        }
        // Castling Rights
        const key = this.turn === Color.WHITE ? 'white' : 'black';
        if (piece.type === PieceType.KING) {
            this.castlingRights[key] = this.castlingRights[key].replace(move.isCastling || '', '');
        }
        else if (piece.type === PieceType.ROOK) {
            if (fc === 0 || fc === 7) {
                this.castlingRights[key] = this.castlingRights[key].replace('K', '').replace('Q', '');
            }
        }
        // Execute move
        const targetIdx = tr * 8 + tc;
        if (move.isEnPassant) {
            const epSqIdx = (0, exports.coordToSq)(fr, fc - (this.turn === Color.WHITE ? 1 : -1));
            this.board[epSqIdx] = null;
        }
        this.board[targetIdx] = promotion ? { type: promotion, color: this.turn } : piece;
        this.board[fr * 8 + fc] = null;
        this.halfMoveClock++;
        if (this.halfMoveClock % 50 === 0)
            this.fullMoveNumber++;
        this.turn = this.turn === Color.WHITE ? Color.BLACK : Color.WHITE;
        this.moveHistory.push(move);
        return true;
    }
    isGameOver() {
        const moves = this.getLegalMoves();
        if (moves.length === 0) {
            if (this.isKingInCheck(this.turn, this.board))
                return { win: this.turn === Color.WHITE ? 'Black' : 'White', reason: 'checkmate' };
            return { win: null, reason: 'stalemate' };
        }
        return { win: null, reason: null };
    }
    evaluate() {
        let score = 0;
        for (let i = 0; i < 64; i++) {
            const p = this.board[i];
            if (p) {
                const val = exports.PIECE_VALUES[p.type];
                score += (p.color === Color.WHITE ? val : -val);
            }
        }
        return score;
    }
    getBestMove(depth) {
        let bestScore = -Infinity;
        let bestMove = null;
        const moves = this.getLegalMoves();
        for (const m of moves) {
            const oldBoard = [...this.board];
            const prevTurn = this.turn;
            // Apply move for search
            const [fr, fc] = m.from;
            const [tr, tc] = m.to;
            this.board[tr * 8 + tc] = this.board[fr * 8 + fc];
            this.board[fr * 8 + fc] = null;
            this.turn = prevTurn === Color.WHITE ? Color.BLACK : Color.WHITE;
            const score = this.minimax(depth - 1, -Infinity, Infinity, false);
            // Revert
            this.board = oldBoard;
            this.turn = prevTurn;
            if (score > bestScore) {
                bestScore = score;
                bestMove = m;
            }
        }
        return bestMove;
    }
    minimax(depth, alpha, beta, isMaximizing) {
        const moves = this.getLegalMoves();
        if (depth === 0 || moves.length === 0)
            return this.evaluate();
        let maxEval = -Infinity;
        let minEval = Infinity;
        if (isMaximizing) {
            for (const m of moves) {
                const oldBoard = [...this.board];
                const prevTurn = this.turn;
                const [fr, fc] = m.from;
                const [tr, tc] = m.to;
                this.board[tr * 8 + tc] = this.board[fr * 8 + fc];
                this.board[fr * 8 + fc] = null;
                this.turn = prevTurn === Color.WHITE ? Color.BLACK : Color.WHITE;
                const ev = this.minimax(depth - 1, alpha, beta, false);
                this.board = oldBoard;
                this.turn = prevTurn;
                maxEval = Math.max(maxEval, ev);
                alpha = Math.max(alpha, ev);
                if (beta <= alpha)
                    break;
            }
            return maxEval;
        }
        else {
            for (const m of moves) {
                const oldBoard = [...this.board];
                const prevTurn = this.turn;
                const [fr, fc] = m.from;
                const [tr, tc] = m.to;
                this.board[tr * 8 + tc] = this.board[fr * 8 + fc];
                this.board[fr * 8 + fc] = null;
                this.turn = prevTurn === Color.WHITE ? Color.BLACK : Color.WHITE;
                const ev = this.minimax(depth - 1, alpha, beta, true);
                this.board = oldBoard;
                this.turn = prevTurn;
                minEval = Math.min(minEval, ev);
                beta = Math.min(beta, ev);
                if (beta <= alpha)
                    break;
            }
            return minEval;
        }
    }
    getGameState() {
        return {
            board: this.board,
            turn: this.turn,
            castlingRights: this.castlingRights,
            enPassantSquare: this.enPassantSquare,
            halfMoveClock: this.halfMoveClock,
            fullMoveNumber: this.fullMoveNumber,
            moveHistory: this.moveHistory
        };
    }
    loadState(state) {
        this.board = state.board;
        this.turn = state.turn;
        this.castlingRights = state.castlingRights;
        this.enPassantSquare = state.enPassantSquare;
        this.halfMoveClock = state.halfMoveClock;
        this.fullMoveNumber = state.fullMoveNumber;
        this.moveHistory = state.moveHistory;
    }
}
exports.ChessEngine = ChessEngine;
};

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