Skip to main content
Glama

Chess Analysis Assistant

by turlockmike
MIT License
4
15
  • Apple
  • Linux
chess-engine.test.ts8.61 kB
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; import { ChessEngine } from "../chess-engine.js"; import { BestMovesResult, EvaluationResult } from "../chess-engine.js"; describe('ChessEngine', () => { let engine: ChessEngine; beforeAll(async () => { engine = new ChessEngine('/opt/homebrew/bin/stockfish'); await engine.init(); }, 30000); afterAll(async () => { await engine.quit(); }, 30000); it('should initialize successfully', () => { expect(engine.isReady()).toBe(true); }); it('should evaluate starting position', async () => { const startingPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; const evaluation = await engine.evaluatePosition(startingPosition, 1); expect(evaluation).toEqual({ isMate: false, score: expect.closeTo(0, 0.5), moveNumber: undefined }); }, 10000); it('should evaluate position with moves', async () => { const startingPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; const result = await engine.evaluatePosition(startingPosition, 1, 3) as BestMovesResult; expect(result.moves.length).toBe(3); expect(result.position).toBe(startingPosition); expect(result.depth).toBeGreaterThanOrEqual(1); expect(result.nodes).toBeGreaterThan(0); expect(result.time).toBeGreaterThan(0); result.moves.forEach(move => { expect(move).toMatchObject({ move: expect.stringMatching(/^[a-h][1-8][a-h][1-8][qrbn]?/), algebraic: expect.stringMatching(/^[NBRQK]?[a-h]?[1-8]?x?[a-h][1-8](?:=[NBRQ])?[+#]?$/), score: expect.any(Number), mate: null, isDraw: false }); }); }, 10000); it('should evaluate position with single move', async () => { const startingPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; const result = await engine.evaluatePosition(startingPosition, 1, 1) as BestMovesResult; expect(result.moves.length).toBe(1); expect(result.position).toBe(startingPosition); expect(result.depth).toBeGreaterThanOrEqual(1); expect(result.nodes).toBeGreaterThan(0); expect(result.time).toBeGreaterThan(0); const move = result.moves[0]; expect(move).toEqual(expect.objectContaining({ move: expect.stringMatching(/^[a-h][1-8][a-h][1-8][qrbn]?$/), algebraic: expect.stringMatching(/^[NBRQK]?[a-h]?[1-8]?x?[a-h][1-8](?:=[NBRQ])?[+#]?$/), score: expect.any(Number), mate: null, isDraw: false })); }, 10000); it('should evaluate position with returnMoves=0', async () => { const startingPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; const result = await engine.evaluatePosition(startingPosition, 1, 0) as EvaluationResult; expect(result).toEqual(expect.objectContaining({ isMate: false, score: expect.any(Number) })); expect((result as any).moves).toBeUndefined(); }, 10000); it('should detect mate in one', async () => { // Scholar's mate position const mateInOne = 'r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 0 4'; const evaluation = await engine.evaluatePosition(mateInOne, 5); expect(evaluation).toEqual({ isMate: true, score: Infinity, moveNumber: 1 }); }, 15000); it('should reject invalid FEN', async () => { const invalidFen = 'invalid-fen-string'; await expect(engine.evaluatePosition(invalidFen)).rejects.toThrow(); }, 10000); it('should handle depth parameter', async () => { const startingPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; const evaluation = await engine.evaluatePosition(startingPosition, 1); // Depth 1 should return quickly expect(evaluation).toBeDefined(); }, 10000); describe('getBestMoves', () => { it('should get best moves from starting position', async () => { const startingPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; const result = await engine.getBestMoves(startingPosition, { depth: 10, numMoves: 3, timeLimit: 1000 }); expect(result.moves.length).toBeLessThanOrEqual(3); expect(result.position).toBe(startingPosition); expect(result.depth).toBeGreaterThanOrEqual(10); expect(result.nodes).toBeGreaterThan(0); expect(result.time).toBeGreaterThan(0); expect(result.time).toBeLessThanOrEqual(2000); // Allow some buffer over timeLimit result.moves.forEach(move => { expect(move).toEqual({ move: expect.stringMatching(/^[a-h][1-8][a-h][1-8][qrbn]?$/), algebraic: expect.stringMatching(/^[NBRQK]?[a-h]?[1-8]?x?[a-h][1-8](?:=[NBRQ])?[+#]?$/), score: expect.any(Number), mate: null, isDraw: false }); }); }, 15000); it('should handle scores correctly for black moves', async () => { // Position where black is clearly winning (-5 pawns) const blackWinningPosition = 'rnbqkbnr/pppppppp/8/8/8/8/P1P1P1P1/RNBQKBNR b KQkq - 0 1'; // Test direct evaluation const evaluation = await engine.evaluatePosition(blackWinningPosition, 10); expect(evaluation).toMatchObject({ isMate: false, score: expect.any(Number) }); // Score should be negative since Black is winning // Each pawn is worth approximately 1 point, and Black is up 5 pawns const evalScore = (evaluation as EvaluationResult).score; expect(evalScore).toBeLessThan(-4); expect(evalScore).toBeGreaterThan(-6); // Allow some variation but not too much // Test through getBestMoves const result = await engine.getBestMoves(blackWinningPosition, { depth: 10, numMoves: 1 }); expect(result.moves.length).toBe(1); const move = result.moves[0]; // Score should be negative since Black is winning expect(move.score).toBeLessThan(-4); // Black is up 5 pawns expect(move.score).toBeGreaterThan(-6); // Allow some variation but not too much expect(move.mate).toBeNull(); // Should not be a mate }, 15000); it('should handle stalemate position', async () => { // This is a stalemate position const stalematePosition = 'k7/8/1Q6/8/8/8/8/K7 b - - 0 1'; const result = await engine.getBestMoves(stalematePosition); expect(result).toEqual({ moves: [], position: stalematePosition, depth: 0, nodes: 0, time: 0 }); }, 10000); it('should detect mate in one position', async () => { // Position with mate in 1 (Qh7# or Qxf7#) const mateInOnePosition = 'r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 0 1'; const result = await engine.getBestMoves(mateInOnePosition, { depth: 10, numMoves: 1 }); expect(result.moves.length).toBe(1); expect(result.position).toBe(mateInOnePosition); expect(result.depth).toBeGreaterThanOrEqual(10); expect(result.nodes).toBeGreaterThan(0); expect(result.time).toBeGreaterThan(0); const move = result.moves[0]; expect(move).toMatchObject({ move: expect.stringMatching(/^f3[fh][78]/), algebraic: expect.stringMatching(/^Qx?[fh]7#$/), score: expect.any(Number), mate: 1, isDraw: false }); }, 15000); it('should handle custom options', async () => { const startingPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; const result = await engine.getBestMoves(startingPosition, { depth: 5, numMoves: 1, timeLimit: 500 }); expect(result.moves.length).toBe(1); expect(result.position).toBe(startingPosition); expect(result.depth).toBeGreaterThanOrEqual(5); expect(result.nodes).toBeGreaterThan(0); expect(result.time).toBeGreaterThan(0); expect(result.time).toBeLessThanOrEqual(1000); // Allow some buffer const move = result.moves[0]; expect(move).toMatchObject({ move: expect.stringMatching(/^[a-h][1-8][a-h][1-8][qrbn]?/), algebraic: expect.stringMatching(/^[NBRQK]?[a-h]?[1-8]?x?[a-h][1-8](?:=[NBRQ])?[+#]?$/), score: expect.any(Number), mate: null, isDraw: false }); }, 10000); it('should reject invalid FEN', async () => { const invalidFen = 'invalid-fen-string'; await expect(engine.getBestMoves(invalidFen)).rejects.toThrow('Invalid FEN position'); }, 10000); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/turlockmike/chess-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server