feat: expand sudoku analysis and interoperability

This commit is contained in:
2026-08-30 23:21:56 +02:00
parent 4a9869baa0
commit 8ca9300ab3
73 changed files with 12482 additions and 384 deletions
+22
View File
@@ -38,6 +38,28 @@ describe("logical solver", () => {
expect(result.steps).toHaveLength(1);
});
it("does not apply positive cage reductions to a false cage", () => {
const result = solveLogically({
version: 1,
size: 4,
givens: Array<number>(16).fill(0),
regions: classicRegions(4),
constraints: [
{
type: "killer-cage",
cells: [0, 1],
sum: 3,
noRepeat: false,
negated: true,
},
],
});
expect(result.status).toBe("stuck");
expect(result.steps).toEqual([]);
expect(result.candidates[0]).toEqual([1, 2, 3, 4]);
});
it("returns bounded killer combinations", () => {
expect(
killerDigitCombinations({ size: 9, count: 2, sum: 10 }).combinations,
+29
View File
@@ -196,6 +196,35 @@ describe("variant generator", () => {
expect(generated.puzzle.rules).not.toMatch(/Kropki|negative/i);
});
it("mines deterministically for a requested practice technique", () => {
const options = {
variant: "classic",
size: 9,
seed: "mine-practice",
targetDifficulty: "hard",
requiredTechnique: "naked-pair",
maxTechniqueAttempts: 6,
maxChecks: 72,
} as const;
const generated = generateVariant(options);
expect(generated.requestedTechnique).toBe("naked-pair");
expect(generated.generationAttempts).toBe(4);
expect(generated.difficulty.techniqueCounts["naked-pair"]).toBeGreaterThan(
0,
);
expect(generateVariant(options)).toEqual(generated);
});
it("keeps Killer-cage practice tied to the Killer generator", () => {
expect(() =>
generateVariant({
variant: "classic",
size: 4,
requiredTechnique: "killer-cage",
}),
).toThrow(/requires the Killer generator/i);
});
it("keeps the public variant ID type exhaustive", () => {
const ids: GeneratorVariant[] = GENERATOR_VARIANTS.map(({ id }) => id);
expect(new Set(ids).size).toBe(GENERATOR_VARIANTS.length);