boardgame-core/tests/samples/boop-utils.test.ts

192 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/data';
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();
});
});
});