import { describe, it, expect } from 'vitest'; import { chess } from '../server/games/chess.mjs'; import { checkers } from '../server/games/checkers.mjs'; import { getGame } from '../server/games/registry.mjs'; import { parchis } from '../server/games/parchis.mjs'; describe('ajedrez: reglas en el servidor', () => { it('rechaza movimientos ilegales y jugar fuera de turno', () => { expect(() => chess.apply(chess.create(), { type: 'move', from: 'e2', to: 'e5' }, 0), ).toThrow(); expect(chess.legal(chess.create(), 1)).toEqual([]); expect(() => chess.apply(chess.create(), { type: 'move', from: 'e7', to: 'e5' }, 1), ).toThrow(); }); it('detecta el mate y preserva el historial para reconstruir la partida', () => { let state = chess.create(); let result; for (const [from, to, seat] of [ ['f2', 'f3', 0], ['e7', 'e5', 1], ['g2', 'g4', 0], ['d8', 'h4', 1], ] as const) { const move = chess.apply(state, { type: 'move', from, to }, seat); state = move.state; result = move.result; } expect(result).toEqual({ winner: 1, reason: 'jaque mate' }); expect(chess.view(state).pgn).toContain('Qh4#'); }); it('conserva la repetición triple al reconstruir desde movimientos', () => { let state = chess.create(); let result; for (let round = 0; round < 2; round++) for (const [from, to, seat] of [ ['g1', 'f3', 0], ['g8', 'f6', 1], ['f3', 'g1', 0], ['f6', 'g8', 1], ] as const) { const applied = chess.apply(state, { type: 'move', from, to }, seat); state = applied.state; result = applied.result; } expect(result?.winner).toBeNull(); }); }); describe('damas con recorrido largo', () => { it('aplica las correcciones también a las partidas ya creadas', () => { expect(getGame('damas', '1.0.0')).toBe(checkers); expect(getGame('damas', '2.0.0')).toBe(checkers); }); const position = () => ({ board: Array(64).fill(0), turn: 0, chain: null, quietMoves: 0, }); it('fuerza capturas y encadena saltos con la misma pieza', () => { const state = position(); state.board[42] = 1; state.board[33] = 2; state.board[17] = 2; state.board[46] = 1; expect(checkers.legal(state, 0)).toEqual([ { type: 'move', from: 42, to: 24, capture: 33 }, ]); const first = checkers.apply(state, { type: 'move', from: 42, to: 24 }, 0); expect(first.state.turn).toBe(0); expect(first.state.chain).toBe(24); expect(() => checkers.apply(first.state, { type: 'move', from: 46, to: 37 }, 0), ).toThrow(); const next = checkers.apply( first.state, { type: 'move', from: 24, to: 10 }, 0, ); expect(next.state.board[17]).toBe(0); expect(next.result?.winner).toBe(0); }); it('la dama recorre diagonales completas y una ficha propia bloquea el paso', () => { const state = position(); state.board[42] = -1; expect(checkers.legal(state, 0).map((m) => m.to)).toEqual( expect.arrayContaining([33, 24, 35, 28, 21, 14, 7, 49, 56, 51, 60]), ); state.board[35] = 1; expect( checkers .legal(state, 0) .filter((m) => m.from === 42) .map((m) => m.to), ).not.toEqual(expect.arrayContaining([28])); expect( checkers.legal(state, 0).some((m) => m.from === 42 && m.to === 7), ).toBe(false); }); it('la dama captura a distancia y conserva la pieza en una segunda captura', () => { const state = position(); state.board[42] = -1; state.board[35] = 2; state.board[19] = 2; state.board[5] = 2; expect( checkers .legal(state, 0) .filter((m) => m.capture === 35) .map((m) => m.to), ).toEqual([28, 21, 14, 7]); const first = checkers.apply(state, { type: 'move', from: 42, to: 28 }, 0); expect(first.state.chain).toBe(28); expect(first.state.turn).toBe(0); expect(checkers.legal(first.state, 0)).toEqual([ { type: 'move', from: 28, to: 10, capture: 19 }, { type: 'move', from: 28, to: 1, capture: 19 }, ]); const second = checkers.apply( first.state, { type: 'move', from: 28, to: 1 }, 0, ); expect(second.state.board[35]).toBe(0); expect(second.state.board[19]).toBe(0); expect(second.state.board[1]).toBe(-1); expect(second.state.chain).toBeNull(); expect(second.state.turn).toBe(1); }); it('no permite saltar dos adversarios en un solo salto', () => { const state = position(); state.board[42] = -1; state.board[35] = 2; state.board[28] = 2; expect( checkers .legal(state, 0) .some((m) => m.to === 21 || m.capture !== undefined), ).toBe(false); }); it('termina la captura al coronar y permite que las damas retrocedan', () => { const state = position(); state.board[17] = 1; state.board[10] = 2; state.board[12] = 2; const result = checkers.apply(state, { type: 'move', from: 17, to: 3 }, 0); expect(result.state.board[3]).toBe(-1); expect(result.state.chain).toBeNull(); expect(result.state.turn).toBe(1); expect( checkers.legal({ ...result.state, turn: 0 }, 0).some((m) => m.to > 3), ).toBe(true); }); it('declara tablas por inactividad y rechaza entradas arbitrarias', () => { const state = position(); state.board[42] = -1; state.board[5] = -2; state.quietMoves = 79; expect( checkers.apply(state, { type: 'move', from: 42, to: 33 }, 0).result ?.winner, ).toBeNull(); expect(() => checkers.apply(state, { type: 'move', from: -1, to: 100 }, 0), ).toThrow(); }); }); describe('parchís básico', () => { it('el dado procede del servidor y salir con un cinco es obligatorio', () => { const state = parchis.create(2); const rolled = parchis.apply(state, { type: 'roll', die: 6 }, 0, () => 5); expect(rolled.state.die).toBe(5); rolled.state.pieces[0][1] = 10; expect(parchis.legal(rolled.state, 0).every((a) => a.piece !== 1)).toBe( true, ); expect( parchis.apply(rolled.state, { type: 'move', piece: 0 }, 0).state .pieces[0][0], ).toBe(0); expect(() => parchis.apply(state, { type: 'roll' }, 1, () => 5)).toThrow(); }); it('bloquea el paso a través de barreras', () => { const state = parchis.create(2); state.pieces[0] = [0, 2, 2, -1]; state.die = 4; expect(parchis.legal(state, 0).some((a) => a.piece === 0)).toBe(false); }); it('captura fuera de un seguro y concede 20 pasos', () => { const state = parchis.create(2); state.pieces[0][0] = 20; state.pieces[1][0] = 5; state.die = 2; const result = parchis.apply(state, { type: 'move', piece: 0 }, 0); expect(result.state.pieces[1][0]).toBe(-1); expect(result.state.die).toBe(20); expect(result.state.bonus).toBe(true); }); it('protege los seguros y exige entrada exacta a meta', () => { const state = parchis.create(2); state.pieces[0][0] = 15; state.pieces[1][0] = 0; state.die = 2; expect(parchis.legal(state, 0).some((a) => a.piece === 0)).toBe(false); state.pieces[0][0] = 70; state.die = 2; expect(parchis.legal(state, 0)).toEqual([{ type: 'pass' }]); }); it('acaba al llevar las cuatro fichas a meta y conserva el turno extra', () => { const state = parchis.create(2); state.pieces[0] = [71, 71, 71, 70]; state.die = 1; expect(parchis.apply(state, { type: 'move', piece: 3 }, 0).result).toEqual({ winner: 0, reason: 'cuatro fichas en meta', }); const roll = parchis.apply(parchis.create(2), { type: 'roll' }, 0, () => 6); expect(roll.state.turn).toBe(0); expect(roll.state.die).toBeNull(); }); });