UNPKG

@chess-labs/terminal

Version:

A command-line chess game powered by Stockfish

229 lines โ€ข 8.9 kB
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