日本発パズル ノノグラム(ピクロス/お絵かきロジック) を、制約伝播ラインソルバー内蔵でブラウザに実装した。勘所は 3 つ: (1) ラインソルバー=「配置の列挙と交差」 — 行/列のラン配置を (位置, 手がかり番号) のメモ化再帰で列挙し、全配置で埋まるセルは確定・全配置で空くセルは確定とする。人間の「重なりテク」はこの交差の特殊例として自動で出てくる、(2) 確定が出るたび交差する列/行を再キューし、不動点まで伝播、(3) 収録 5 問は「line-solvable(勘・場合分け不要)」かつ「解が原画と完全一致」をテストで保証 — 手がかりは原画から導出するので、絵と手がかりが食い違う事故は構造的に起きない。ソルバー内蔵パズル第 5 弾。
🌐 ライブデモ: https://sen.ltd/portfolio/nonogram/
📦 GitHub: https://github.com/sen-ltd/nonogram
ラインソルバー: 列挙して交差する
1 本の行(列)には手がかり [3,1] のようなラン列があり、既知セルと整合する配置は有限個しかない。ソルバーは (位置, 手がかり番号) のメモ化再帰——部分問題は O(L·k) 個——で全配置を歩き、セルごとに「埋まりうるか」「空きうるか」を記録する:
if (canFill[j] && !canEmpty[j]) out[j] = FILLED; // 全配置で埋まる → 確定
else if (!canFill[j] && canEmpty[j]) out[j] = EMPTY;
この交差は、人間のピクロステクニックの形式化そのものだ。有名な「重なり」——5 マスにラン 3 なら中央 1 マスは必ず埋まる——も自動で導出され、テストで pin してある:
expect(solveLine([U, U, U, U, U], [3])).toEqual([U, U, F, U, U]);
配置が 1 つも無ければその行は矛盾しており、ソルバーは null を返す。UI の「your grid contradicts the clues」はこれをそのまま表示している。
不動点までの伝播
ラインパスがセルを確定させたら、**交差する列(行)**が再度解く価値を持つ。dirty キューで変化が無くなるまでラインパスを回す——これが盤面ソルバーの全てだ。この推論だけで完成する問題が line-solvable(人間が場合分けなしで解けるクラス)で、確定順も記録しているので、デモの Solve はその順に盤面が埋まっていく様子をアニメで見せられる。
収録問題は「勘なし」をテストで保証する
収録 5 問(ハート・矢印・家・カップ・猫)は自作のドット絵。テストは各問が line-solvable であることと、解いた盤面が原画を完全再現することを要求する:
expect(r.solved, `${def.name} must be line-solvable (no guessing)`).toBe(true);
expect(asBool, `${def.name} must reproduce the art`).toEqual(solution);
手がかりは fromPixels が原画から導出する。つまり原画が single source of truth で、絵と手がかりの乖離は構造的に起きない。倉庫番(#286) の「レベルはソルバーを通してから出荷」と同じ思想だ。
まとめ
ノノグラムのラインソルバーは「列挙して交差する」だけで人間の解法テクニック全てを包含し、不動点までの伝播が盤面を完成させる。ソルバー内蔵パズルはこれで 5 本目——数独 / ライツアウト(GF(2))/ 15パズル(パリティ+IDA*)/ 倉庫番(pull-BFS)/ ノノグラム(制約伝播)。デモの Solve で、猫が推論だけで浮かび上がる様子をぜひ。
🌐 ライブデモ: https://sen.ltd/portfolio/nonogram/
📦 GitHub: https://github.com/sen-ltd/nonogram
