Files
sudoku-tools/tests/state/candidateMaintenance.test.ts

146 lines
5.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { classicRegions, type PuzzleDefinition } from "../../src/domain";
import type { LogicalStep } from "../../src/solver";
import {
applyLogicalStepToSession,
autoRemoveNotesAfterPlacements,
fillLegalCenterCandidates,
pruneInvalidNotes,
} from "../../src/state/candidateMaintenance";
import { createSession, maskValues } from "../../src/state/session";
function puzzle4(): PuzzleDefinition {
return {
version: 1,
size: 4,
givens: new Array<number>(16).fill(0),
regions: classicRegions(4),
constraints: [],
};
}
describe("candidate maintenance", () => {
it("fills legal center candidates without changing corner notes or metadata", () => {
const session = createSession(new Array<number>(16).fill(0));
session.values[0] = 1;
session.cornerMarks[1] = 0b1111;
session.centerMarks[10] = 0b0010;
session.colors[3] = 4;
session.elapsedSeconds = 73;
session.paused = true;
const result = fillLegalCenterCandidates(session, puzzle4());
expect(result).not.toBe(session);
expect(result.centerMarks).not.toBe(session.centerMarks);
expect(result.centerMarks[0]).toBe(0);
expect(maskValues(result.centerMarks[1]!, 4)).toEqual([2, 3, 4]);
expect(maskValues(result.centerMarks[10]!, 4)).toEqual([1, 2, 3, 4]);
expect(result.cornerMarks[1]).toBe(0b1111);
expect(result.colors[3]).toBe(4);
expect(result.elapsedSeconds).toBe(73);
expect(result.paused).toBe(true);
expect(session.centerMarks[10]).toBe(0b0010);
});
it("only removes illegal center and corner notes", () => {
const session = createSession(new Array<number>(16).fill(0));
session.values[0] = 1;
session.centerMarks.fill(0b1111);
session.cornerMarks.fill(0b1111 | (1 << 8));
const result = pruneInvalidNotes(session, puzzle4());
expect(result.centerMarks[0]).toBe(0);
expect(result.cornerMarks[0]).toBe(0);
expect(maskValues(result.centerMarks[1]!, 4)).toEqual([2, 3, 4]);
expect(maskValues(result.cornerMarks[4]!, 4)).toEqual([2, 3, 4]);
expect(maskValues(result.centerMarks[10]!, 4)).toEqual([1, 2, 3, 4]);
expect(result.cornerMarks[10]).toBe(0b1111);
expect(session.cornerMarks[1]).toBe(0b1111 | (1 << 8));
});
it("auto-removes notes after placements but does not invent notes on erase", () => {
const previous = createSession(new Array<number>(16).fill(0));
previous.centerMarks.fill(0b1111);
previous.cornerMarks.fill(0b1111);
const placed = createSession(new Array<number>(16).fill(0));
placed.centerMarks.fill(0b1111);
placed.cornerMarks.fill(0b1111);
placed.values[0] = 1;
const maintained = autoRemoveNotesAfterPlacements(
previous,
placed,
puzzle4(),
);
expect(maskValues(maintained.centerMarks[1]!, 4)).toEqual([2, 3, 4]);
expect(maskValues(maintained.cornerMarks[4]!, 4)).toEqual([2, 3, 4]);
expect(maintained.centerMarks[10]).toBe(0b1111);
const erased = createSession(new Array<number>(16).fill(0));
erased.centerMarks[1] = 0b0001;
const previousFilled = createSession(new Array<number>(16).fill(0));
previousFilled.values[0] = 1;
const afterErase = autoRemoveNotesAfterPlacements(
previousFilled,
erased,
puzzle4(),
);
expect(afterErase.centerMarks[1]).toBe(0b0001);
expect(afterErase).not.toBe(erased);
});
it("applies a logical step to tracked candidates as one immutable transition", () => {
const base = createSession(new Array<number>(16).fill(0));
base.cornerMarks.fill(0b1111);
base.colors[10] = 3;
base.elapsedSeconds = 99;
const session = fillLegalCenterCandidates(base, puzzle4());
const step: LogicalStep = {
technique: "naked-single",
placements: [{ cell: 0, value: 1 }],
eliminations: [{ cell: 10, values: [2] }],
focusCells: [0, 10],
explanation: "Test step",
};
const result = applyLogicalStepToSession(session, step, puzzle4());
expect(result.values[0]).toBe(1);
expect(result.centerMarks[0]).toBe(0);
expect(result.cornerMarks[0]).toBe(0);
expect(maskValues(result.centerMarks[1]!, 4)).toEqual([2, 3, 4]);
expect(maskValues(result.centerMarks[10]!, 4)).toEqual([1, 3, 4]);
expect(result.cornerMarks[1]).toBe(0b1111);
expect(result.colors[10]).toBe(3);
expect(result.elapsedSeconds).toBe(99);
expect(session.values[0]).toBe(0);
expect(session.centerMarks[10]).toBe(0b1111);
});
it("leaves absent tracked candidates absent and rejects stale steps", () => {
const session = createSession(new Array<number>(16).fill(0));
const elimination: LogicalStep = {
technique: "pointing",
placements: [],
eliminations: [{ cell: 1, values: [1] }],
focusCells: [1],
explanation: "Test elimination",
};
expect(
applyLogicalStepToSession(session, elimination, puzzle4()).centerMarks[1],
).toBe(0);
session.values[0] = 2;
const placement: LogicalStep = {
...elimination,
placements: [{ cell: 0, value: 1 }],
eliminations: [],
};
expect(() =>
applyLogicalStepToSession(session, placement, puzzle4()),
).toThrow(/filled cell/u);
});
});