Chess
ProCreations grug 27b GGUF IQ4 · 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 59,258 bytes · SHA-256 15986b979a67
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chess — Rules-complete browser game with AI</title>
<style>
/* ── css: layout ── */
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Segoe UI',system-ui,sans-serif;background:#1e1e1e;color:#eee;overflow:hidden}
#layout{display:flex;align-items:center;justify-content:center;height:100vh;gap:24px;padding:16px}
#board-wrap{position:relative;flex-shrink:0}
#board{width:512px;height:512px;display:grid;grid-template-columns:repeat(8,1fr);grid-template-rows:repeat(8,1fr);border:6px solid #4a3525;border-radius:4px}
.square{position:relative;display:flex;align-items:center;justify-content:center;font-size:3.4em;cursor:pointer;user-select:none;transition:background .12s}
.square.light{background:#f0d9b5}
.square.dark{background:#b58863}
.square.last-move.light{background:#f7ec68}
.square.last-move.dark{background:#daa520}
.square.selected.light{background:#f7f769}
.square.selected.dark{background:#baca2b}
.square.legal-dot::after{content:'';position:absolute;width:22px;height:22px;background:rgba(0,0,0,.25);border-radius:50%}
.square.legal-ring::after{content:'';position:absolute;width:100%;height:100%;border:6px solid rgba(0,0,0,.3);border-radius:50%;box-sizing:border-box}
.square.check{box-shadow:inset 0 0 18px 6px rgba(255,40,40,.7)}
.square:hover{filter:brightness(1.08)}
#sidebar{width:260px;height:512px;display:flex;flex-direction:column;gap:12px;padding:12px;background:#2a2a2a;border-radius:8px;overflow:hidden}
#captured-trays{display:flex;flex-direction:column;gap:4px}
.tray{min-height:28px;font-size:1.4em;line-height:1;padding:2px 4px;background:#222;border-radius:4px;word-break:break-all}
#controls{display:flex;flex-direction:column;gap:6px}
#controls button{padding:8px;border:none;border-radius:6px;background:#4a3525;color:#f0d9b5;cursor:pointer;font-size:.9em;transition:background .15s}
#controls button:hover{background:#6b5342}
#controls button.active{background:#daa520;color:#1e1e1e}
#move-list{flex:1;overflow-y:auto;font-family:monospace;font-size:.85em;line-height:1.6;padding:4px;background:#222;border-radius:4px}
.move-entry{padding:2px 4px;border-radius:2px}
.move-entry:hover{background:#333}
#promotion-overlay{position:absolute;background:#2a2a2a;border:3px solid #daa520;border-radius:10px;padding:6px;display:flex;gap:6px;z-index:100;box-shadow:0 4px 12px rgba(0,0,0,.6)}
.promo-choice{width:64px;height:64px;font-size:2.8em;display:flex;align-items:center;justify-content:center;background:#b58863;border-radius:8px;cursor:pointer}
.promo-choice:hover{background:#daa520}
#game-banner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,.88);color:#daa520;padding:16px 28px;border-radius:12px;font-size:1.3em;z-index:50;text-align:center}
@media(max-width:840px){#layout{flex-direction:column}#sidebar{width:100%;max-width:512px;height:auto}}
</style>
</head>
<body>
<div id="layout">
<div id="board-wrap"><div id="board"></div></div>
<aside id="sidebar">
<div id="captured-trays"></div>
<div id="controls"></div>
<div id="move-list"></div>
<div id="promotion-overlay" hidden=""></div>
<div id="game-banner" hidden=""></div>
</aside>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./game":"src/game.ts","./renderer":"src/renderer.ts","./rules":"src/rules.ts","./types":"src/types.ts","./ai":"src/ai.ts"},"src/position.ts":{"./types":"src/types.ts"},"src/rules.ts":{"./types":"src/types.ts"},"src/ai.ts":{"./types":"src/types.ts","./rules":"src/rules.ts"},"src/game.ts":{"./types":"src/types.ts","./position":"src/position.ts","./rules":"src/rules.ts","./ai":"src/ai.ts"},"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";
Object.defineProperty(exports, "__esModule", { value: true });
const game_1 = require("./game");
const renderer_1 = require("./renderer");
const rules_1 = require("./rules");
const types_1 = require("./types");
const game = new game_1.ChessGameImpl();
const boardElement = document.getElementById('board');
const sidebar = document.getElementById('sidebar');
const controls = document.getElementById('controls');
const capturedTrays = document.getElementById('captured-trays');
const moveList = document.getElementById('move-list');
const banner = document.getElementById('game-banner');
const promotionOverlay = document.getElementById('promotion-overlay');
let flipped = false;
let selectedSquare = null;
let legalDestinations = [];
let playerColor = 'white';
let aiDepth = 3;
let isSoundEnabled = true;
let aiSide = 'black';
let moveInProgress = false;
// Try restore from localStorage
if (!game.loadFromLocalStorage()) {
game.start();
}
// Create AudioContext after user gesture
let audioContext = null;
document.addEventListener('click', () => {
if (!audioContext) {
audioContext = new AudioContext();
}
}, { once: true });
function playSound(type) {
if (!isSoundEnabled || !audioContext)
return;
const oscillator = audioContext.createOscillator();
const gain = audioContext.createGain();
oscillator.connect(gain);
gain.connect(audioContext.destination);
switch (type) {
case 'move':
oscillator.frequency.value = 400;
oscillator.type = 'sine';
gain.gain.value = 0.2;
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.1);
break;
case 'capture':
oscillator.frequency.value = 200;
oscillator.type = 'square';
gain.gain.value = 0.3;
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.2);
break;
case 'check':
oscillator.frequency.value = 600;
oscillator.type = 'sawtooth';
gain.gain.value = 0.2;
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.3);
break;
}
}
function renderBoard() {
boardElement.innerHTML = '';
const legalMoves = game.legalMoves();
for (let rank = 0; rank < 8; rank++) {
for (let file = 0; file < 8; file++) {
const squareIndex = flipped ? file * 8 + rank : rank * 8 + file;
const displayRank = flipped ? 7 - rank : rank;
const displayFile = flipped ? 7 - file : file;
const square = displayFile * 8 + displayRank;
const squareElement = document.createElement('div');
squareElement.className = 'square ' + ((displayFile + displayRank) % 2 === 0 ? 'light' : 'dark');
squareElement.dataset.square = square.toString();
const piece = game.state.board[square];
squareElement.textContent = (0, renderer_1.pieceUnicode)(piece);
// Last move highlight
if (game.state.moveHistory.length > 0) {
const lastMove = game.state.moveHistory[game.state.moveHistory.length - 1];
if (lastMove.from === square || lastMove.to === square) {
squareElement.classList.add('last-move');
}
}
// Selected square
if (selectedSquare === square) {
squareElement.classList.add('selected');
}
// Legal move highlights
if (legalDestinations.includes(square)) {
const move = legalMoves.find(m => m.from === selectedSquare && m.to === square);
if (move && move.capture) {
squareElement.classList.add('legal-ring');
}
else {
squareElement.classList.add('legal-dot');
}
}
// Check highlight
if ((0, rules_1.isCheck)(game.state) && (0, types_1.pieceType)(piece) === 6 && (0, types_1.pieceColor)(piece) === game.state.turn) {
squareElement.classList.add('check');
}
squareElement.addEventListener('click', () => handleClick(square));
boardElement.appendChild(squareElement);
}
}
}
function handleClick(square) {
if (game.state.result !== 'playing')
return;
if (moveInProgress)
return;
if (playerColor === 'both')
return;
if (game.state.turn !== playerColor)
return;
const piece = game.state.board[square];
if (selectedSquare !== null) {
if (legalDestinations.includes(square)) {
const matchingMove = game.legalMoves().find(move => move.from === selectedSquare && move.to === square);
if (matchingMove) {
if (matchingMove.promotion) {
showPromotionPicker(selectedSquare, square);
return;
}
applyMove(matchingMove);
return;
}
}
selectedSquare = null;
legalDestinations = [];
renderBoard();
return;
}
if (piece !== 0 && (0, types_1.pieceColor)(piece) === game.state.turn) {
selectedSquare = square;
legalDestinations = game.legalMoves().filter(move => move.from === square).map(move => move.to);
renderBoard();
return;
}
}
function showPromotionPicker(from, to) {
const square = from;
const squareElement = boardElement.querySelector(`[data-square="${square}"]`);
if (!squareElement)
return;
promotionOverlay.innerHTML = '';
promotionOverlay.style.left = squareElement.offsetLeft + 'px';
promotionOverlay.style.top = squareElement.offsetTop + 'px';
promotionOverlay.hidden = false;
const pieces = game.state.turn === 'white' ? [5, 4, 3, 2] : [-5, -4, -3, -2];
for (const piece of pieces) {
const choice = document.createElement('div');
choice.className = 'promo-choice';
choice.textContent = (0, renderer_1.pieceUnicode)(piece);
choice.addEventListener('click', () => {
const matchingMove = game.legalMoves().find(move => move.from === from && move.to === to && move.promotion === piece);
if (matchingMove) {
matchingMove.promotion = piece;
applyMove(matchingMove);
}
promotionOverlay.hidden = true;
promotionOverlay.innerHTML = '';
});
promotionOverlay.appendChild(choice);
}
}
function applyMove(move) {
const lastMove = game.state.moveHistory[game.state.moveHistory.length - 1];
if (lastMove && lastMove.enPassant) {
const square = lastMove.from >> 3;
game.state.board[square * 8 + (lastMove.to & 7) + 1] = 0;
}
game.applyMove(move);
playSound(move.capture ? 'capture' : 'move');
if ((0, rules_1.isCheck)(game.state)) {
playSound('check');
}
selectedSquare = null;
legalDestinations = [];
renderBoard();
renderMoveList();
renderCapturedPieces();
const result = (0, rules_1.detectGameState)(game.state);
if (result !== 'playing') {
showGameOver(result);
return;
}
if (playerColor !== 'both' && game.state.turn !== playerColor) {
makeAiMove();
}
}
function makeAiMove() {
moveInProgress = true;
renderControls();
const searchResult = game.aiMove(aiDepth, playerColor);
if (searchResult.move) {
applyMove(searchResult.move);
}
moveInProgress = false;
renderControls();
}
function showGameOver(result) {
banner.hidden = false;
switch (result) {
case 'white-mate':
banner.textContent = 'White wins by checkmate!';
break;
case 'black-mate':
banner.textContent = 'Black wins by checkmate!';
break;
case 'stalemate':
banner.textContent = 'Stalemate — draw!';
break;
case 'draw-insufficient':
banner.textContent = 'Draw by insufficient material!';
break;
case 'draw-50-move':
banner.textContent = 'Draw by 50-move rule!';
break;
default:
banner.textContent = 'Game over';
}
}
function renderMoveList() {
moveList.innerHTML = '';
const moves = game.state.moveHistory;
const notation = moves.map((move, index) => {
const piece = game.state.board[move.from];
const squareFrom = ('abcdefgh'[move.from >> 3] + (move.from & 7 + 1));
const squareTo = ('abcdefgh'[move.to >> 3] + (move.to & 7 + 1));
return (0, renderer_1.moveNotation)(piece, (0, types_1.pieceType)(piece), squareFrom, squareTo, move.capture, move.promotion, move.enPassant, move.shortCastle, move.longCastle, (0, rules_1.isCheck)(game.state), game.state.result.includes('mate'));
});
for (let i = 0; i < notation.length; i += 2) {
const entry = document.createElement('div');
entry.className = 'move-entry';
entry.textContent = `${(i / 2 + 1)}. ${notation[i]} ${notation[i + 1] || ''}`;
moveList.appendChild(entry);
}
moveList.scrollTop = moveList.scrollHeight;
}
function renderCapturedPieces() {
capturedTrays.innerHTML = '';
const whiteTray = document.createElement('div');
whiteTray.className = 'tray';
whiteTray.textContent = game.capturedWhite.map(renderer_1.pieceUnicode).join(' ');
const blackTray = document.createElement('div');
blackTray.className = 'tray';
blackTray.textContent = game.capturedBlack.map(renderer_1.pieceUnicode).join(' ');
capturedTrays.appendChild(whiteTray);
capturedTrays.appendChild(blackTray);
}
function renderControls() {
controls.innerHTML = '';
const flipButton = document.createElement('button');
flipButton.textContent = 'Flip Board';
flipButton.addEventListener('click', () => {
flipped = !flipped;
renderBoard();
});
controls.appendChild(flipButton);
const undoButton = document.createElement('button');
undoButton.textContent = 'Undo';
undoButton.addEventListener('click', () => {
game.undo();
selectedSquare = null;
legalDestinations = [];
renderBoard();
renderMoveList();
renderCapturedPieces();
banner.hidden = true;
});
controls.appendChild(undoButton);
const newButton = document.createElement('button');
newButton.textContent = 'New Game';
newButton.addEventListener('click', () => {
game.newGame();
selectedSquare = null;
legalDestinations = [];
renderBoard();
renderMoveList();
renderCapturedPieces();
banner.hidden = true;
});
controls.appendChild(newButton);
const soundButton = document.createElement('button');
soundButton.textContent = isSoundEnabled ? '🔊 Sound On' : '🔇 Sound Off';
soundButton.className = isSoundEnabled ? 'active' : '';
soundButton.addEventListener('click', () => {
isSoundEnabled = !isSoundEnabled;
renderControls();
});
controls.appendChild(soundButton);
const whiteButton = document.createElement('button');
whiteButton.textContent = 'Play White';
whiteButton.className = playerColor === 'white' ? 'active' : '';
whiteButton.addEventListener('click', () => {
playerColor = 'white';
aiSide = 'black';
renderControls();
});
controls.appendChild(whiteButton);
const blackButton = document.createElement('button');
blackButton.textContent = 'Play Black';
blackButton.className = playerColor === 'black' ? 'active' : '';
blackButton.addEventListener('click', () => {
playerColor = 'black';
aiSide = 'white';
renderControls();
});
controls.appendChild(blackButton);
const bothButton = document.createElement('button');
bothButton.textContent = 'Play Both / Analysis';
bothButton.className = playerColor === 'both' ? 'active' : '';
bothButton.addEventListener('click', () => {
playerColor = 'both';
renderControls();
});
controls.appendChild(bothButton);
const depth1Button = document.createElement('button');
depth1Button.textContent = 'Depth 1';
depth1Button.className = aiDepth === 1 ? 'active' : '';
depth1Button.addEventListener('click', () => {
aiDepth = 1;
renderControls();
});
controls.appendChild(depth1Button);
const depth2Button = document.createElement('button');
depth2Button.textContent = 'Depth 2';
depth2Button.className = aiDepth === 2 ? 'active' : '';
depth2Button.addEventListener('click', () => {
aiDepth = 2;
renderControls();
});
controls.appendChild(depth2Button);
const depth3Button = document.createElement('button');
depth3Button.textContent = 'Depth 3';
depth3Button.className = aiDepth === 3 ? 'active' : '';
depth3Button.addEventListener('click', () => {
aiDepth = 3;
renderControls();
});
controls.appendChild(depth3Button);
}
// Expose debug interface on window
window.game = game;
window.debug = {
fen: () => game.fen(),
move: (moveString) => game.move(moveString),
legalMoves: () => game.legalMoves(),
state: () => game.state,
result: () => (0, rules_1.detectGameState)(game.state),
};
game.start();
renderBoard();
renderMoveList();
renderCapturedPieces();
renderControls();
};
// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BLACK_KING = exports.WHITE_KING = exports.BLACK_QUEEN = exports.WHITE_QUEEN = exports.BLACK_ROOK = exports.WHITE_ROOK = exports.BLACK_BISHOP = exports.WHITE_BISHOP = exports.BLACK_KNIGHT = exports.WHITE_KNIGHT = exports.BLACK_PAWN = exports.WHITE_PAWN = exports.EMPTY = void 0;
exports.sq = sq;
exports.squareFile = squareFile;
exports.squareRank = squareRank;
exports.pieceColor = pieceColor;
exports.pieceType = pieceType;
exports.isLegalSquare = isLegalSquare;
exports.sqNotation = sqNotation;
exports.EMPTY = 0;
exports.WHITE_PAWN = 1;
exports.BLACK_PAWN = -1;
exports.WHITE_KNIGHT = 2;
exports.BLACK_KNIGHT = -2;
exports.WHITE_BISHOP = 3;
exports.BLACK_BISHOP = -3;
exports.WHITE_ROOK = 4;
exports.BLACK_ROOK = -4;
exports.WHITE_QUEEN = 5;
exports.BLACK_QUEEN = -5;
exports.WHITE_KING = 6;
exports.BLACK_KING = -6;
function sq(file, rank) {
return file * 8 + rank;
}
function squareFile(square) {
return square >> 3;
}
function squareRank(square) {
return square & 7;
}
function pieceColor(piece) {
if (piece === exports.EMPTY)
return 'white';
return piece > 0 ? 'white' : 'black';
}
function pieceType(piece) {
return Math.abs(piece);
}
function isLegalSquare(square) {
const file = square >> 3;
const rank = square & 7;
return ((file ^ rank) & 1) === 0 && file >= 0 && file <= 7 && rank >= 0 && rank <= 7;
}
function sqNotation(square) {
return `${'abcdefgh'[squareFile(square)]}${squareRank(square) + 1}`;
}
};
// ── module: src/position.ts ──
__mods["src/position.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.newInitialPosition = newInitialPosition;
const types_1 = require("./types");
function newInitialPosition() {
const board = new Array(64).fill(types_1.EMPTY);
// White pieces
board[0] = types_1.WHITE_ROOK;
board[1] = types_1.WHITE_KNIGHT;
board[2] = types_1.WHITE_BISHOP;
board[3] = types_1.WHITE_QUEEN;
board[4] = types_1.WHITE_KING;
board[5] = types_1.WHITE_BISHOP;
board[6] = types_1.WHITE_KNIGHT;
board[7] = types_1.WHITE_ROOK;
for (let file = 0; file < 8; file++)
board[file * 8 + 1] = types_1.WHITE_PAWN;
// Black pieces
board[64 + 0] = types_1.BLACK_ROOK;
board[64 + 1] = types_1.BLACK_KNIGHT;
board[64 + 2] = types_1.BLACK_BISHOP;
board[64 + 3] = types_1.BLACK_QUEEN;
board[64 + 4] = types_1.BLACK_KING;
board[64 + 5] = types_1.BLACK_BISHOP;
board[64 + 6] = types_1.BLACK_KNIGHT;
board[64 + 7] = types_1.BLACK_ROOK;
for (let file = 0; file < 8; file++)
board[file * 8 + 6] = types_1.BLACK_PAWN;
return {
board,
turn: 'white',
enPassantSquare: null,
whiteShortCastle: true,
whiteLongCastle: true,
blackShortCastle: true,
blackLongCastle: true,
moveHistory: [],
fiftyMoveClock: 0,
result: 'playing',
};
}
};
// ── module: src/rules.ts ──
__mods["src/rules.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.filterLegalMoves = filterLegalMoves;
exports.moveLeavesKingInCheck = moveLeavesKingInCheck;
exports.isCheck = isCheck;
exports.findKing = findKing;
exports.isSquareAttacked = isSquareAttacked;
exports.detectGameState = detectGameState;
const types_1 = require("./types");
function filterLegalMoves(state) {
return generateAllMoves(state).filter((move) => !moveLeavesKingInCheck(state, move));
}
function generateAllMoves(state) {
const board = state.board;
const moves = [];
const turn = state.turn;
for (let square = 0; square < 64; square++) {
const piece = board[square];
if ((0, types_1.pieceColor)(piece) !== turn)
continue;
const type = (0, types_1.pieceType)(piece);
switch (type) {
case 1:
generatePawnMoves(board, square, turn, state.enPassantSquare, moves);
break;
case 2:
generateKnightMoves(board, square, moves);
break;
case 3:
generateSlidingMoves(board, square, [[-1, -1], [-1, 1], [1, -1], [1, 1]], moves);
break;
case 4:
generateSlidingMoves(board, square, [[0, 1], [0, -1], [1, 0], [-1, 0]], moves);
break;
case 5:
generateSlidingMoves(board, square, [[-1, -1], [-1, 1], [1, -1], [1, 1], [0, 1], [0, -1], [1, 0], [-1, 0]], moves);
break;
case 6:
generateKingMoves(board, square, turn, state, moves);
break;
}
}
return moves;
}
function generateSlidingMoves(board, from, directions, moves) {
const piece = board[from];
const file = (0, types_1.squareFile)(from);
const rank = (0, types_1.squareRank)(from);
const friendlyColor = (0, types_1.pieceColor)(piece);
for (const [dFile, dRank] of directions) {
let f = file + dFile;
let r = rank + dRank;
while (f >= 0 && f < 8 && r >= 0 && r < 8) {
const square = (0, types_1.sq)(f, r);
const target = board[square];
const targetColor = (0, types_1.pieceColor)(target);
if (targetColor === friendlyColor)
break;
moves.push({
from,
to: square,
enPassant: false,
promotion: null,
shortCastle: false,
longCastle: false,
pawnFile: null,
pawnRank: null,
capture: target,
});
if (targetColor !== friendlyColor)
break;
f += dFile;
r += dRank;
}
}
}
function generateKnightMoves(board, from, moves) {
const file = (0, types_1.squareFile)(from);
const rank = (0, types_1.squareRank)(from);
const piece = board[from];
const friendlyColor = (0, types_1.pieceColor)(piece);
for (const [dFile, dRank] of [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]]) {
const f = file + dFile;
const r = rank + dRank;
if (f < 0 || f > 7 || r < 0 || r > 7)
continue;
const square = (0, types_1.sq)(f, r);
const target = board[square];
const targetColor = (0, types_1.pieceColor)(target);
if (targetColor === friendlyColor)
continue;
moves.push({
from,
to: square,
enPassant: false,
promotion: null,
shortCastle: false,
longCastle: false,
pawnFile: null,
pawnRank: null,
capture: target,
});
}
}
function generatePawnMoves(board, from, color, enPassantSquare, moves) {
const file = (0, types_1.squareFile)(from);
const rank = (0, types_1.squareRank)(from);
const direction = color === 'white' ? 1 : -1;
const promotionRank = color === 'white' ? 7 : 0;
const startRank = color === 'white' ? 1 : 6;
const forward = (0, types_1.sq)(file, rank + direction);
const forwardSquare = rank + direction;
const forward2Square = rank + 2 * direction;
if ((0, types_1.isLegalSquare)(forward)) {
if (board[forward] === types_1.EMPTY) {
if (forwardSquare === promotionRank) {
for (const promoPiece of [5, 4, 3, 2]) {
const sign = color === 'white' ? 1 : -1;
moves.push({
from,
to: forward,
enPassant: false,
promotion: promoPiece * sign,
shortCastle: false,
longCastle: false,
pawnFile: file,
pawnRank: rank,
capture: null,
});
}
}
else {
moves.push({
from,
to: forward,
enPassant: false,
promotion: null,
shortCastle: false,
longCastle: false,
pawnFile: file,
pawnRank: rank,
capture: null,
});
if (rank === startRank && (0, types_1.isLegalSquare)((0, types_1.sq)(file, forward2Square)) && board[(0, types_1.sq)(file, forward2Square)] === types_1.EMPTY) {
moves.push({
from,
to: (0, types_1.sq)(file, forward2Square),
enPassant: false,
promotion: null,
shortCastle: false,
longCastle: false,
pawnFile: file,
pawnRank: rank,
capture: null,
});
}
}
}
}
for (const captureFile of [file - 1, file + 1]) {
if (captureFile < 0 || captureFile > 7)
continue;
const captureSquare = (0, types_1.sq)(captureFile, forwardSquare);
if (!(0, types_1.isLegalSquare)(captureSquare))
continue;
const targetPiece = board[captureSquare];
const oppositeColorPawn = targetPiece !== types_1.EMPTY && (0, types_1.pieceColor)(targetPiece) !== color;
if (oppositeColorPawn) {
if (forwardSquare === promotionRank) {
for (const promoPiece of [5, 4, 3, 2]) {
const sign = color === 'white' ? 1 : -1;
moves.push({
from,
to: captureSquare,
enPassant: false,
promotion: promoPiece * sign,
shortCastle: false,
longCastle: false,
pawnFile: file,
pawnRank: rank,
capture: targetPiece,
});
}
}
else {
moves.push({
from,
to: captureSquare,
enPassant: false,
promotion: null,
shortCastle: false,
longCastle: false,
pawnFile: file,
pawnRank: rank,
capture: targetPiece,
});
}
}
if (enPassantSquare === captureSquare) {
moves.push({
from,
to: captureSquare,
enPassant: true,
promotion: null,
shortCastle: false,
longCastle: false,
pawnFile: file,
pawnRank: rank,
capture: color === 'white' ? -1 : 1,
});
}
}
}
function generateKingMoves(board, from, color, state, moves) {
const file = (0, types_1.squareFile)(from);
const rank = (0, types_1.squareRank)(from);
for (const [dFile, dRank] of [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]) {
const f = file + dFile;
const r = rank + dRank;
if (f < 0 || f > 7 || r < 0 || r > 7)
continue;
const square = (0, types_1.sq)(f, r);
const target = board[square];
const targetColor = (0, types_1.pieceColor)(target);
if (targetColor === color)
continue;
moves.push({
from,
to: square,
enPassant: false,
promotion: null,
shortCastle: false,
longCastle: false,
pawnFile: null,
pawnRank: null,
capture: target,
});
}
if (color === 'white') {
if (state.whiteShortCastle) {
const kingFrom = (0, types_1.sq)(4, 0);
const kingTo = (0, types_1.sq)(6, 0);
if (board[kingTo] === types_1.EMPTY && board[(0, types_1.sq)(5, 0)] === types_1.EMPTY) {
moves.push({
from: kingFrom,
to: kingTo,
enPassant: false,
promotion: null,
shortCastle: true,
longCastle: false,
pawnFile: null,
pawnRank: null,
capture: null,
});
}
}
if (state.whiteLongCastle) {
const kingFrom = (0, types_1.sq)(4, 0);
const kingTo = (0, types_1.sq)(2, 0);
if (board[kingTo] === types_1.EMPTY && board[(0, types_1.sq)(3, 0)] === types_1.EMPTY && board[(0, types_1.sq)(1, 0)] === types_1.EMPTY) {
moves.push({
from: kingFrom,
to: kingTo,
enPassant: false,
promotion: null,
shortCastle: false,
longCastle: true,
pawnFile: null,
pawnRank: null,
capture: null,
});
}
}
}
else {
if (state.blackShortCastle) {
const kingFrom = (0, types_1.sq)(4, 7);
const kingTo = (0, types_1.sq)(6, 7);
if (board[kingTo] === types_1.EMPTY && board[(0, types_1.sq)(5, 7)] === types_1.EMPTY) {
moves.push({
from: kingFrom,
to: kingTo,
enPassant: false,
promotion: null,
shortCastle: true,
longCastle: false,
pawnFile: null,
pawnRank: null,
capture: null,
});
}
}
if (state.blackLongCastle) {
const kingFrom = (0, types_1.sq)(4, 7);
const kingTo = (0, types_1.sq)(2, 7);
if (board[kingTo] === types_1.EMPTY && board[(0, types_1.sq)(3, 7)] === types_1.EMPTY && board[(0, types_1.sq)(1, 7)] === types_1.EMPTY) {
moves.push({
from: kingFrom,
to: kingTo,
enPassant: false,
promotion: null,
shortCastle: false,
longCastle: true,
pawnFile: null,
pawnRank: null,
capture: null,
});
}
}
}
}
function moveLeavesKingInCheck(state, move) {
const next = copyState(state);
applyMoveToState(next, move);
return isCheck(next);
}
function isCheck(state) {
const kingSquare = findKing(state.board, state.turn);
if (kingSquare < 0)
return false;
return isSquareAttacked(state.board, kingSquare, state.turn);
}
function findKing(board, color) {
const king = color === 'white' ? types_1.WHITE_KING : types_1.BLACK_KING;
for (let square = 0; square < 64; square++) {
if (board[square] === king)
return square;
}
return -1;
}
function isSquareAttacked(board, square, color) {
return !!generateAttackingMoves(board, square).find(move => (0, types_1.pieceColor)(board[move.from]) !== color);
}
function generateAttackingMoves(board, square) {
const moves = [];
const rank = (square & 7);
const file = (square >> 3);
// Pawn attacks
const pawnAttackDirections = [[-1, -1], [-1, 1], [1, -1], [1, 1]];
for (const [dFile, dRank] of pawnAttackDirections) {
const f = file + dFile;
const r = rank + dRank;
if (f >= 0 && f < 8 && r >= 0 && r < 8) {
const from = f * 8 + r;
const piece = board[from];
if ((0, types_1.pieceType)(piece) === 1 && (0, types_1.pieceColor)(piece) === 'white' && dRank > 0 || (0, types_1.pieceType)(piece) === 1 && (0, types_1.pieceColor)(piece) === 'black' && dRank < 0) {
moves.push({ from, to: square, enPassant: false, promotion: null, shortCastle: false, longCastle: false, pawnFile: null, pawnRank: null, capture: board[square] });
}
}
}
// Knight attacks
const knightMoves = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]];
for (const [dFile, dRank] of knightMoves) {
const f = file + dFile;
const r = rank + dRank;
if (f >= 0 && f < 8 && r >= 0 && r < 8) {
const from = f * 8 + r;
if ((0, types_1.pieceType)(board[from]) === 2) {
moves.push({ from, to: square, enPassant: false, promotion: null, shortCastle: false, longCastle: false, pawnFile: null, pawnRank: null, capture: board[square] });
}
}
}
// King attacks
const kingMoves = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]];
for (const [dFile, dRank] of kingMoves) {
const f = file + dFile;
const r = rank + dRank;
if (f >= 0 && f < 8 && r >= 0 && r < 8) {
const from = f * 8 + r;
if ((0, types_1.pieceType)(board[from]) === 6) {
moves.push({ from, to: square, enPassant: false, promotion: null, shortCastle: false, longCastle: false, pawnFile: null, pawnRank: null, capture: board[square] });
}
}
}
// Sliding piece attacks
const slidingDirections = [
{ types: [3, 5], directions: [[-1, -1], [-1, 1], [1, -1], [1, 1]] },
{ types: [4, 5], directions: [[0, 1], [0, -1], [1, 0], [-1, 0]] },
];
for (const sliding of slidingDirections) {
for (const [dFile, dRank] of sliding.directions) {
let f = file + dFile;
let r = rank + dRank;
while (f >= 0 && f < 8 && r >= 0 && r < 8) {
const from = f * 8 + r;
const piece = board[from];
if ((0, types_1.pieceType)(piece) === 0) {
f += dFile;
r += dRank;
continue;
}
if (sliding.types.includes((0, types_1.pieceType)(piece))) {
moves.push({ from, to: square, enPassant: false, promotion: null, shortCastle: false, longCastle: false, pawnFile: null, pawnRank: null, capture: board[square] });
}
break;
}
}
}
return moves;
}
function applyMoveToState(state, move) {
state.board[move.to] = state.board[move.from];
state.board[move.from] = types_1.EMPTY;
if (move.enPassant) {
const rank = (move.to & 7);
state.board[(rank - 1) + ((move.to >> 3) * 8)] = types_1.EMPTY;
}
if (move.promotion) {
state.board[move.to] = move.promotion;
}
if (move.shortCastle) {
const rank = (move.to & 7);
const file = (move.to >> 3);
state.board[(file - 1) * 8 + rank] = state.board[(file + 1) * 8 + rank];
state.board[(file + 1) * 8 + rank] = types_1.EMPTY;
}
if (move.longCastle) {
const rank = (move.to & 7);
const file = (move.to >> 3);
state.board[(file + 1) * 8 + rank] = state.board[(file - 2) * 8 + rank];
state.board[(file - 2) * 8 + rank] = types_1.EMPTY;
}
const nextTurn = state.turn === 'white' ? 'black' : 'white';
state.turn = nextTurn;
if (move.pawnFile !== null && move.pawnRank !== null) {
if (Math.abs((move.to >> 3) - move.pawnFile) === 0 && Math.abs((move.to & 7) - move.pawnRank) === 2) {
state.enPassantSquare = move.pawnFile * 8 + ((move.to & 7) + move.pawnRank) / 2;
}
else {
state.enPassantSquare = null;
}
}
else {
state.enPassantSquare = null;
}
state.fiftyMoveClock = (move.pawnFile !== null || move.capture !== null || move.promotion !== null) ? 0 : state.fiftyMoveClock + 1;
}
function copyState(state) {
return {
board: [...state.board],
turn: state.turn,
enPassantSquare: state.enPassantSquare,
whiteShortCastle: state.whiteShortCastle,
whiteLongCastle: state.whiteLongCastle,
blackShortCastle: state.blackShortCastle,
blackLongCastle: state.blackLongCastle,
moveHistory: [...state.moveHistory],
fiftyMoveClock: state.fiftyMoveClock,
result: state.result,
};
}
function detectGameState(state) {
if (state.fiftyMoveClock >= 50)
return 'draw-50-move';
if (insufficientMaterial(state.board))
return 'draw-insufficient';
const legalMoves = filterLegalMoves(state);
if (legalMoves.length === 0) {
if (isCheck(state)) {
return state.turn === 'white' ? 'black-mate' : 'white-mate';
}
return 'stalemate';
}
return 'playing';
}
function insufficientMaterial(board) {
const pieces = board.filter(piece => piece !== types_1.EMPTY);
const types = pieces.map(types_1.pieceType).sort();
const colors = pieces.map(types_1.pieceColor);
if (types.length === 0)
return true;
if (types.length === 1)
return true;
const whitePieces = pieces.filter(p => (0, types_1.pieceColor)(p) === 'white');
const blackPieces = pieces.filter(p => (0, types_1.pieceColor)(p) === 'black');
if (whitePieces.length === 1 && blackPieces.length === 1)
return true;
if (whitePieces.length === 1 && blackPieces.length === 2) {
const blackTypes = blackPieces.map(types_1.pieceType).sort();
if (blackTypes[1] === 3 || blackTypes[1] === 2)
return true;
}
if (blackPieces.length === 1 && whitePieces.length === 2) {
const whiteTypes = whitePieces.map(types_1.pieceType).sort();
if (whiteTypes[1] === 3 || whiteTypes[1] === 2)
return true;
}
return false;
}
};
// ── module: src/ai.ts ──
__mods["src/ai.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluateState = evaluateState;
exports.findBestMove = findBestMove;
const types_1 = require("./types");
const rules_1 = require("./rules");
const MATERIAL_VALUES = {
pawn: 100,
knight: 320,
bishop: 330,
rook: 500,
queen: 900,
king: 0,
};
const PIECE_SQUARE_TABLES = [
// Pawn tables
[
[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],
],
// Knight tables
[
[-50, -40, -30, -30, -30, -30, -40, -50],
[-40, -20, 0, 0, 0, 0, -20, -40],
[-30, 0, 10, 15, 15, 10, 0, -30],
[-30, 5, 15, 20, 20, 15, 5, -30],
[-30, 0, 15, 20, 20, 15, 0, -30],
[-30, 5, 10, 15, 15, 10, 5, -30],
[-40, -20, 0, 5, 5, 0, -20, -40],
[-50, -40, -30, -30, -30, -30, -40, -50],
],
// Bishop tables
[
[-20, -10, -10, -10, -10, -10, -10, -20],
[-10, 0, 0, 0, 0, 0, 0, -10],
[-10, 0, 5, 10, 10, 5, 0, -10],
[-10, 5, 5, 10, 10, 5, 5, -10],
[-10, 0, 10, 10, 10, 10, 0, -10],
[-10, 10, 10, 10, 10, 10, 10, -10],
[-10, 5, 0, 0, 0, 0, 5, -10],
[-20, -10, -10, -10, -10, -10, -10, -20],
],
// Rook tables
[
[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],
],
// Queen tables
[
[-20, -10, -10, -5, -5, -10, -10, -20],
[-10, 0, 0, 0, 0, 0, 0, -10],
[-10, 0, 5, 5, 5, 5, 0, -10],
[-5, 0, 5, 5, 5, 5, 0, -5],
[0, 0, 5, 5, 5, 5, 0, -5],
[-10, 5, 5, 5, 5, 5, 0, -10],
[-10, 0, 5, 0, 0, 0, 0, -10],
[-20, -10, -10, -5, -5, -10, -10, -20],
],
];
function evaluateState(state) {
const board = state.board;
let score = 0;
for (let square = 0; square < 64; square++) {
const piece = board[square];
if (piece === types_1.EMPTY)
continue;
const type = (0, types_1.pieceType)(piece);
const color = (0, types_1.pieceColor)(piece);
const file = (0, types_1.squareFile)(square);
const rank = (0, types_1.squareRank)(square);
const materialValue = materialValueForPiece(type);
const pstValue = pstValueForPiece(type, file, rank, color);
if (color === 'white') {
score += materialValue + pstValue;
}
else {
score -= materialValue + pstValue;
}
}
return score;
}
function materialValueForPiece(type) {
switch (type) {
case 1: return MATERIAL_VALUES.pawn;
case 2: return MATERIAL_VALUES.knight;
case 3: return MATERIAL_VALUES.bishop;
case 4: return MATERIAL_VALUES.rook;
case 5: return MATERIAL_VALUES.queen;
case 6: return MATERIAL_VALUES.king;
default: return 0;
}
}
function pstValueForPiece(type, file, rank, color) {
if (type === 6)
return 0;
const table = PIECE_SQUARE_TABLES[type - 1];
if (!table)
return 0;
const pstRank = color === 'white' ? rank : 7 - rank;
return table[pstRank][file];
}
function findBestMove(state, depth, side, timeLimit) {
return minimax(state, depth, side, timeLimit);
}
function minimax(state, depth, side, timeLimit) {
const startTime = Date.now();
let bestMove = null;
let bestScore = -Infinity;
let alpha = -Infinity;
let beta = Infinity;
const legalMoves = (0, rules_1.filterLegalMoves)(state);
if (legalMoves.length === 0) {
return { move: null, score: 0 };
}
// Move ordering: captures first
const moves = legalMoves.sort((a, b) => {
const aCaptureValue = a.capture ? materialValueForPiece((0, types_1.pieceType)(a.capture)) : 0;
const bCaptureValue = b.capture ? materialValueForPiece((0, types_1.pieceType)(b.capture)) : 0;
return bCaptureValue - aCaptureValue;
});
for (const move of moves) {
if (timeLimit && Date.now() - startTime > timeLimit) {
return { move: bestMove, score: bestScore };
}
const nextState = applyMoveForSearch(state, move);
const score = alphaBeta(nextState, depth - 1, alpha, beta, !isMaximizing(state), timeLimit, startTime);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
alpha = Math.max(alpha, score);
if (timeLimit && Date.now() - startTime > timeLimit) {
return { move: bestMove, score: bestScore };
}
}
return { move: bestMove, score: bestScore };
}
function alphaBeta(state, depth, alpha, beta, isMax, timeLimit, startTime) {
if (timeLimit && startTime && Date.now() - startTime > timeLimit) {
return evaluateState(state);
}
if (depth === 0) {
return quiescence(state, alpha, beta, isMax, timeLimit, startTime);
}
const legalMoves = (0, rules_1.filterLegalMoves)(state);
if (legalMoves.length === 0) {
return evaluateState(state);
}
const moves = legalMoves.sort((a, b) => {
const aCaptureValue = a.capture ? materialValueForPiece((0, types_1.pieceType)(a.capture)) : 0;
const bCaptureValue = b.capture ? materialValueForPiece((0, types_1.pieceType)(b.capture)) : 0;
return bCaptureValue - aCaptureValue;
});
if (isMax) {
let value = -Infinity;
for (const move of moves) {
const nextState = applyMoveForSearch(state, move);
value = Math.max(value, alphaBeta(nextState, depth - 1, alpha, beta, false, timeLimit, startTime));
alpha = Math.max(alpha, value);
if (timeLimit && startTime && Date.now() - startTime > timeLimit) {
return value;
}
if (beta <= alpha)
break;
}
return value;
}
else {
let value = Infinity;
for (const move of moves) {
const nextState = applyMoveForSearch(state, move);
value = Math.min(value, alphaBeta(nextState, depth - 1, alpha, beta, true, timeLimit, startTime));
beta = Math.min(beta, value);
if (timeLimit && startTime && Date.now() - startTime > timeLimit) {
return value;
}
if (beta <= alpha)
break;
}
return value;
}
}
function quiescence(state, alpha, beta, isMax, timeLimit, startTime) {
const currentValue = evaluateState(state);
if (isMax) {
if (currentValue >= beta)
return currentValue;
if (currentValue > alpha)
alpha = currentValue;
}
else {
if (currentValue <= alpha)
return currentValue;
if (currentValue < beta)
beta = currentValue;
}
const legalMoves = (0, rules_1.filterLegalMoves)(state).filter(move => move.capture);
if (isMax) {
for (const move of legalMoves) {
const nextState = applyMoveForSearch(state, move);
const score = quiescence(nextState, alpha, beta, false, timeLimit, startTime);
if (score > alpha)
alpha = score;
if (timeLimit && startTime && Date.now() - startTime > timeLimit) {
return alpha;
}
if (beta <= alpha)
break;
}
return alpha;
}
else {
for (const move of legalMoves) {
const nextState = applyMoveForSearch(state, move);
const score = quiescence(nextState, alpha, beta, true, timeLimit, startTime);
if (score < beta)
beta = score;
if (timeLimit && startTime && Date.now() - startTime > timeLimit) {
return beta;
}
if (beta <= alpha)
break;
}
return beta;
}
}
function applyMoveForSearch(state, move) {
const next = { ...state, board: [...state.board], moveHistory: [...state.moveHistory] };
next.board[move.to] = next.board[move.from];
next.board[move.from] = types_1.EMPTY;
if (move.enPassant) {
const rank = (move.to & 7);
next.board[(rank - 1) + ((move.to >> 3) * 8)] = types_1.EMPTY;
}
if (move.promotion) {
next.board[move.to] = move.promotion;
}
if (move.shortCastle) {
const rank = (move.to & 7);
const file = (move.to >> 3);
next.board[(file - 1) * 8 + rank] = next.board[(file + 1) * 8 + rank];
next.board[(file + 1) * 8 + rank] = types_1.EMPTY;
}
if (move.longCastle) {
const rank = (move.to & 7);
const file = (move.to >> 3);
next.board[(file + 1) * 8 + rank] = next.board[(file - 2) * 8 + rank];
next.board[(file - 2) * 8 + rank] = types_1.EMPTY;
}
next.turn = state.turn === 'white' ? 'black' : 'white';
if (move.pawnFile !== null && move.pawnRank !== null) {
if (Math.abs((move.to >> 3) - move.pawnFile) === 0 && Math.abs((move.to & 7) - move.pawnRank) === 2) {
next.enPassantSquare = move.pawnFile * 8 + move.pawnRank + 1;
}
else {
next.enPassantSquare = null;
}
}
else {
next.enPassantSquare = null;
}
next.fiftyMoveClock = (move.pawnFile !== null || move.capture !== null || move.promotion !== null) ? 0 : state.fiftyMoveClock + 1;
return next;
}
function isMaximizing(state) {
return state.turn === 'white';
}
};
// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChessGameImpl = void 0;
const types_1 = require("./types");
const position_1 = require("./position");
const rules_1 = require("./rules");
const ai_1 = require("./ai");
class ChessGameImpl {
constructor() {
this.state = (0, position_1.newInitialPosition)();
this.capturedWhite = [];
this.capturedBlack = [];
}
start() {
this.state = (0, position_1.newInitialPosition)();
this.capturedWhite = [];
this.capturedBlack = [];
this.saveToLocalStorage();
}
fen() {
let fen = '';
for (let rank = 7; rank >= 0; rank--) {
let emptyCount = 0;
for (let file = 0; file < 8; file++) {
const square = (0, types_1.sq)(file, rank);
const piece = this.state.board[square];
if (piece === types_1.EMPTY) {
emptyCount++;
}
else {
if (emptyCount > 0) {
fen += emptyCount.toString();
emptyCount = 0;
}
fen += pieceToChar(piece);
}
}
if (emptyCount > 0) {
fen += emptyCount.toString();
}
if (rank > 0) {
fen += '/';
}
}
fen += ' ' + this.state.turn;
fen += ' ' + this.castlingString();
fen += ' ' + (this.state.enPassantSquare ? (0, types_1.sqNotation)(this.state.enPassantSquare) : '-');
fen += ' ' + this.state.fiftyMoveClock;
fen += ' 1';
return fen;
}
castlingString() {
let result = '';
if (this.state.whiteShortCastle)
result += 'K';
if (this.state.whiteLongCastle)
result += 'Q';
if (this.state.blackShortCastle)
result += 'k';
if (this.state.blackLongCastle)
result += 'q';
return result || '-';
}
move(moveString) {
const legalMoves = (0, rules_1.filterLegalMoves)(this.state);
const move = legalMoves.find(m => {
const notation = (0, types_1.sqNotation)(m.from) + (0, types_1.sqNotation)(m.to) + (m.promotion ? pieceToChar(m.promotion).toUpperCase() : '');
return notation.toLowerCase() === moveString.toLowerCase();
});
if (!move)
return null;
this.applyMove(move);
return move;
}
applyMove(move) {
const board = this.state.board;
const piece = board[move.from];
if (move.capture) {
if ((0, types_1.pieceColor)(move.capture) === 'white') {
this.capturedWhite.push(move.capture);
}
else {
this.capturedBlack.push(move.capture);
}
}
board[move.to] = piece;
board[move.from] = types_1.EMPTY;
if (move.enPassant) {
const rank = (move.to & 7);
board[(rank - 1) + ((move.to >> 3) * 8)] = types_1.EMPTY;
}
if (move.promotion) {
board[move.to] = move.promotion;
}
if (move.shortCastle) {
const rank = (move.to & 7);
const file = (move.to >> 3);
board[(file - 1) * 8 + rank] = board[(file + 1) * 8 + rank];
board[(file + 1) * 8 + rank] = types_1.EMPTY;
}
if (move.longCastle) {
const rank = (move.to & 7);
const file = (move.to >> 3);
board[(file + 1) * 8 + rank] = board[(file - 2) * 8 + rank];
board[(file - 2) * 8 + rank] = types_1.EMPTY;
}
if ((0, types_1.pieceType)(piece) === 6) {
if (this.state.turn === 'white') {
this.state.whiteShortCastle = false;
this.state.whiteLongCastle = false;
}
else {
this.state.blackShortCastle = false;
this.state.blackLongCastle = false;
}
}
if ((0, types_1.pieceType)(piece) === 4) {
if (piece === 4) {
this.state.whiteShortCastle = false;
this.state.whiteLongCastle = false;
}
}
this.state.turn = this.state.turn === 'white' ? 'black' : 'white';
if (move.pawnFile !== null && move.pawnRank !== null) {
if (Math.abs((move.to >> 3) - move.pawnFile) === 0 && Math.abs((move.to & 7) - move.pawnRank) === 2) {
this.state.enPassantSquare = move.pawnFile * 8 + move.pawnRank + 1;
}
else {
this.state.enPassantSquare = null;
}
}
else {
this.state.enPassantSquare = null;
}
this.state.fiftyMoveClock = (move.pawnFile !== null || move.capture !== null || move.promotion !== null) ? 0 : this.state.fiftyMoveClock + 1;
this.state.moveHistory.push(move);
const result = (0, rules_1.detectGameState)(this.state);
this.state.result = result;
this.saveToLocalStorage();
}
legalMoves() {
return (0, rules_1.filterLegalMoves)(this.state);
}
undo() {
if (this.state.moveHistory.length < 2) {
this.state.moveHistory = [];
this.state = (0, position_1.newInitialPosition)();
this.capturedWhite = [];
this.capturedBlack = [];
}
else {
this.state.moveHistory.pop();
this.state.moveHistory.pop();
this.state = (0, position_1.newInitialPosition)();
const capturedPieces = [...this.capturedWhite, ...this.capturedBlack];
for (const move of this.state.moveHistory) {
this.applyMove(move);
}
this.capturedWhite = capturedPieces;
this.capturedBlack = [];
}
this.saveToLocalStorage();
}
newGame() {
this.start();
}
aiMove(depth, side, timeLimit) {
return (0, ai_1.findBestMove)(this.state, depth, side, timeLimit);
}
saveToLocalStorage() {
localStorage.setItem('chess-game-state', JSON.stringify({
state: this.state,
capturedWhite: this.capturedWhite,
capturedBlack: this.capturedBlack,
}));
}
loadFromLocalStorage() {
const saved = localStorage.getItem('chess-game-state');
if (!saved)
return false;
const data = JSON.parse(saved);
this.state = data.state;
this.capturedWhite = data.capturedWhite;
this.capturedBlack = data.capturedBlack;
return true;
}
}
exports.ChessGameImpl = ChessGameImpl;
function pieceToChar(piece) {
switch (piece) {
case types_1.EMPTY: return '.';
case 1: return 'P';
case -1: return 'p';
case 2: return 'N';
case -2: return 'n';
case 3: return 'B';
case -3: return 'b';
case 4: return 'R';
case -4: return 'r';
case 5: return 'Q';
case -5: return 'q';
case 6: return 'K';
case -6: return 'k';
default: return '?';
}
}
};
// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pieceUnicode = pieceUnicode;
exports.moveNotation = moveNotation;
function pieceUnicode(piece) {
switch (piece) {
case 0: return '';
case 1: return '♙';
case -1: return '♟';
case 2: return '♘';
case -2: return '♞';
case 3: return '♗';
case -3: return '♝';
case 4: return '♖';
case -4: return '♜';
case 5: return '♕';
case -5: return '♛';
case 6: return '♔';
case -6: return '♚';
default: return '?';
}
}
function moveNotation(piece, pieceType, squareFrom, squareTo, capture, promotion, enPassant, shortCastle, longCastle, check, mate) {
if (shortCastle)
return 'O-O' + (check && !mate ? '+' : '') + (mate ? '#' : '');
if (longCastle)
return 'O-O-O' + (check && !mate ? '+' : '') + (mate ? '#' : '');
let notation = '';
if (pieceType === 1) {
notation = capture ? squareFrom[0] + 'x' + squareTo : squareTo;
if (enPassant)
notation += ' e.p.';
if (promotion) {
notation += '=' + pieceUnicode(promotion).toUpperCase();
}
}
else {
notation = pieceUnicode(piece).toUpperCase() + (capture ? 'x' : '') + squareTo;
}
if (check && !mate)
notation += '+';
if (mate)
notation += '#';
return notation;
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"bartowski/ProCreations_grug-27b-GGUF-IQ4","provider":"brigid","persona":"typescript","sessionId":"aa6ac0ee-e8c6-497f-be60-8f542cfe3bbe","tokensIn":2752943,"tokensOut":33169,"tokensTotal":2786112,"cost":0,"turns":78,"toolCalls":77,"failedToolCalls":3,"timestamp":"2026-08-05T17:15:13.414Z"} -->