Chess
Gemma 4 12b · typescript

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>