feat: expand sudoku analysis and interoperability
This commit is contained in:
@@ -52,6 +52,12 @@ describe("fpuzzles interoperability", () => {
|
||||
inequality: [{ cells: ["R6C1", "R6C2"], value: ">" }],
|
||||
renban: [{ lines: [["R7C1", "R7C2"]] }],
|
||||
palindrome: [{ lines: [["R8C1", "R8C2"]] }],
|
||||
xsum: [{ cell: "R0C1", value: "15" }],
|
||||
skyscraper: [{ cell: "R2C10", value: "2" }],
|
||||
quadruple: [
|
||||
{ cells: ["R1C1", "R1C2", "R2C1", "R2C2"], values: [1, 3, 8] },
|
||||
],
|
||||
maximum: [{ cell: "R2C2" }],
|
||||
});
|
||||
expect(puzzle.givens[0]).toBe(5);
|
||||
expect(puzzle.values?.[1]).toBe(3);
|
||||
@@ -64,6 +70,10 @@ describe("fpuzzles interoperability", () => {
|
||||
{ type: "kropki", a: 27, b: 28, kind: "white" },
|
||||
{ type: "kropki", a: 28, b: 29, kind: "black" },
|
||||
{ type: "inequality", lesser: 46, greater: 45 },
|
||||
{ type: "x-sum", side: "top", index: 0, sum: 15 },
|
||||
{ type: "skyscraper", side: "right", index: 1, count: 2 },
|
||||
{ type: "quadruple", cells: [0, 1, 9, 10], digits: [1, 3, 8] },
|
||||
{ type: "maximum", cell: 10 },
|
||||
]),
|
||||
);
|
||||
expect(() => normalizePuzzle(toDomainPuzzle(puzzle))).not.toThrow();
|
||||
@@ -85,6 +95,10 @@ describe("fpuzzles interoperability", () => {
|
||||
{ type: "inequality", lesser: 45, greater: 46 },
|
||||
{ type: "renban", cells: [54, 55] },
|
||||
{ type: "palindrome", cells: [63, 64] },
|
||||
{ type: "x-sum", side: "left", index: 0, sum: 6 },
|
||||
{ type: "skyscraper", side: "right", index: 1, count: 2 },
|
||||
{ type: "quadruple", cells: [0, 1, 9, 10], digits: [1, 3, 8] },
|
||||
{ type: "maximum", cell: 10 },
|
||||
],
|
||||
title: "Round trip",
|
||||
};
|
||||
@@ -110,6 +124,53 @@ describe("fpuzzles interoperability", () => {
|
||||
).toThrow(/local-only app cannot fetch/u);
|
||||
});
|
||||
|
||||
it("refuses to weaken false clues during fpuzzles export", () => {
|
||||
const source: SudokuDocument = {
|
||||
schema: SUDOKU_DOCUMENT_SCHEMA,
|
||||
version: 1,
|
||||
size: 9,
|
||||
givens: Array<number>(81).fill(0),
|
||||
constraints: [
|
||||
{
|
||||
type: "x-sum",
|
||||
side: "top",
|
||||
index: 0,
|
||||
sum: 11,
|
||||
negated: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(() => exportFpuzzles(source)).toThrow(/individually false clue/u);
|
||||
});
|
||||
|
||||
it("rejects out-of-range outside clues during fpuzzles export", () => {
|
||||
const source = {
|
||||
schema: SUDOKU_DOCUMENT_SCHEMA,
|
||||
version: 1,
|
||||
size: 9,
|
||||
givens: Array<number>(81).fill(0),
|
||||
} as const;
|
||||
|
||||
for (const constraint of [
|
||||
{ type: "x-sum", side: "top", index: -1, sum: 15 },
|
||||
{ type: "x-sum", side: "bottom", index: 9, sum: 15 },
|
||||
{ type: "x-sum", side: "right", index: 0, sum: 0 },
|
||||
{ type: "x-sum", side: "left", index: 0, sum: 46 },
|
||||
{ type: "skyscraper", side: "right", index: -1, count: 2 },
|
||||
{ type: "skyscraper", side: "left", index: 9, count: 2 },
|
||||
{ type: "skyscraper", side: "bottom", index: 0, count: 0 },
|
||||
{ type: "skyscraper", side: "top", index: 0, count: 10 },
|
||||
] as const) {
|
||||
expect(() =>
|
||||
exportFpuzzles({
|
||||
...source,
|
||||
constraints: [constraint],
|
||||
}),
|
||||
).toThrow(/outside the supported range/u);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects unsupported generalized dots instead of weakening them", () => {
|
||||
expect(() =>
|
||||
parseFpuzzles({
|
||||
@@ -120,6 +181,40 @@ describe("fpuzzles interoperability", () => {
|
||||
).toThrow(/Difference-2 dots are not supported/u);
|
||||
});
|
||||
|
||||
it("rejects quadruples spanning more than four cells", () => {
|
||||
expect(() =>
|
||||
parseFpuzzles({
|
||||
size: 9,
|
||||
grid: emptyGrid(),
|
||||
quadruple: [
|
||||
{
|
||||
cells: ["R1C1", "R1C2", "R2C1", "R2C2", "R3C3"],
|
||||
values: [1, 2],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toThrow(/one to four cells and clue digits/u);
|
||||
expect(() =>
|
||||
parseFpuzzles({
|
||||
size: 9,
|
||||
grid: emptyGrid(),
|
||||
quadruple: [{ cells: ["R1C1"], values: [1, 2] }],
|
||||
}),
|
||||
).toThrow(/one to four cells and clue digits/u);
|
||||
expect(() =>
|
||||
parseFpuzzles({
|
||||
size: 9,
|
||||
grid: emptyGrid(),
|
||||
quadruple: [
|
||||
{
|
||||
cells: ["R1C1", "R1C3", "R2C1", "R2C3"],
|
||||
values: [1, 2],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toThrow(/four cells surrounding one grid intersection/u);
|
||||
});
|
||||
|
||||
it("rejects unsupported or unknown rules instead of silently weakening them", () => {
|
||||
expect(() =>
|
||||
parseFpuzzles({
|
||||
|
||||
Reference in New Issue
Block a user