import { describe, expect, it } from 'vitest'; import { formedGeometry, badgePlacement } from './formed'; import type { Board } from './board'; const SIZE = 15; // board builds a 15×15 empty board with the given committed tiles placed. function board(tiles: Array<[number, number, string]> = []): Board { const b: Board = []; for (let r = 0; r < SIZE; r++) { const row: (Board[number][number])[] = []; for (let c = 0; c < SIZE; c++) row.push(null); b.push(row); } for (const [r, c, letter] of tiles) b[r][c] = { letter, blank: false }; return b; } const cells = (g: ReturnType) => [...(g?.cells ?? new Set())].sort(); describe('formedGeometry', () => { it('returns null with nothing staged', () => { expect(formedGeometry(board(), [], true)).toBeNull(); }); it('finds a fresh horizontal word with no cross words', () => { const g = formedGeometry(board(), [ { row: 7, col: 7 }, { row: 7, col: 8 }, ], true); expect(g?.main).toEqual({ row: 7, col: 7, dir: 'H', len: 2 }); expect(cells(g)).toEqual(['7,7', '7,8']); }); it('finds a fresh vertical word', () => { const g = formedGeometry(board(), [ { row: 7, col: 7 }, { row: 8, col: 7 }, ], true); expect(g?.main).toEqual({ row: 7, col: 7, dir: 'V', len: 2 }); expect(cells(g)).toEqual(['7,7', '8,7']); }); it('extends a committed tile into the main word (committed cell in the set)', () => { // A committed A at (7,7); a lone tile to its right forms the two-letter main word. const g = formedGeometry(board([[7, 7, 'A']]), [{ row: 7, col: 8 }], true); expect(g?.main).toEqual({ row: 7, col: 7, dir: 'H', len: 2 }); expect(cells(g)).toEqual(['7,7', '7,8']); }); it('collects a cross word through a staged tile, committed cells included', () => { // Committed X above and Y below (7,8) leave a one-cell gap; a horizontal play through it also // completes the vertical X-_-Y cross word. const g = formedGeometry(board([[6, 8, 'X'], [8, 8, 'Y']]), [ { row: 7, col: 7 }, { row: 7, col: 8 }, ], true); expect(g?.main).toEqual({ row: 7, col: 7, dir: 'H', len: 2 }); expect(cells(g)).toEqual(['6,8', '7,7', '7,8', '8,8']); }); it('omits cross words under the single-word rule (multipleWords = false)', () => { // The same board as the cross-word case, but a single-word game: the engine ignores the vertical // X-_-Y run (and it need not be a real word), so the geometry must highlight only the main // (horizontal) word — the reported "БО lights up green in a one-word game" bug. const g = formedGeometry(board([[6, 8, 'X'], [8, 8, 'Y']]), [ { row: 7, col: 7 }, { row: 7, col: 8 }, ], false); expect(g?.main).toEqual({ row: 7, col: 7, dir: 'H', len: 2 }); expect(cells(g)).toEqual(['7,7', '7,8']); }); it('takes the longer axis as the main word for a lone connecting tile', () => { // (7,8) joins a length-3 horizontal (cols 6-8) and a length-4 vertical (rows 5-8): the vertical // is longer, so it is the main word and the horizontal becomes a cross word. const g = formedGeometry( board([ [7, 6, 'A'], [7, 7, 'B'], [5, 8, 'C'], [6, 8, 'D'], [8, 8, 'E'], ]), [{ row: 7, col: 8 }], true, ); expect(g?.main).toEqual({ row: 5, col: 8, dir: 'V', len: 4 }); expect(cells(g)).toEqual(['5,8', '6,8', '7,6', '7,7', '7,8', '8,8']); }); }); describe('badgePlacement', () => { it('anchors a mid-board horizontal word at the last letter, top-right', () => { expect(badgePlacement({ row: 7, col: 5, dir: 'H', len: 3 })).toEqual({ row: 7, col: 7, corner: 'tr' }); }); it('anchors a right-edge horizontal word at the first letter, bottom-left', () => { expect(badgePlacement({ row: 7, col: 12, dir: 'H', len: 3 })).toEqual({ row: 7, col: 12, corner: 'bl' }); }); it('anchors a top-edge horizontal word at the first letter, bottom-left', () => { expect(badgePlacement({ row: 0, col: 5, dir: 'H', len: 3 })).toEqual({ row: 0, col: 5, corner: 'bl' }); }); it('anchors a left-edge horizontal word at the last letter, top-right', () => { expect(badgePlacement({ row: 7, col: 0, dir: 'H', len: 3 })).toEqual({ row: 7, col: 2, corner: 'tr' }); }); it('anchors a bottom-edge horizontal word at the last letter, top-right', () => { expect(badgePlacement({ row: 14, col: 5, dir: 'H', len: 3 })).toEqual({ row: 14, col: 7, corner: 'tr' }); }); it('anchors a mid-board vertical word at the first letter, top-right', () => { expect(badgePlacement({ row: 5, col: 7, dir: 'V', len: 3 })).toEqual({ row: 5, col: 7, corner: 'tr' }); }); it('anchors a top-edge vertical word at the last letter, bottom-left', () => { expect(badgePlacement({ row: 0, col: 7, dir: 'V', len: 3 })).toEqual({ row: 2, col: 7, corner: 'bl' }); }); it('anchors a right-edge vertical word at the last letter, bottom-left', () => { expect(badgePlacement({ row: 5, col: 14, dir: 'V', len: 3 })).toEqual({ row: 7, col: 14, corner: 'bl' }); }); it('anchors a full-width horizontal word at the last letter, top-right (clamped by the board)', () => { expect(badgePlacement({ row: 7, col: 0, dir: 'H', len: 15 })).toEqual({ row: 7, col: 14, corner: 'tr' }); }); });