import { describe, it, expect } from 'vitest'; import { getLineCandidates, isInBounds, isCellOccupied, getNeighborPositions, findPartInRegion, findPartAtPosition } from '@/samples/boop/utils'; import { createInitialState, BOARD_SIZE, WIN_LENGTH } from '@/samples/boop'; import { createGameContext } from '@/core/game'; import { registry } from '@/samples/boop'; describe('Boop Utils', () => { describe('isInBounds', () => { it('should return true for valid board positions', () => { expect(isInBounds(0, 0)).toBe(true); expect(isInBounds(3, 3)).toBe(true); expect(isInBounds(5, 5)).toBe(true); }); it('should return false for positions outside board', () => { expect(isInBounds(-1, 0)).toBe(false); expect(isInBounds(0, -1)).toBe(false); expect(isInBounds(BOARD_SIZE, 0)).toBe(false); expect(isInBounds(0, BOARD_SIZE)).toBe(false); expect(isInBounds(6, 6)).toBe(false); }); }); describe('getLineCandidates', () => { it('should generate all possible winning lines', () => { const lines = Array.from(getLineCandidates()); // For 8x8 board with WIN_LENGTH=3: // 4 directions × various starting positions expect(lines.length).toBeGreaterThan(0); }); it('should generate lines with correct length', () => { const lines = Array.from(getLineCandidates()); for (const line of lines) { expect(line.length).toBe(WIN_LENGTH); } }); it('should generate horizontal lines', () => { const lines = Array.from(getLineCandidates()); const horizontalLines = lines.filter(line => line.every(([_, y]) => y === line[0][1]) ); expect(horizontalLines.length).toBeGreaterThan(0); // First horizontal line should start at [0,0], [1,0], [2,0] (direction [0,1] means varying x) const firstHorizontal = horizontalLines[0]; expect(firstHorizontal).toEqual([[0, 0], [1, 0], [2, 0]]); }); it('should generate vertical lines', () => { const lines = Array.from(getLineCandidates()); const verticalLines = lines.filter(line => line.every(([x, _]) => x === line[0][0]) ); expect(verticalLines.length).toBeGreaterThan(0); }); it('should generate diagonal lines', () => { const lines = Array.from(getLineCandidates()); const diagonalLines = lines.filter(line => { const [[x1, y1], [x2, y2]] = line; return x1 !== x2 && y1 !== y2; }); expect(diagonalLines.length).toBeGreaterThan(0); }); it('should only include lines that fit within board bounds', () => { const lines = Array.from(getLineCandidates()); for (const line of lines) { for (const [x, y] of line) { expect(isInBounds(x, y)).toBe(true); } } }); }); describe('isCellOccupied', () => { it('should return false for empty cell in initial state', () => { const state = createInitialState(); expect(isCellOccupied(state, 0, 0)).toBe(false); expect(isCellOccupied(state, 3, 3)).toBe(false); }); it('should return true for occupied cell', async () => { const ctx = createGameContext(registry, createInitialState()); // Place a piece via command (need to await) await ctx._commands.run('place 2 2 white kitten'); expect(isCellOccupied(ctx, 2, 2)).toBe(true); }); }); describe('getNeighborPositions', () => { it('should return 8 neighbor positions for center position', () => { const neighbors = Array.from(getNeighborPositions(2, 2)); expect(neighbors.length).toBe(8); const expected = [ [1, 1], [1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2], [3, 3] ]; expect(neighbors).toEqual(expect.arrayContaining(expected)); }); it('should include diagonal neighbors', () => { const neighbors = Array.from(getNeighborPositions(0, 0)); expect(neighbors).toContainEqual([1, 1]); expect(neighbors).toContainEqual([-1, -1]); }); it('should not include the center position itself', () => { const neighbors = Array.from(getNeighborPositions(5, 5)); expect(neighbors).not.toContainEqual([5, 5]); }); }); describe('findPartInRegion', () => { it('should find a piece in the specified region', () => { const state = createInitialState(); // Find a white kitten in white's supply const piece = findPartInRegion(state, 'white', 'kitten'); expect(piece).not.toBeNull(); expect(piece?.player).toBe('white'); expect(piece?.type).toBe('kitten'); expect(piece?.regionId).toBe('white'); }); it('should return null if no matching piece in region', () => { const state = createInitialState(); // No kittens on board initially const piece = findPartInRegion(state, 'board', 'kitten'); expect(piece).toBeNull(); }); it('should filter by player when specified', () => { const state = createInitialState(); const whitePiece = findPartInRegion(state, 'white', 'kitten', 'white'); expect(whitePiece).not.toBeNull(); expect(whitePiece?.player).toBe('white'); const blackPiece = findPartInRegion(state, 'white', 'kitten', 'black'); expect(blackPiece).toBeNull(); }); it('should search all regions when regionId is empty string', () => { const state = createInitialState(); // Find any cat piece const piece = findPartInRegion(state, '', 'cat'); expect(piece).not.toBeNull(); expect(piece?.type).toBe('cat'); }); }); describe('findPartAtPosition', () => { it('should return null for empty position', () => { const state = createInitialState(); expect(findPartAtPosition(state, 0, 0)).toBeNull(); }); it('should find piece at specified position', async () => { const ctx = createGameContext(registry, createInitialState()); // Place a piece await ctx._commands.run('place 3 3 white kitten'); const piece = findPartAtPosition(ctx, 3, 3); expect(piece).not.toBeNull(); expect(piece?.player).toBe('white'); expect(piece?.type).toBe('kitten'); }); it('should work with game context', () => { const ctx = createGameContext(registry, createInitialState()); const piece = findPartAtPosition(ctx, 5, 5); expect(piece).toBeNull(); }); }); });