import { describe, expect, it } from "vitest"; import { createEmptyPuzzle, normalizePuzzle } from "../../src/domain"; import { candidateCellsForValues, candidateUnitIndicesForCells, candidateValuesFromMask, deriveCandidateLinks, inspectCandidateCells, inspectCandidateHouse, } from "../../src/helpers"; function mask(...values: number[]): number { return values.reduce((result, value) => result | (1 << (value - 1)), 0); } const puzzle = normalizePuzzle(createEmptyPuzzle(4)); describe("candidate inspection", () => { it("decodes masks and identifies the houses touching selected cells", () => { expect(candidateValuesFromMask(mask(1, 3, 4), 4)).toEqual([1, 3, 4]); expect(candidateUnitIndicesForCells(puzzle, [0])).toEqual([0, 1, 2]); const cells = inspectCandidateCells( puzzle, [mask(1, 4), ...new Array(15).fill(0)], [0], ); expect(cells).toEqual([ { cell: 0, values: [1, 4], houseLabels: ["Row 1", "Column 1", "Region 1"], }, ]); }); it("reports missing values, candidate positions and conjugate pairs in a house", () => { const values = [4, 0, 0, 2, ...new Array(12).fill(0)]; const masks = new Array(16).fill(0); masks[1] = mask(1, 3); masks[2] = mask(1, 3); expect(inspectCandidateHouse(puzzle, values, masks, 0)).toMatchObject({ label: "Row 1", missingValues: [1, 3], positions: [ { value: 1, cells: [1, 2], linkKind: "strong" }, { value: 3, cells: [1, 2], linkKind: "strong" }, ], }); }); }); describe("candidate link graph", () => { it("derives strong house and bivalue links and merges shared contexts", () => { const masks = new Array(16).fill(0); masks[0] = mask(1, 2); masks[1] = mask(1, 3); const links = deriveCandidateLinks(puzzle, masks); const houseLink = links.find( ({ a, b }) => a.cell === 0 && a.value === 1 && b.cell === 1 && b.value === 1, ); expect(houseLink).toMatchObject({ kind: "strong" }); expect(houseLink?.contexts.map(({ label }) => label)).toEqual([ "Row 1", "Region 1", ]); expect(links).toContainEqual( expect.objectContaining({ kind: "strong", a: { cell: 0, value: 1 }, b: { cell: 0, value: 2 }, }), ); }); it("keeps larger candidate groups weak and supports digit and house scopes", () => { const masks = new Array(16).fill(0); masks[0] = mask(2, 4); masks[1] = mask(2); masks[2] = mask(2); masks[3] = mask(3, 4); const links = deriveCandidateLinks(puzzle, masks, { unitIndices: [0], values: [2], }); expect(links).toHaveLength(3); expect(links.every(({ kind }) => kind === "weak")).toBe(true); expect(links.every(({ a, b }) => a.value === 2 && b.value === 2)).toBe( true, ); expect(candidateCellsForValues(masks, [4])).toEqual([0, 3]); }); });