98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
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<number>(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<number>(12).fill(0)];
|
|
const masks = new Array<number>(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<number>(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<number>(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]);
|
|
});
|
|
});
|