clone

a daily shikaku puzzle for phones: one seeded puzzle per date, solved by dragging rectangles

main
rect.run / tests / lib / shikaku / engine_test.ts
engine_test.ts 4.4K
1import { assertEquals } from "@std/assert";23import {4 canonicalizeRectangle,5 createRectangleFromCorners,6 getRectangleCells,7 isPuzzleSolved,8 validateRectanglePlacement,9} from "@/lib/shikaku/engine.ts";10import type { PlacedRectangle, ShikakuPuzzle } from "@/lib/shikaku/types.ts";1112const samplePuzzle: ShikakuPuzzle = {13 id: "daily-2026-04-16-0",14 date: "2026-04-16",15 streakIndex: 0,16 width: 4,17 height: 4,18 difficulty: "easy",19 seed: "sample-seed",20 givens: [21 { id: 0, row: 0, col: 0, value: 4 },22 { id: 1, row: 0, col: 2, value: 4 },23 { id: 2, row: 2, col: 0, value: 4 },24 { id: 3, row: 2, col: 2, value: 4 },25 ],26 solution: [27 { clueId: 0, row: 0, col: 0, width: 2, height: 2, value: 4 },28 { clueId: 1, row: 0, col: 2, width: 2, height: 2, value: 4 },29 { clueId: 2, row: 2, col: 0, width: 2, height: 2, value: 4 },30 { clueId: 3, row: 2, col: 2, width: 2, height: 2, value: 4 },31 ],32};3334Deno.test("createRectangleFromCorners normalizes drag direction", () => {35 assertEquals(36 createRectangleFromCorners({ row: 1, col: 1 }, { row: 0, col: 0 }),37 {38 row: 0,39 col: 0,40 width: 2,41 height: 2,42 },43 );44});4546Deno.test("getRectangleCells expands every covered cell", () => {47 assertEquals(getRectangleCells({ row: 1, col: 1, width: 2, height: 2 }), [48 "1,1",49 "1,2",50 "2,1",51 "2,2",52 ]);53});5455Deno.test("validateRectanglePlacement accepts valid clue-matching rectangle", () => {56 const candidate = { row: 0, col: 0, width: 2, height: 2 };5758 assertEquals(validateRectanglePlacement(samplePuzzle, candidate, []), {59 ok: true,60 given: { id: 0, row: 0, col: 0, value: 4 },61 });62});6364Deno.test("validateRectanglePlacement accepts rectangles whose clue is not at the drag start", () => {65 const offCenterPuzzle: ShikakuPuzzle = {66 id: "off-center",67 date: "2026-04-16",68 streakIndex: 0,69 width: 3,70 height: 2,71 difficulty: "easy",72 seed: "off-center",73 givens: [{ id: 0, row: 0, col: 1, value: 6 }],74 solution: [{ clueId: 0, row: 0, col: 0, width: 3, height: 2, value: 6 }],75 };7677 assertEquals(78 validateRectanglePlacement(offCenterPuzzle, {79 row: 0,80 col: 0,81 width: 3,82 height: 2,83 }, []),84 {85 ok: true,86 given: { id: 0, row: 0, col: 1, value: 6 },87 },88 );8990 assertEquals(91 validateRectanglePlacement(samplePuzzle, {92 row: 0,93 col: 1,94 width: 2,95 height: 2,96 }, []),97 {98 ok: true,99 given: { id: 1, row: 0, col: 2, value: 4 },100 },101 );102});103104Deno.test("validateRectanglePlacement rejects overlap and multiple givens", () => {105 const existing: PlacedRectangle[] = [{106 clueId: 0,107 row: 0,108 col: 0,109 width: 2,110 height: 2,111 value: 4,112 }];113114 assertEquals(115 validateRectanglePlacement(samplePuzzle, {116 row: 0,117 col: 0,118 width: 3,119 height: 2,120 }, []),121 {122 ok: false,123 reason: "multiple-givens",124 },125 );126127 assertEquals(128 validateRectanglePlacement(samplePuzzle, {129 row: 0,130 col: 1,131 width: 2,132 height: 2,133 }, existing),134 {135 ok: false,136 reason: "overlap",137 },138 );139});140141Deno.test("isPuzzleSolved accepts any valid full tiling, not only the planned one", () => {142 assertEquals(isPuzzleSolved(samplePuzzle, samplePuzzle.solution ?? []), true);143144 const multiSolutionPuzzle: ShikakuPuzzle = {145 id: "multi-solution",146 date: "2026-04-16",147 streakIndex: 0,148 width: 2,149 height: 2,150 difficulty: "easy",151 seed: "multi-solution",152 givens: [153 { id: 0, row: 0, col: 0, value: 2 },154 { id: 1, row: 1, col: 1, value: 2 },155 ],156 solution: [157 { clueId: 0, row: 0, col: 0, width: 2, height: 1, value: 2 },158 { clueId: 1, row: 1, col: 0, width: 2, height: 1, value: 2 },159 ],160 };161162 const alternateValidTiling: PlacedRectangle[] = [163 { clueId: 0, row: 0, col: 0, width: 1, height: 2, value: 2 },164 { clueId: 1, row: 0, col: 1, width: 1, height: 2, value: 2 },165 ];166167 assertEquals(isPuzzleSolved(multiSolutionPuzzle, alternateValidTiling), true);168 assertEquals(169 isPuzzleSolved(samplePuzzle, [170 { clueId: 0, row: 0, col: 0, width: 4, height: 1, value: 4 },171 ]),172 false,173 );174});175176Deno.test("canonicalizeRectangle returns a stable key", () => {177 assertEquals(178 canonicalizeRectangle({179 clueId: 3,180 row: 2,181 col: 2,182 width: 2,183 height: 2,184 value: 4,185 }),186 "3:2:2:2:2:4",187 );188});