@chess-labs/terminal
Version:
A command-line chess game powered by Stockfish
229 lines โข 8.9 kB
JavaScript
import { initGameState, movePiece, getLegalMoves, Color } from '@chess-labs/core';
import { StockfishEngine } from './engine.js';
import chalk from 'chalk';
export class ChessGame {
config;
coreGameState;
engine;
gameState = 'setup';
moveHistory = [];
constructor(config) {
this.config = config;
this.coreGameState = initGameState();
this.engine = new StockfishEngine(config);
}
async initialize() {
console.log(chalk.blue('๐ง Initializing chess engine...'));
try {
await this.engine.start();
console.log(chalk.green('โ
Engine ready!'));
this.gameState = 'playing';
}
catch (error) {
console.error(chalk.red('โ Failed to initialize engine:'), error);
throw error;
}
}
displayBoard() {
console.log(`\n${this.formatBoard()}`);
console.log(`\nCurrent player: ${chalk.bold(this.coreGameState.currentTurn)}`);
if (this.moveHistory.length > 0) {
console.log(`Last move: ${chalk.yellow(this.moveHistory[this.moveHistory.length - 1])}`);
}
}
formatBoard() {
const pieces = {
wp: 'โ',
wr: 'โ',
wn: 'โ',
wb: 'โ',
wq: 'โ',
wk: 'โ',
bp: 'โ๏ธ',
br: 'โ',
bn: 'โ',
bb: 'โ',
bq: 'โ',
bk: 'โ',
};
const isPlayerBlack = this.config.playerColor === 'black';
// Column labels
const colLabels = isPlayerBlack ? 'h g f e d c b a' : 'a b c d e f g h';
let result = ` ${colLabels}\n`;
for (let displayRow = 0; displayRow < 8; displayRow++) {
// Calculate rank number to display
const rankNumber = isPlayerBlack ? displayRow + 1 : 8 - displayRow;
result += `${rankNumber} `;
for (let displayCol = 0; displayCol < 8; displayCol++) {
// Calculate actual board coordinates (180 degree rotation for black player)
const actualRow = isPlayerBlack ? 7 - displayRow : displayRow;
const actualCol = isPlayerBlack ? 7 - displayCol : displayCol;
const piece = this.coreGameState.board[actualRow][actualCol];
if (piece) {
// Map piece types to correct characters
const typeChar = piece.type === 'knight' ? 'n' : piece.type[0];
const pieceKey = `${piece.color === Color.WHITE ? 'w' : 'b'}${typeChar}`;
result += pieces[pieceKey] || '?';
}
else {
// Alternating background for empty squares
result += (actualRow + actualCol) % 2 === 0 ? 'ยท' : ' ';
}
result += ' ';
}
result += `${rankNumber}\n`;
}
result += ` ${colLabels}`;
return result;
}
async makePlayerMove(from, to) {
if (this.gameState !== 'playing') {
return { move: `${from}-${to}`, isValid: false };
}
try {
const fromPos = this.parsePosition(from);
const toPos = this.parsePosition(to);
// Check if move is legal
const legalMoves = getLegalMoves(fromPos, this.coreGameState);
const isLegal = legalMoves.some((move) => move.to.row === toPos.row && move.to.col === toPos.col);
if (!isLegal) {
console.log(chalk.red('โ Illegal move!'));
return { move: `${from}-${to}`, isValid: false };
}
// Execute move
const newGameState = movePiece(fromPos, toPos, this.coreGameState);
if (!newGameState) {
console.log(chalk.red('โ Invalid move!'));
return { move: `${from}-${to}`, isValid: false };
}
this.coreGameState = newGameState;
const moveNotation = `${from}-${to}`;
this.moveHistory.push(moveNotation);
console.log(chalk.green(`โ
Move made: ${moveNotation}`));
return { move: moveNotation, isValid: true };
}
catch (error) {
console.error(chalk.red('โ Invalid move format or position'));
return { move: `${from}-${to}`, isValid: false };
}
}
boardToFen(gameState) {
// Basic FEN conversion - just the board position part
let fen = '';
for (let row = 0; row < 8; row++) {
let emptyCount = 0;
for (let col = 0; col < 8; col++) {
const piece = gameState.board[row][col];
if (piece) {
if (emptyCount > 0) {
fen += emptyCount.toString();
emptyCount = 0;
}
const pieceChar = this.pieceToFenChar(piece);
fen += pieceChar;
}
else {
emptyCount++;
}
}
if (emptyCount > 0) {
fen += emptyCount.toString();
}
if (row < 7)
fen += '/';
}
// Add current turn
fen += ` ${gameState.currentTurn === Color.WHITE ? 'w' : 'b'}`;
// Add castling rights (simplified)
fen += ' KQkq';
// Add en passant target square (simplified)
fen += ' -';
// Add halfmove and fullmove counters (simplified)
fen += ' 0 1';
return fen;
}
pieceToFenChar(piece) {
const chars = {
pawn: 'p',
rook: 'r',
knight: 'n',
bishop: 'b',
queen: 'q',
king: 'k',
};
const char = chars[piece.type] || 'p';
return piece.color === Color.WHITE ? char.toUpperCase() : char;
}
async makeEngineMove() {
const engineColor = this.config.playerColor === 'white' ? Color.BLACK : Color.WHITE;
if (this.gameState !== 'playing' || this.coreGameState.currentTurn !== engineColor) {
return { move: '', isValid: false };
}
try {
console.log(chalk.blue('๐ค Engine is thinking...'));
const fen = this.boardToFen(this.coreGameState);
const engineMove = await this.engine.getBestMove(fen);
if (!engineMove || engineMove === '(none)') {
console.log(chalk.red('โ Engine could not find a move'));
return { move: '', isValid: false };
}
// Parse UCI move format (e.g., "e2e4")
const from = engineMove.substring(0, 2);
const to = engineMove.substring(2, 4);
const fromPos = this.parsePosition(from);
const toPos = this.parsePosition(to);
// Execute engine move
const newGameState = movePiece(fromPos, toPos, this.coreGameState);
if (!newGameState) {
console.log(chalk.red('โ Engine move failed!'));
return { move: '', isValid: false };
}
this.coreGameState = newGameState;
this.moveHistory.push(engineMove);
console.log(chalk.green(`๐ค Engine played: ${engineMove}`));
return { move: engineMove, isValid: true };
}
catch (error) {
console.error(chalk.red('โ Engine move failed:'), error);
return { move: '', isValid: false };
}
}
parsePosition(notation) {
if (notation.length !== 2) {
throw new Error('Invalid position notation');
}
const col = notation.charCodeAt(0) - 97; // 'a' = 0, 'b' = 1, etc.
const row = 8 - Number.parseInt(notation[1]); // '1' = row 7, '8' = row 0
if (col < 0 || col > 7 || row < 0 || row > 7) {
throw new Error('Position out of bounds');
}
return { row, col };
}
formatPosition(pos) {
const col = String.fromCharCode(97 + pos.col); // 0 = 'a', 1 = 'b', etc.
const row = (8 - pos.row).toString(); // row 0 = '8', row 7 = '1'
return col + row;
}
getGameState() {
return this.gameState;
}
getCurrentPlayer() {
return this.coreGameState.currentTurn === Color.WHITE ? 'white' : 'black';
}
getMoveHistory() {
return [...this.moveHistory];
}
getPlayerColor() {
return this.config.playerColor;
}
isPlayerTurn() {
const playerColor = this.config.playerColor === 'white' ? Color.WHITE : Color.BLACK;
return this.coreGameState.currentTurn === playerColor;
}
endGame() {
console.log(chalk.blue('๐ Game ended'));
this.gameState = 'ended';
this.engine.stop();
}
}
//# sourceMappingURL=game.js.map