Chess on the ATmosphere checkmate.blue
chess
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 190 lines 5.4 kB view raw
1import { describe, it, expect } from 'vitest'; 2import { Chess } from 'chess.js'; 3import { 4 toDests, 5 isPromotion, 6 applyMove, 7 turnColor, 8 lastMoveSquares, 9 gameResult, 10 makePgn, 11} from '$lib/game-logic'; 12 13describe('toDests', () => { 14 it('returns all legal moves from starting position', () => { 15 const chess = new Chess(); 16 const dests = toDests(chess); 17 18 // Knights and pawns can move from start 19 expect(dests.has('e2')).toBe(true); 20 expect(dests.get('e2')).toContain('e4'); 21 expect(dests.get('e2')).toContain('e3'); 22 expect(dests.get('g1')).toContain('f3'); 23 expect(dests.get('g1')).toContain('h3'); 24 }); 25 26 it('returns empty map for checkmate position', () => { 27 const chess = new Chess(); 28 // Scholar's mate 29 chess.move('e4'); chess.move('e5'); 30 chess.move('Qh5'); chess.move('Nc6'); 31 chess.move('Bc4'); chess.move('Nf6'); 32 chess.move('Qxf7'); 33 34 const dests = toDests(chess); 35 expect(dests.size).toBe(0); 36 }); 37 38 it('only includes squares with legal moves', () => { 39 const chess = new Chess(); 40 const dests = toDests(chess); 41 42 // Black pieces cannot move on white's turn 43 expect(dests.has('e7')).toBe(false); 44 // White rook is blocked 45 expect(dests.has('a1')).toBe(false); 46 }); 47}); 48 49describe('isPromotion', () => { 50 it('detects pawn promotion on 7th rank', () => { 51 const chess = new Chess('6k1/3P4/8/8/8/8/8/4K3 w - - 0 1'); 52 expect(isPromotion(chess, 'd7', 'd8')).toBe(true); 53 }); 54 55 it('returns false for normal pawn move', () => { 56 const chess = new Chess(); 57 expect(isPromotion(chess, 'e2', 'e4')).toBe(false); 58 }); 59 60 it('returns false for non-pawn moves', () => { 61 const chess = new Chess(); 62 expect(isPromotion(chess, 'g1', 'f3')).toBe(false); 63 }); 64}); 65 66describe('applyMove', () => { 67 it('applies a valid move and returns true', () => { 68 const chess = new Chess(); 69 expect(applyMove(chess, 'e2', 'e4')).toBe(true); 70 // After e4, the pawn is on e4 (4th rank has the pawn) 71 expect(chess.get('e4')).toBeTruthy(); 72 }); 73 74 it('returns false for illegal move', () => { 75 const chess = new Chess(); 76 // chess.js throws on invalid moves, applyMove should handle this 77 // e2 to e5 is not a legal pawn move 78 expect(() => applyMove(chess, 'e2', 'e5')).toThrow(); 79 }); 80 81 it('applies promotion with piece specified', () => { 82 const chess = new Chess('6k1/3P4/8/8/8/8/8/4K3 w - - 0 1'); 83 expect(applyMove(chess, 'd7', 'd8', 'q')).toBe(true); 84 expect(chess.get('d8')?.type).toBe('q'); 85 }); 86}); 87 88describe('turnColor', () => { 89 it('returns white at start', () => { 90 expect(turnColor(new Chess())).toBe('white'); 91 }); 92 93 it('returns black after one move', () => { 94 const chess = new Chess(); 95 chess.move('e4'); 96 expect(turnColor(chess)).toBe('black'); 97 }); 98}); 99 100describe('lastMoveSquares', () => { 101 it('returns undefined when no moves played', () => { 102 expect(lastMoveSquares(new Chess())).toBeUndefined(); 103 }); 104 105 it('returns from/to of the last move', () => { 106 const chess = new Chess(); 107 chess.move('e4'); 108 expect(lastMoveSquares(chess)).toEqual(['e2', 'e4']); 109 110 chess.move('e5'); 111 expect(lastMoveSquares(chess)).toEqual(['e7', 'e5']); 112 }); 113}); 114 115describe('gameResult', () => { 116 it('returns null when game is not over', () => { 117 expect(gameResult(new Chess())).toBeNull(); 118 }); 119 120 it('detects checkmate by white', () => { 121 const chess = new Chess(); 122 chess.move('e4'); chess.move('e5'); 123 chess.move('Qh5'); chess.move('Nc6'); 124 chess.move('Bc4'); chess.move('Nf6'); 125 chess.move('Qxf7'); 126 127 const result = gameResult(chess); 128 expect(result).not.toBeNull(); 129 expect(result!.result).toBe('1-0'); 130 expect(result!.reason).toBe('checkmate'); 131 }); 132 133 it('detects stalemate', () => { 134 // Stalemate position: black king trapped with no legal moves 135 const chess = new Chess('k7/8/1K6/8/8/8/8/7R b - - 0 1'); 136 // Verify it's actually stalemate 137 if (chess.isStalemate()) { 138 const result = gameResult(chess); 139 expect(result).not.toBeNull(); 140 expect(result!.result).toBe('1/2-1/2'); 141 expect(result!.reason).toBe('stalemate'); 142 } 143 }); 144 145 it('detects insufficient material', () => { 146 // King vs King 147 const chess = new Chess('4k3/8/8/8/8/8/8/4K3 w - - 0 1'); 148 const result = gameResult(chess); 149 expect(result).not.toBeNull(); 150 expect(result!.result).toBe('1/2-1/2'); 151 expect(result!.reason).toBe('insufficient'); 152 }); 153}); 154 155describe('makePgn', () => { 156 it('generates PGN with headers', () => { 157 const chess = new Chess(); 158 chess.move('e4'); 159 chess.move('e5'); 160 161 const pgn = makePgn(chess, 'did:plc:white', 'did:plc:black'); 162 expect(pgn).toContain('[Event "checkmate.blue"]'); 163 expect(pgn).toContain('[Site "https://checkmate.blue"]'); 164 expect(pgn).toContain('[White "did:plc:white"]'); 165 expect(pgn).toContain('[Black "did:plc:black"]'); 166 expect(pgn).toContain('[Result "*"]'); 167 expect(pgn).toContain('1. e4 e5'); 168 }); 169 170 it('sets result when game is over', () => { 171 const chess = new Chess(); 172 chess.move('e4'); chess.move('e5'); 173 chess.move('Qh5'); chess.move('Nc6'); 174 chess.move('Bc4'); chess.move('Nf6'); 175 chess.move('Qxf7'); 176 177 const pgn = makePgn(chess); 178 expect(pgn).toContain('[Result "1-0"]'); 179 }); 180 181 it('uses placeholder when DIDs not provided', () => { 182 const chess = new Chess(); 183 chess.move('e4'); 184 185 const pgn = makePgn(chess); 186 // chess.js defaults to "?" for unset player headers 187 expect(pgn).not.toContain('[White "did:'); 188 expect(pgn).not.toContain('[Black "did:'); 189 }); 190});