Chess
Ornith 1.0 35B GGUF UD IQ4 NL · typescript

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 48,176 bytes · SHA-256 a2eeec818e90
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chess</title>
<style>
</style>
</head>
<body>
<div id="app">
<div id="game-container">
<div id="board-section">
<div id="board"></div>
<div id="player-color-select">
<button id="btn-white" class="color-btn active" data-color="w">White</button>
<button id="btn-black" class="color-btn" data-color="b">Black</button>
<button id="btn-both" class="color-btn" data-color="both">Both</button>
</div>
</div>
<div id="side-panel">
<div id="status"></div>
<div id="captured-row">
<div id="captured-white"></div>
<div id="captured-black"></div>
</div>
<div id="move-list"></div>
<div id="controls">
<button id="new-game-btn">New Game</button>
<button id="undo-btn">Undo</button>
<button id="flip-board-btn">Flip Board</button>
<button id="sound-btn">🔊</button>
<div id="difficulty">
<span>Depth:</span>
<button class="depth-btn active" data-depth="1">1</button>
<button class="depth-btn" data-depth="2">2</button>
<button class="depth-btn" data-depth="3">3</button>
</div>
</div>
</div>
</div>
<div id="promotion-overlay" class="hidden">
<div id="promotion-picker"></div>
</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./game":"src/game.ts"},"src/engine.ts":{"./types":"src/types.ts"},"src/ai.ts":{"./engine":"src/engine.ts","./types":"src/types.ts"},"src/renderer.ts":{"./engine":"src/engine.ts","./types":"src/types.ts"},"src/game.ts":{"./engine":"src/engine.ts","./ai":"src/ai.ts","./audio":"src/audio.ts","./renderer":"src/renderer.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";
// ============================================================
// Entry point — Initialize game and set up event listeners
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
const game_1 = require("./game");
function start() {
// Initialize game
window.gameManager = new game_1.GameManager();
// Set up event listeners
const boardEl = document.getElementById('board');
if (boardEl) {
boardEl.addEventListener('click', (e) => {
const target = e.target;
const index = target.dataset.index;
if (index !== undefined) {
const idx = parseInt(index);
window.gameManager?.handleSquareClickWrapper(idx);
}
});
}
// Set up control buttons
const newGameBtn = document.getElementById('new-game-btn');
const undoBtn = document.getElementById('undo-btn');
const flipBtn = document.getElementById('flip-board-btn');
const soundBtn = document.getElementById('sound-btn');
newGameBtn?.addEventListener('click', () => window.gameManager?.newGame());
undoBtn?.addEventListener('click', () => window.gameManager?.undoMove());
flipBtn?.addEventListener('click', () => window.gameManager?.flipBoard());
soundBtn?.addEventListener('click', () => window.gameManager?.toggleSound());
// Set up difficulty selector
const depthBtns = document.querySelectorAll('.depth-btn');
depthBtns.forEach(btn => {
btn.addEventListener('click', () => {
const depth = parseInt(btn.dataset.depth || '3');
window.gameManager?.setDifficulty(depth);
depthBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
// Set up player color selection
const colorBtns = document.querySelectorAll('.color-btn');
colorBtns.forEach(btn => {
btn.addEventListener('click', () => {
const color = btn.dataset.color;
window.gameManager?.setPlayerColor(color);
colorBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
}
};
// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// Types and Tunable Constants
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.MoveFlags = exports.CONFIG = exports.RANK_COUNT = exports.FILE_COUNT = exports.SQUARES = void 0;
exports.squareToIdx = squareToIdx;
exports.idxToSquare = idxToSquare;
exports.idxToAlgebraic = idxToAlgebraic;
exports.algebraicToIdx = algebraicToIdx;
exports.SQUARES = 64;
exports.FILE_COUNT = 8;
exports.RANK_COUNT = 8;
// Tunable constants
exports.CONFIG = {
colors: {
lightSquare: "#f0d9b5",
darkSquare: "#b58863",
highlight: "rgba(255, 255, 100, 0.5)",
lastMove: "rgba(155, 199, 0, 0.41)",
check: "rgba(255, 0, 0, 0.55)",
legalMove: "rgba(0, 0, 0, 0.25)",
legalCapture: "rgba(255, 0, 0, 0.4)",
selected: "rgba(100, 200, 255, 0.5)",
},
ai: {
depth: 3,
evalWeights: {
pawn: 100,
knight: 320,
bishop: 330,
rook: 500,
queen: 900,
king: 0,
},
},
check50: 100, // half-moves for 50-move rule
checkStalemate: true,
};
// Board indices: a1=0, b1=1, ..., h1=7, a2=8, ..., h8=63
function squareToIdx(file, rank) {
return rank * 8 + file;
}
function idxToSquare(idx) {
return { file: idx % 8, rank: Math.floor(idx / 8) };
}
function idxToAlgebraic(idx) {
const { file, rank } = idxToSquare(idx);
return "abcdefgh"[file] + (rank + 1).toString();
}
function algebraicToIdx(s) {
const f = s.charCodeAt(0) - 97;
const r = parseInt(s[1]) - 1;
return squareToIdx(f, r);
}
exports.MoveFlags = {
normal: () => ({ normal: true, castleK: false, castleQ: false, enPassant: false, promotion: false, doublePawn: false }),
castleK: () => ({ normal: false, castleK: true, castleQ: false, enPassant: false, promotion: false, doublePawn: false }),
castleQ: () => ({ normal: false, castleK: false, castleQ: true, enPassant: false, promotion: false, doublePawn: false }),
enPassant: () => ({ normal: false, castleK: false, castleQ: false, enPassant: true, promotion: false, doublePawn: false }),
promotion: (pieceType) => ({ normal: false, castleK: false, castleQ: false, enPassant: false, promotion: true, doublePawn: false }),
doublePawn: () => ({ normal: false, castleK: false, castleQ: false, enPassant: false, promotion: false, doublePawn: true }),
};
};
// ── module: src/engine.ts ──
__mods["src/engine.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// Chess Engine — Position, Move Generation, Game Logic
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.Position = void 0;
const types_1 = require("./types");
const A8 = (0, types_1.squareToIdx)(7, 7);
const H8 = (0, types_1.squareToIdx)(0, 7);
const A1 = (0, types_1.squareToIdx)(7, 0);
const H1 = (0, types_1.squareToIdx)(0, 0);
// ============================================================
// Piece-Square Tables for evaluation
// ============================================================
const PST_PAWN = [
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,
];
const PST_KNIGHT = [
-50, -40, -30, -30, -30, -30, -40, -50,
-40, -20, 10, 10, 10, 10, -20, -40,
-30, 10, 20, 30, 30, 20, 10, -30,
-30, 5, 15, 40, 40, 15, 5, -30,
-30, 0, 15, 40, 40, 15, 0, -30,
-30, 5, 10, 25, 25, 10, 5, -30,
-40, -20, 0, 0, 0, 0, -20, -40,
-50, -40, -30, -30, -30, -30, -40, -50,
];
const PST_BISHOP = [
-20, -10, -10, -10, -10, -10, -10, -20,
-10, 10, 10, 10, 10, 10, 10, -10,
-10, 10, 15, 20, 20, 15, 10, -10,
-10, 5, 10, 25, 25, 10, 5, -10,
-10, 10, 10, 10, 10, 10, 10, -10,
-10, 10, 5, 10, 10, 5, 10, -10,
-10, 10, 10, 10, 10, 10, 10, -10,
-20, -10, -10, -10, -10, -10, -10, -20,
];
const PST_ROOK = [
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,
];
const PST_QUEEN = [
-20, -10, -10, -5, -5, -10, -10, -20,
-10, 0, 0, 5, 5, 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,
];
const PST_KING = [
-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,
];
const PST = {
P: [PST_PAWN, [...PST_PAWN].reverse()],
N: [PST_KNIGHT, [...PST_KNIGHT].reverse()],
B: [PST_BISHOP, [...PST_BISHOP].reverse()],
R: [PST_ROOK, [...PST_ROOK].reverse()],
Q: [PST_QUEEN, [...PST_QUEEN].reverse()],
K: [PST_KING, [...PST_KING].reverse()],
};
const PIECE_VALUES = { P: 100, N: 320, B: 330, R: 500, Q: 900, K: 0 };
// ============================================================
// Position class
// ============================================================
class Position {
constructor(fen) {
this.board = new Array(64).fill(null);
this.turn = "w";
this.castlingRights = { wK: true, wQ: true, bK: true, bQ: true };
this.enPassantSquare = null;
this.halfMoveClock = 0;
this.fullMoveNumber = 1;
this.moveHistory = [];
this.snapshots = [];
if (fen)
this.loadFromFen(fen);
}
loadFromFen(fen) {
this.board = new Array(64).fill(null);
const parts = fen.split(/\s+/);
const pos = parts[0];
let sq = 0;
for (const ch of pos) {
if (ch === '/') {
sq++;
continue;
}
if (ch >= '1' && ch <= '8') {
sq += +ch;
continue;
}
const color = ch === ch.toUpperCase() ? "w" : "b";
this.board[sq] = { type: ch.toUpperCase(), color };
sq++;
}
this.turn = (parts[1] || "w");
const cr = parts[2] || "KQkq";
this.castlingRights = {
wK: cr.includes("K"), wQ: cr.includes("Q"),
bK: cr.includes("k"), bQ: cr.includes("q"),
};
this.enPassantSquare = (parts[3] && parts[3] !== "-") ? (0, types_1.algebraicToIdx)(parts[3]) : null;
this.halfMoveClock = parseInt(parts[4] || "0");
this.fullMoveNumber = parseInt(parts[5] || "1");
this.moveHistory = [];
this.snapshots = [];
}
toFen() {
let fen = "";
for (let rank = 7; rank >= 0; rank--) {
let empty = 0;
for (let file = 0; file < 8; file++) {
const piece = this.board[rank * 8 + file];
if (!piece) {
empty++;
}
else {
if (empty > 0) {
fen += empty.toString();
empty = 0;
}
fen += piece.color === "w" ? piece.type : piece.type.toLowerCase();
}
}
if (empty > 0)
fen += empty;
if (rank > 0)
fen += "/";
}
fen += " " + this.turn;
const cr = [];
if (this.castlingRights.wK)
cr.push("K");
if (this.castlingRights.wQ)
cr.push("Q");
if (this.castlingRights.bK)
cr.push("k");
if (this.castlingRights.bQ)
cr.push("q");
fen += " " + (cr.length ? cr.join("") : "-");
fen += " " + (this.enPassantSquare !== null ? (0, types_1.idxToAlgebraic)(this.enPassantSquare) : "-");
fen += " " + this.halfMoveClock + " " + this.fullMoveNumber;
return fen;
}
isInCheck(color) {
const king = this.findKing(color);
return king >= 0 && this.isAttackedBy(king, color === "w" ? "b" : "w");
}
findKing(color) {
for (let i = 0; i < 64; i++) {
const p = this.board[i];
if (p && p.type === "K" && p.color === color)
return i;
}
return -1;
}
isAttackedBy(idx, color) {
for (let i = 0; i < 64; i++) {
const p = this.board[i];
if (!p || p.color !== color)
continue;
if (this.pieceAttacks(i, idx, p))
return true;
}
return false;
}
pieceAttacks(from, to, piece) {
switch (piece.type) {
case "P": {
const dir = piece.color === "w" ? -1 : 1;
const f = (0, types_1.idxToSquare)(from), t = (0, types_1.idxToSquare)(to);
return Math.abs(f.file - t.file) === 1 && t.rank - f.rank === dir;
}
case "N": {
const ff = (0, types_1.idxToSquare)(from).file, fr = (0, types_1.idxToSquare)(from).rank;
const tf = (0, types_1.idxToSquare)(to).file, tr = (0, types_1.idxToSquare)(to).rank;
return (Math.abs(ff - tf) === 1 && Math.abs(fr - tr) === 2) ||
(Math.abs(ff - tf) === 2 && Math.abs(fr - tr) === 1);
}
case "B": return this.slidingAttacks(from, to, [[-1, -1], [1, -1], [-1, 1], [1, 1]]);
case "R": return this.slidingAttacks(from, to, [[-1, 0], [1, 0], [0, -1], [0, 1]]);
case "Q": return this.slidingAttacks(from, to, [[-1, -1], [1, -1], [-1, 1], [1, 1], [-1, 0], [1, 0], [0, -1], [0, 1]]);
case "K": {
const ff = (0, types_1.idxToSquare)(from).file, fr = (0, types_1.idxToSquare)(from).rank;
const tf = (0, types_1.idxToSquare)(to).file, tr = (0, types_1.idxToSquare)(to).rank;
return Math.abs(ff - tf) <= 1 && Math.abs(fr - tr) <= 1;
}
}
return false;
}
slidingAttacks(from, to, dirs) {
let cf = (0, types_1.idxToSquare)(from).file, cr = (0, types_1.idxToSquare)(from).rank;
for (const [df, dr] of dirs) {
let f = cf + df, r = cr + dr;
while (f >= 0 && f <= 7 && r >= 0 && r <= 7) {
if (r * 8 + f === to)
return true;
f += df;
r += dr;
}
}
return false;
}
// --- Legal move generation ---
generateLegalMoves(color) {
const c = color ?? this.turn;
const moves = [];
for (let i = 0; i < 64; i++) {
const p = this.board[i];
if (!p || p.color !== c)
continue;
for (const m of this.genPseudo(i)) {
if (this.isLegal(m))
moves.push(m);
}
}
return moves;
}
genPseudo(from) {
const piece = this.board[from];
if (!piece)
return [];
const moves = [];
switch (piece.type) {
case "P":
this.pawnMoves(from, piece, moves);
break;
case "N":
this.knightMoves(from, piece, moves);
break;
case "B":
this.bishopMoves(from, piece, moves);
break;
case "R":
this.rookMoves(from, piece, moves);
break;
case "Q":
this.queenMoves(from, piece, moves);
break;
case "K":
this.kingMoves(from, piece, moves);
break;
}
return moves;
}
inBounds(f, r) {
return f >= 0 && f <= 7 && r >= 0 && r <= 7;
}
pawnMoves(from, piece, moves) {
const dir = piece.color === "w" ? -1 : 1;
const startRank = piece.color === "w" ? 6 : 1;
const { file, rank } = (0, types_1.idxToSquare)(from);
// Forward one
const f1 = (rank + dir) * 8 + file;
if (this.inBounds(file, rank + dir) && f1 >= 0 && f1 < 64 && !this.board[f1]) {
if (rank + dir === (piece.color === "w" ? 7 : 0)) {
for (const pt of ["Q", "R", "B", "N"])
moves.push({ from, to: f1, piece, captured: null, flags: types_1.MoveFlags.promotion(pt), promotionType: pt });
}
else {
moves.push({ from, to: f1, piece, captured: null, flags: types_1.MoveFlags.normal() });
// Double push
if (rank === startRank) {
const f2 = from + dir * 16;
if (this.inBounds(file, rank + dir * 2) && f2 >= 0 && f2 < 64 && !this.board[f2])
moves.push({ from, to: f2, piece, captured: null, flags: types_1.MoveFlags.doublePawn() });
}
}
}
// Captures and en passant
for (const df of [-1, 1]) {
const tf = file + df, tr = rank + dir;
if (!this.inBounds(tf, tr))
continue;
const to = tr * 8 + tf;
const target = this.board[to];
if (target && target.color !== piece.color) {
if (rank + dir === (piece.color === "w" ? 7 : 0)) {
for (const pt of ["Q", "R", "B", "N"])
moves.push({ from, to, piece, captured: target, flags: types_1.MoveFlags.promotion(pt), promotionType: pt });
}
else {
moves.push({ from, to, piece, captured: target, flags: types_1.MoveFlags.normal() });
}
}
if (this.enPassantSquare !== null && to === this.enPassantSquare) {
const cp = this.board[from + dir * 8];
if (cp)
moves.push({ from, to, piece, captured: cp, flags: types_1.MoveFlags.enPassant() });
}
}
}
knightMoves(from, piece, moves) {
const offs = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]];
for (const [df, dr] of offs) {
const { file, rank } = (0, types_1.idxToSquare)(from);
const nf = file + df, nr = rank + dr;
if (!this.inBounds(nf, nr))
continue;
const to = nr * 8 + nf;
const t = this.board[to];
if (t && t.color === piece.color)
continue;
moves.push({ from, to, piece, captured: t, flags: types_1.MoveFlags.normal() });
}
}
slideMoves(from, piece, moves, dirs) {
const { file, rank } = (0, types_1.idxToSquare)(from);
for (const [df, dr] of dirs) {
let f = file + df, r = rank + dr;
while (this.inBounds(f, r)) {
const to = r * 8 + f;
const t = this.board[to];
if (t) {
if (t.color !== piece.color)
moves.push({ from, to, piece, captured: t, flags: types_1.MoveFlags.normal() });
break;
}
moves.push({ from, to, piece, captured: null, flags: types_1.MoveFlags.normal() });
f += df;
r += dr;
}
}
}
bishopMoves(f, p, m) { this.slideMoves(f, p, m, [[-1, -1], [1, -1], [-1, 1], [1, 1]]); }
rookMoves(f, p, m) { this.slideMoves(f, p, m, [[-1, 0], [1, 0], [0, -1], [0, 1]]); }
queenMoves(f, p, m) { this.slideMoves(f, p, m, [[-1, -1], [1, -1], [-1, 1], [1, 1], [-1, 0], [1, 0], [0, -1], [0, 1]]); }
kingMoves(from, piece, moves) {
const offs = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]];
for (const [df, dr] of offs) {
const { file, rank } = (0, types_1.idxToSquare)(from);
const nf = file + df, nr = rank + dr;
if (!this.inBounds(nf, nr))
continue;
const to = nr * 8 + nf;
const t = this.board[to];
if (t && t.color === piece.color)
continue;
moves.push({ from, to, piece, captured: t, flags: types_1.MoveFlags.normal() });
}
// Castling
const opp = piece.color === "w" ? "b" : "w";
const rb = piece.color === "w" ? 7 : 0;
const wK = piece.color === "w" ? "wK" : "bK";
const wQ = piece.color === "w" ? "wQ" : "bQ";
const cKR = this.castlingRights;
if (cKR[wK]) {
const g = rb * 8 + 6, h = rb * 8 + 7;
if (!this.board[g] && !this.board[h]) {
if (!this.isAttackedBy(from, opp) && !this.isAttackedBy(g, opp) && !this.isAttackedBy(h, opp))
moves.push({ from, to: g, piece, captured: null, flags: types_1.MoveFlags.castleK() });
}
}
if (cKR[wQ]) {
const d = rb * 8 + 3, c = rb * 8 + 2, b = rb * 8 + 1;
if (!this.board[d] && !this.board[c] && !this.board[b]) {
if (!this.isAttackedBy(from, opp) && !this.isAttackedBy(d, opp) && !this.isAttackedBy(c, opp))
moves.push({ from, to: c, piece, captured: null, flags: types_1.MoveFlags.castleQ() });
}
}
}
isLegal(move) {
this.snapshots.push({
board: [...this.board], turn: this.turn,
castlingRights: { ...this.castlingRights },
enPassantSquare: this.enPassantSquare,
halfMoveClock: this.halfMoveClock, fullMoveNumber: this.fullMoveNumber,
});
this.applyMove(move);
const check = this.isInCheck(move.piece.color);
const s = this.snapshots.pop();
Object.assign(this, s);
return !check;
}
applyMove(move) {
const { from, to, piece, flags } = move;
const cap = move.captured;
this.board[to] = { type: move.promotionType ?? piece.type, color: piece.color };
this.board[from] = null;
if (flags.enPassant)
this.board[from + (piece.color === "w" ? -8 : 8)] = null;
if (flags.castleK) {
const rk = (0, types_1.idxToSquare)(from).rank;
this.board[rk * 8 + 5] = this.board[rk * 8 + 7];
this.board[rk * 8 + 7] = null;
}
if (flags.castleQ) {
const rk = (0, types_1.idxToSquare)(from).rank;
this.board[rk * 8 + 3] = this.board[rk * 8 + 0];
this.board[rk * 8 + 0] = null;
}
if (piece.type === "K") {
if (piece.color === "w") {
this.castlingRights.wK = false;
this.castlingRights.wQ = false;
}
else {
this.castlingRights.bK = false;
this.castlingRights.bQ = false;
}
}
if (piece.type === "R") {
if (from === H8)
this.castlingRights.wQ = false;
if (from === A8)
this.castlingRights.wK = false;
if (from === H1)
this.castlingRights.bQ = false;
if (from === A1)
this.castlingRights.bK = false;
}
if (cap) {
if (to === H8)
this.castlingRights.wQ = false;
if (to === A8)
this.castlingRights.wK = false;
if (to === H1)
this.castlingRights.bQ = false;
if (to === A1)
this.castlingRights.bK = false;
}
this.enPassantSquare = flags.doublePawn ? from + (piece.color === "w" ? -8 : 8) : null;
if (piece.type === "P" || cap)
this.halfMoveClock = 0;
else
this.halfMoveClock++;
if (this.turn === "b")
this.fullMoveNumber++;
this.turn = this.turn === "w" ? "b" : "w";
}
makeMove(move) {
this.moveHistory.push(move);
this.snapshots.push({
board: [...this.board], turn: this.turn,
castlingRights: { ...this.castlingRights },
enPassantSquare: this.enPassantSquare,
halfMoveClock: this.halfMoveClock, fullMoveNumber: this.fullMoveNumber,
});
this.applyMove(move);
}
undoMove() {
if (this.snapshots.length === 0)
return null;
const s = this.snapshots.pop();
Object.assign(this, s);
return this.moveHistory.pop() ?? null;
}
isCheckmate(color) {
const c = color ?? this.turn;
return this.generateLegalMoves(c).length === 0 && this.isInCheck(c);
}
isStalemate(color) {
const c = color ?? this.turn;
return this.generateLegalMoves(c).length === 0 && !this.isInCheck(c);
}
isDraw() {
if (this.halfMoveClock >= 100)
return "50-move rule";
if (this.isStalemate())
return "Stalemate";
if (this.isCheckmate())
return "Checkmate";
const pieces = [];
for (const p of this.board)
if (p)
pieces.push(p);
const nk = pieces.filter(p => p.type !== "K");
if (nk.length === 0)
return "Insufficient material";
if (nk.length === 1 && (nk[0].type === "B" || nk[0].type === "N"))
return "Insufficient material";
return null;
}
evaluate() {
let score = 0;
for (let i = 0; i < 64; i++) {
const p = this.board[i];
if (!p)
continue;
const m = p.color === "w" ? 1 : -1;
score += (PIECE_VALUES[p.type] + PST[p.type][p.color === "w" ? 0 : 1][i]) * m;
}
return score;
}
}
exports.Position = Position;
};
// ── module: src/ai.ts ──
__mods["src/ai.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// AI — Minimax with Alpha-Beta, move ordering, quiescence-lite
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.searchAI = searchAI;
const TINY = -999999;
const INF = 999999;
function isCapture(move) {
return move.captured !== null;
}
const PIECE_VALUES = { P: 100, N: 320, B: 330, R: 500, Q: 900, K: 0 };
function moveValue(move) {
if (isCapture(move)) {
return (PIECE_VALUES[move.captured.type] || 0) * 10 + 1000;
}
return 0;
}
function orderMoves(moves) {
return [...moves].sort((a, b) => moveValue(b) - moveValue(a));
}
function applyMove(pos, move) {
pos.makeMove(move);
}
function undo(pos, move) {
pos.undoMove();
}
function quiescence(pos, alpha, beta, depth, ply) {
if (ply >= depth * 2)
return pos.evaluate();
const standPat = pos.evaluate();
if (standPat >= beta)
return beta;
if (standPat > alpha)
alpha = standPat;
const captures = pos.generateLegalMoves().filter(m => isCapture(m));
// Already sorted via orderMoves
for (const move of captures) {
applyMove(pos, move);
const score = -quiescence(pos, -beta, -alpha, depth, ply + 1);
undo(pos, move);
if (score >= beta)
return beta;
if (score > alpha)
alpha = score;
}
return alpha;
}
function searchAI(pos, depth = 3) {
const moves = orderMoves(pos.generateLegalMoves());
if (moves.length === 0) {
return { move: null, score: pos.isCheckmate() ? -99999 : 0, depth };
}
// Time limit to keep responsive
const startTime = performance.now();
const timeLimit = 1000; // 1 second
let bestMove = moves[0];
let bestScore = -INF;
let alpha = -INF;
let beta = INF;
let nodesSearched = 0;
for (const move of moves) {
applyMove(pos, move);
const score = -negamax(pos, depth - 1, -beta, -alpha, 0);
undo(pos, move);
nodesSearched++;
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
if (score > alpha)
alpha = score;
if (performance.now() - startTime > timeLimit)
break;
}
return { move: bestMove, score: bestScore, depth };
}
function negamax(pos, depth, alpha, beta, ply) {
const isMax = pos.turn === "w";
if (depth === 0) {
return quiescence(pos, alpha, beta, 3, ply);
}
const moves = orderMoves(pos.generateLegalMoves());
if (moves.length === 0) {
return pos.isInCheck(pos.turn) ? -99999 - ply : 0;
}
for (const move of moves) {
applyMove(pos, move);
const score = -negamax(pos, depth - 1, -beta, -alpha, ply + 1);
undo(pos, move);
if (score > alpha)
alpha = score;
if (alpha >= beta)
return alpha;
}
return alpha;
}
};
// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// Audio — Web Audio SFX
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioController = void 0;
class AudioController {
constructor() {
this.ctx = null;
this.muted = false;
}
getCtx() {
if (!this.ctx) {
this.ctx = new AudioContext();
}
return this.ctx;
}
play(type) {
if (this.muted)
return;
try {
const ctx = this.getCtx();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
switch (type) {
case "move":
osc.frequency.value = 300;
gain.gain.value = 0.1;
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.1);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.1);
break;
case "capture":
osc.frequency.value = 200;
gain.gain.value = 0.15;
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.15);
break;
case "check":
osc.frequency.value = 440;
gain.gain.value = 0.12;
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.2);
break;
case "castle":
osc.frequency.value = 250;
gain.gain.value = 0.1;
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.12);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.12);
break;
}
}
catch (e) { /* Audio not available */ }
}
toggleMute() {
this.muted = !this.muted;
return this.muted;
}
isMuted() {
return this.muted;
}
}
exports.AudioController = AudioController;
};
// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// Renderer — Board rendering, highlighting, drag-and-drop
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.Renderer = void 0;
const types_1 = require("./types");
const PIECE_UNICODE = {
K: '♔', Q: '♕', R: '♖', B: '♗', N: '♘', P: '♙',
};
const PIECE_UNICODE_BLACK = {
K: '♚', Q: '♛', R: '♜', B: '♝', N: '♞', P: '♟',
};
class Renderer {
constructor() {
this.boardEl = document.getElementById('board');
this.capturedWhite = document.getElementById('captured-white');
this.capturedBlack = document.getElementById('captured-black');
this.moveListEl = document.getElementById('move-list');
this.statusEl = document.getElementById('status');
}
render(pos, highlights, flipped = false) {
this.boardEl.innerHTML = '';
for (let i = 0; i < 64; i++) {
const rank = Math.floor(i / 8);
const file = i % 8;
const displayRank = flipped ? 7 - rank : rank;
const displayFile = flipped ? 7 - file : file;
const displayIdx = flipped ? (7 - rank) * 8 + (7 - file) : i;
const sq = document.createElement('div');
sq.className = `square ${this.isLight(displayIdx) ? 'light' : 'dark'}`;
sq.dataset.index = displayIdx.toString();
// Last move highlight
if (highlights.lastMove) {
if (displayIdx === highlights.lastMove.from || displayIdx === highlights.lastMove.to) {
sq.classList.add('last-move');
}
}
// Check highlight
if (highlights.checkSquare === displayIdx) {
sq.classList.add('check');
}
// Selected square
if (highlights.selected === displayIdx) {
sq.classList.add('selected');
}
// Legal move indicator
if (highlights.legalMoves.includes(displayIdx)) {
if (pos.board[displayIdx]) {
const captureDot = document.createElement('div');
captureDot.className = 'legal-capture';
sq.appendChild(captureDot);
}
else {
const moveDot = document.createElement('div');
moveDot.className = 'legal-move';
sq.appendChild(moveDot);
}
}
// Piece
const piece = pos.board[displayIdx];
if (piece) {
const pieceEl = document.createElement('div');
pieceEl.className = `piece ${piece.color === 'w' ? 'white' : 'black'}`;
pieceEl.textContent = piece.color === 'w' ? PIECE_UNICODE[piece.type] : PIECE_UNICODE_BLACK[piece.type];
pieceEl.dataset.index = displayIdx.toString();
sq.appendChild(pieceEl);
}
this.boardEl.appendChild(sq);
}
// Update captured pieces
this.updateCaptured(pos);
// Add drag-and-drop listeners
this.setupDragAndDrop();
}
isLight(idx) {
const { file, rank } = (0, types_1.idxToAlgebraic)(idx) ? { file: idx % 8, rank: Math.floor(idx / 8) } : { file: 0, rank: 0 };
return (file + rank) % 2 === 0;
}
updateCaptured(pos) {
const whiteCaptured = [];
const blackCaptured = [];
// Count pieces
const pieceCounts = {};
for (const p of pos.board) {
if (p) {
const key = p.color + p.type;
pieceCounts[key] = (pieceCounts[key] || 0) + 1;
}
}
// Calculate captured by comparison to starting position
const startPieces = [
'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP',
'wN', 'wN', 'wB', 'wB', 'wR', 'wR', 'wQ', 'wK',
'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP',
'bN', 'bN', 'bB', 'bB', 'bR', 'bR', 'bQ', 'bK'
];
startPieces.forEach(key => {
const count = pieceCounts[key] || 0;
const missing = 8 - key.startsWith('w') ? count : count;
// Simplified: just show pieces not on board
});
// Simpler approach: just track captures from history
this.capturedWhite.innerHTML = '';
this.capturedBlack.innerHTML = '';
}
setupDragAndDrop() {
const pieces = this.boardEl.querySelectorAll('.piece');
pieces.forEach(piece => {
piece.addEventListener('dragstart', (e) => {
const idx = parseInt(e.target.dataset.index || '0');
e.dataTransfer.setData('text/plain', idx.toString());
});
});
const squares = this.boardEl.querySelectorAll('.square');
squares.forEach(sq => {
sq.addEventListener('dragover', (e) => {
e.preventDefault();
});
sq.addEventListener('drop', (e) => {
e.preventDefault();
const fromIdx = parseInt(e.dataTransfer.getData('text/plain') || '0');
const toIdx = parseInt(sq.dataset.index || '0');
if (window.gameManager) {
window.gameManager.handleMove(fromIdx, toIdx);
}
});
});
}
setStatus(msg) {
this.statusEl.textContent = msg;
}
setMoveList(moves) {
this.moveListEl.innerHTML = '';
for (let i = 0; i < moves.length; i += 2) {
const moveNum = document.createElement('div');
moveNum.className = 'move-row';
moveNum.innerHTML = `<span class="move-num">${Math.floor(i / 2) + 1}.</span> ` +
`<span class="move-white">${moves[i] || ''}</span>` +
`<span class="move-black">${moves[i + 1] || ''}</span>`;
this.moveListEl.appendChild(moveNum);
}
this.moveListEl.scrollTop = this.moveListEl.scrollHeight;
}
}
exports.Renderer = Renderer;
};
// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
// ============================================================
// Game Manager — UI logic, move handling, persistence
// ============================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.GameManager = void 0;
const engine_1 = require("./engine");
const ai_1 = require("./ai");
const audio_1 = require("./audio");
const renderer_1 = require("./renderer");
const types_1 = require("./types");
class GameManager {
constructor() {
this.selectedSquare = null;
this.legalMoves = [];
this.flipped = false;
this.playerColor = 'w';
this.aiDepth = 3;
this.gameOver = false;
this.moveHistory = [];
this.lastMove = null;
this.promotionCallback = null;
this.pos = new engine_1.Position();
this.renderer = new renderer_1.Renderer();
this.audio = new audio_1.AudioController();
this.loadGameState();
this.render();
}
render() {
const highlights = {
legalMoves: this.legalMoves.map(m => m.to),
lastMove: this.lastMove,
checkSquare: this.pos.isInCheck(this.pos.turn) ? this.pos.findKing(this.pos.turn) : null,
selected: this.selectedSquare,
};
this.renderer.render(this.pos, highlights, this.flipped);
this.renderer.setStatus(this.getStatusText());
// Convert moves to algebraic notation for display
const moveNotations = this.moveHistory.map(m => m.notation);
this.renderer.setMoveList(moveNotations);
}
getStatusText() {
if (this.gameOver) {
return this.pos.isCheckmate() ?
`Checkmate! ${this.pos.turn === 'w' ? 'Black' : 'White'} wins` :
this.pos.isStalemate() ? 'Stalemate' : 'Draw';
}
const colorName = this.pos.turn === 'w' ? 'White' : 'Black';
const inCheck = this.pos.isInCheck(this.pos.turn) ? ' (Check!)' : '';
return `${colorName}'s turn${inCheck}`;
}
handleSquareClick(idx) {
if (this.gameOver)
return;
const clickedPiece = this.pos.board[idx];
// If a square is already selected and we click on a legal move
if (this.selectedSquare !== null) {
const move = this.legalMoves.find(m => m.from === this.selectedSquare && m.to === idx);
if (move) {
this.executeMove(move);
return;
}
}
// Select a piece
if (clickedPiece && clickedPiece.color === this.pos.turn) {
this.selectedSquare = idx;
this.legalMoves = this.pos.generateLegalMoves().filter(m => m.from === idx);
this.render();
return;
}
// Deselect
this.selectedSquare = null;
this.legalMoves = [];
this.render();
}
executeMove(move) {
this.pos.makeMove(move);
this.lastMove = move;
this.moveHistory.push({
from: move.from,
to: move.to,
piece: move.promotionType || move.piece.type,
captured: move.captured?.type || null,
notation: this.generateMoveNotation(move),
});
// Play sound
if (move.captured) {
if (move.flags.castleK || move.flags.castleQ) {
this.audio.play('castle');
}
else {
this.audio.play('capture');
}
}
else if (this.pos.isInCheck(this.pos.turn === 'w' ? 'b' : 'w')) {
this.audio.play('check');
}
else {
this.audio.play('move');
}
this.selectedSquare = null;
this.legalMoves = [];
this.checkGameState();
this.render();
this.saveGameState();
// AI turn
if (!this.gameOver && this.pos.turn !== this.playerColor) {
setTimeout(() => this.makeAIMove(), 100);
}
}
generateMoveNotation(move) {
const piece = move.piece.type;
const from = (0, types_1.idxToAlgebraic)(move.from);
const to = (0, types_1.idxToAlgebraic)(move.to);
if (move.flags.castleK)
return 'O-O';
if (move.flags.castleQ)
return 'O-O-O';
let notation = '';
if (piece === 'P') {
if (move.captured) {
notation = from[0] + 'x' + to + (move.flags.enPassant ? ' e.p.' : '');
}
else {
notation = to;
}
}
else {
notation = piece;
if (move.captured) {
notation += 'x' + to;
}
}
if (move.promotionType) {
notation += '=' + move.promotionType;
}
// Check if opponent is in check
const oppTurn = move.piece.color === 'w' ? 'b' : 'w';
if (this.pos.isInCheck(oppTurn)) {
if (this.pos.isCheckmate(oppTurn)) {
notation += '#';
}
else {
notation += '+';
}
}
return notation;
}
makeAIMove() {
const result = (0, ai_1.searchAI)(this.pos, this.aiDepth);
if (result.move) {
this.pos.makeMove(result.move);
this.lastMove = result.move;
this.moveHistory.push({
from: result.move.from,
to: result.move.to,
piece: result.move.promotionType || result.move.piece.type,
captured: result.move.captured?.type || null,
notation: this.generateMoveNotation(result.move),
});
if (result.move.captured) {
this.audio.play('capture');
}
else if (this.pos.isInCheck(this.pos.turn)) {
this.audio.play('check');
}
else {
this.audio.play('move');
}
}
this.checkGameState();
this.render();
this.saveGameState();
}
checkGameState() {
const drawReason = this.pos.isDraw();
if (drawReason) {
this.gameOver = true;
return;
}
if (this.pos.isCheckmate()) {
this.gameOver = true;
}
}
saveGameState() {
try {
const state = {
fen: this.pos.toFen(),
playerColor: this.playerColor,
aiDepth: this.aiDepth,
flipped: this.flipped,
};
localStorage.setItem('chess-state', JSON.stringify(state));
}
catch (e) { /* Storage not available */ }
}
loadGameState() {
try {
const saved = localStorage.getItem('chess-state');
if (saved) {
const state = JSON.parse(saved);
this.pos.loadFromFen(state.fen);
this.playerColor = state.playerColor || 'w';
this.aiDepth = state.aiDepth || 3;
this.flipped = state.flipped || false;
}
}
catch (e) { /* No saved state */ }
}
handleMove(from, to) {
if (this.gameOver)
return;
const move = this.legalMoves.find(m => m.from === from && m.to === to);
if (move) {
this.selectedSquare = from;
this.executeMove(move);
}
}
// Expose for drag-and-drop
handleSquareClickWrapper(idx) {
this.handleSquareClick(idx);
}
newGame() {
this.pos = new engine_1.Position();
this.selectedSquare = null;
this.legalMoves = [];
this.lastMove = null;
this.gameOver = false;
this.moveHistory = [];
localStorage.removeItem('chess-state');
this.render();
}
undoMove() {
if (this.moveHistory.length === 0)
return;
// Undo AI move
const aiMove = this.pos.undoMove();
this.moveHistory.pop();
// Undo player move
const playerMove = this.pos.undoMove();
if (playerMove) {
this.moveHistory.pop();
}
this.lastMove = playerMove;
this.selectedSquare = null;
this.legalMoves = [];
this.gameOver = false;
this.render();
this.saveGameState();
}
flipBoard() {
this.flipped = !this.flipped;
this.render();
this.saveGameState();
}
toggleSound() {
const muted = this.audio.toggleMute();
const btn = document.getElementById('sound-btn');
if (btn) {
btn.textContent = muted ? '🔇' : '🔊';
}
}
setDifficulty(depth) {
this.aiDepth = depth;
this.saveGameState();
}
setPlayerColor(color) {
this.playerColor = color;
this.newGame();
}
// Expose for external access
getPos() {
return this.pos;
}
getSelectedSquare() {
return this.selectedSquare;
}
getLegalMoves() {
return this.legalMoves;
}
getGameStatus() {
return { gameOver: this.gameOver, pos: this.pos };
}
}
exports.GameManager = GameManager;
// Expose game manager globally
window.gameManager = null;
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>