ダブルチョコ(Double Choco)を、5 段のソルバー内蔵でブラウザに実装した。ルールは「各ブロックは灰マスと生成りマスを同数持ち、灰の側と生成りの側が同じ形(回転・鏡像可)」。効いているのは同じ形という一節だ。数を数える条件ならマスを 1 つ動かしても生き残ることがあるが、形の条件は何も生き残らせない。出荷 72 盤で全部数えると、「灰と生成りが同数の連結集合」は 958,977 個、そのうち両半分が 1 つながりなのは 83,225 個(8.7%)、さらに両半分が合同なのは 34,800 個(41.8%、元の 3.6%)。ルール 1 文で 96.4% が消える。しかもこの一節は、弱いルール(枚数だけ)の世界を支配していた定理を空虚にする:出荷答えで隣接するブロック対 1,445 組のうち、貼り合わせて合法ブロックになるものは 0 組(枚数だけの世界なら 241 組が「盤が曖昧である証明」になる)。ソルバー内蔵パズル第 62 弾。
デモ: https://sen.ltd/portfolio/double-choco/
リポジトリ: https://github.com/sen-ltd/double-choco
ルール
盤の各マスには灰か生成りの色が最初から印刷されている。プレイヤーがやるのは、盤をブロックに切り分けること。
- 各ブロックは、灰マスと生成りマスを同じ枚数持つ
- 各ブロックの中で、灰の部分は 1 つながり、生成りの部分も 1 つながり
- そしてその 2 つの部分は合同(同じ形。回転してよいし、裏返してよい)
- 数字は「そのブロックが各色を何枚持つか」。1 ブロックに数字はたかだか 1 つ、0 個でもよい
Nikoli の比較的新しいジャンルで、2018 年に「ひこうき」名義の読者が投稿したもの。存在判定が NP 完全であることは修士論文で示されている(Đurić, 2022)。
3 つ目の条件が、このジャンルの全部だ。
数の条件と形の条件は種類が違う
自分がこれまでに実装してきた「盤を切り分ける」系のパズル — シカク、アラフ、フィルオミノ、ヘヤワケ — は、どれも領域を数で採点する。面積、マス数、合計。数は寛容だ。面積 6 の領域は、形が違っても面積 6 のままでいられる。
ダブルチョコは領域を形で採点する。合法なブロックの灰側から生成り側にマスを 1 つ移してみると、枚数はどちらもそのままなのに、2 つの半分はもう同じポリオミノではない。ブロックは死ぬ。
だから最初に測るべきなのは、この一節が探索空間のどれだけを捨てているかだ。出荷している 72 盤(6×6 と 8×8 を 36 枚ずつ)について、8 マス以下の連結集合をすべて歩いて仕分けした。
| 盤 | 灰と生成りが同数 | 両半分が 1 つながり | 両半分が合同 | 生存率 |
|---|---|---|---|---|
| 6×6 | 254,099 | 20,716 | 9,375 | 3.7% |
| 8×8 | 704,878 | 62,509 | 25,425 | 3.6% |
| 計 | 958,977 | 83,225 | 34,800 | 3.6% |
枚数が合っている集合のうち、91.3% は「半分が 1 つながりでない」だけで落ちる。生き残った 83,225 個から、合同条件がさらに 58.2% を落とす。最終的に残るのは 3.6%。
そして「合同」は「だいたい同じ」の言い換えではない。生き残ったブロックのうち、2 つの半分が平行移動だけで重なるものは 34.7% しかない。残りは本当に回転か鏡像が要る。出荷した 774 ブロックの内訳は、平行移動 454(58.7%)、回転が要るもの 299(38.6%)、回転では作れない鏡像が要るもの 21(2.7%)。L と J を区別しない、というルールの一言が、実際に 21 回効いている。
形の条件だけを分離する実験
答えがどれだけ固いかを測るとき、自分はいつも「1 マスを隣のブロックに移す」をやる。ここではそれが役に立たない。出荷盤で可能な 1 マス移動は 5,377 通り、全部不正 — だが 64.7% は「枚数が合わなくなった」というだけの理由で不正になる。算数が全部捕まえてしまうので、何も分からない。
鋭い実験は交換のほうだった。ブロック A のマスと、ブロック B の同じ色のマスを 1 つずつ取り替える。盤の上の枚数は 1 つも変わらない。どの数字も、自分のブロックに正しい枚数を持ったままだ。残った反対意見は幾何しかない。
出荷答えの上で可能な交換は 17,860 通り。合法なものは 0 通り。何が反対したかの内訳:
| 反対した理由 | 反対した回数 | それだけが反対した回数 |
|---|---|---|
| ブロックが 2 つに割れる | 17,505 | 234 |
| 半分が 2 つに割れる | 17,109 | 144 |
| 2 つの半分が同じ形でなくなる | 1,570 | 83 |
| 数字がブロックと食い違う | 367 | 6 |
| 1 ブロックに数字が 2 つ | 319 | 21 |
83 通りの並べ替えは、枚数を全部保ち、ブロックも半分も 1 つながりのまま保ち、全部の数字を満足させたまま、それでも不正だ。 理由は S が L でないから、それだけ。他のどのルールにも言えないことを、形の条件がここで言っている。
弱いルールの世界を支配する定理が、ここでは空虚になる
形の条件を落とすと、ダブルチョコは「枚数だけ」のパズルになる。そちらには綺麗な小定理がある。
接している、数字を持たない 2 つのブロックを貼り合わせる。和集合は連結で、枚数は依然として釣り合っていて、依然として数字を持たない。したがってそれは別の合法解だ。よって一意な盤では、数字を持たないブロックどうしが接することはありえない。
この定理は強い。最小の数字数がグラフの独立数で下から押さえられる。実際、自分が最初に書いた実装(ルールを取り違えて「枚数だけ」で作っていた)は、この定理を中心に据えていた。
形の条件を入れると、貼り合わせた結果も合同でなければならない。そしてそれはほぼ絶対に成り立たない。
| 盤 | 接しているブロック対 | 和が合法ブロック | 形の条件なしなら合法 | 間に数字の無い対 |
|---|---|---|---|---|
| 6×6 | 479 | 0 (0.0%) | 83 (17.3%) | 83 |
| 8×8 | 966 | 0 (0.0%) | 158 (16.4%) | 158 |
出荷答えで接しているブロック対 1,445 組のうち、貼り合わせて合法ブロックになるものは 1 組も無い。そして間に数字を持たない対は 241 組ある — 枚数だけの世界なら、この 241 組は 1 組残らず「この盤は曖昧だ」の証明になっていたはずのものだ。
だからこそ、このジャンルの盤は数字を置かなくて済む。出荷した 774 ブロックのうち 349 個(45.1%)は数字を持たない。形の条件がすでにそのブロックを押さえているので、数字が行く必要が無い。
予想を外したところ:解の存在
入る前の自分の予想はこうだった。「存在判定が NP 完全なら、ランダムな色分けはほとんど解無しだろう」。測ったら違った。
| 盤 | 色分け | 試行 | 解がある | 形の条件なしなら |
|---|---|---|---|---|
| 6×6 | 一様ランダム | 150 | 143 (95.3%) | 145 (96.7%) |
| 6×6 | ぼかし | 150 | 102 (68.0%) | 137 (91.3%) |
| 8×8 | 一様ランダム | 80 | 72 (90.0%) | 77 (96.3%) |
| 8×8 | ぼかし | 80 | 32 (40.0%) | 46 (57.5%) |
一様ランダムな色分けは、形の条件があってもだいたい切れてしまう(8×8 で 90.0%、条件なしで 96.3%)。条件が効くのはぼかした色分け — つまり実際に出版されている盤に見えるほうだ(40.0% 対 57.5%)。同色の大きな塊は、ブロックが近くに合同な相方を見つけられない場所そのものだからだ。
(表の「形の条件なし」が 100% でないのは、ブロックを各色 4 枚までに制限しているから。「盤全体が 1 ブロック」という逃げ道が使えないと、枚数だけの世界でも存在は保証されない。)
いずれにせよ、形の条件が壊しているのは存在ではなく豊富さだった。だから生成器は色分けから始められない。答えを先に敷いて、色分けをそこから落とす。ラスタ順に「ある形+その回転か鏡像」を置いていき、片方を灰、片方を生成りに塗る。色分けは分割の副産物になる。
ダイヤルは数字で、しかも短い
1 ブロックに数字はたかだか 1 つ。つまり数字が言えることの総量はブロック数で頭打ちになる。ランダムに m 個のブロックへ数字を置いて、答えが一意かどうかを聞いてみる。
| 数字の数 | 6×6 一意 | 8×8 一意 |
|---|---|---|
| 3 | 2/180 (1.1%) | 0/180 (0.0%) |
| 5 | 29/180 (16.1%) | 1/180 (0.6%) |
| 7 | 91/180 (50.6%) | 2/180 (1.1%) |
| 9 | — | 8/180 (4.4%) |
| 11 | — | 33/180 (18.3%) |
| 13 | — | 68/180 (37.8%) |
| 全ブロックに 1 つずつ | 30/36 (83.3%) | 29/36 (80.6%) |
最後の行が本題だ。全部のブロックに自分のサイズを書き込んでも一意とは限らない。 6×6 で 36 枚中 6 枚、8×8 で 36 枚中 7 枚が、そこまでやっても 2 つ目の答えを持っている。数字はダイヤルであると同時に天井で、その天井は高くない。
出荷盤が持っている数字は 6×6 で平均 4.3 個、8×8 で 7.6 個。どれも「1 つ消してみて、2 つ目の答えが出たから戻した」ものだ。npm test はこれを出荷ファイルから再導出する。
5 段のソルバー
ソルバーはマスではなくカタログの上で動く。catalogue() が盤の合法ブロックを全部・各 1 回だけ列挙する(各連結集合をその最小番号マスからのみ到達させるので重複しない)。その上は集合被覆だ。
| 段 | 何を知っているか |
|---|---|
only |
1 つのブロックにしか覆われないマスは、そのブロックに属す |
bond |
あるマスの生き残りブロック全部に共通して含まれるマスは、必ず同じブロックに入る。よって片方だけを含むブロックは死ぬ |
area |
全ブロックは釣り合っていて、未確定マスの 1 連結成分の中に収まる。よって色数の偏った成分を作る壁は引けない |
probe |
ブロックを 1 つ仮定して下の 3 段を回し、それだけで矛盾したら捨てる |
search |
候補が最少のマスから分岐する完全ソルバー |
| 段 | 6×6 確定マス | 8×8 確定マス | 6×6 完答 | 8×8 完答 |
|---|---|---|---|---|
only |
11.7% | 6.0% | 1/36 | 0/36 |
bond |
62.7% | 36.4% | 19/36 | 7/36 |
area |
81.9% | 53.1% | 29/36 | 17/36 |
probe |
100% | 100% | 36/36 | 36/36 |
出荷盤は 1 枚残らず probe で分岐 0 回で落ちる。探索を area 止まりで回しても、中央値は 6×6 で 1 ノード、8×8 で 3 ノード(最悪 7 と 39)。
area は一見すると重そうだが、実際には「壁が隅を絞め殺した」瞬間を捕まえる唯一の段で、bond から area で確定率が 6×6 で 62.7% → 81.9% に上がる。
ルールの誤読は、逆向きに壊れる
ルールは普通「各ブロックは灰と生成りを同数含み、2 つの領域は同じ形」と書かれる。外しどころは 3 つあって、外し方によって壊れ方が違う。
| 誤読 | 盤 | 意図した答えが合法 | 解の数 | なお一意 |
|---|---|---|---|---|
| 形の条件を落とす | 6×6 | 36/36 | 中央値 9(1 枚は 400 打ち切り) | 3 |
| 形の条件を落とす | 8×8 | 36/36 | 中央値 276(14 枚は 400 打ち切り) | 1 |
| 半分が分断されてよい | 6×6 | 36/36 | 中央値 1、最大 13 | 24 |
| 半分が分断されてよい | 8×8 | 36/36 | 中央値 5、最大 400 | 13 |
| 「同じ形」を「同じ向き」と読む | 6×6 | 1/36 | 解のある盤 1/36 | 0 |
| 「同じ形」を「同じ向き」と読む | 8×8 | 0/36 | 解のある盤 0/36 | 0 |
緩めると、意図した答えは合法のままだ。ただ 1 つではなくなるだけで、数えてみるまで何もおかしく見えない。締めると、意図した答えが不正になる。回転と鏡像を禁じた読み方では、72 枚のうち解を持つ盤は 1 枚だけで、残りは真っ白になる。
危ないのは緩いほうだ。黙って壊れるから。
外の台帳と照合する
形エンジンは全部の数字の土台なので、合同判定を間違えるとこのページの数値が全部間違う。だから公表されているポリオミノの個数を 3 通り再現させている。
| マス数 | 固定 | A001168 | 自由 | A000105 | 片面 | A000988 |
|---|---|---|---|---|---|---|
| 5 | 63 | 63 | 12 | 12 | 18 | 18 |
| 6 | 216 | 216 | 35 | 35 | 60 | 60 |
| 7 | 760 | 760 | 108 | 108 | 196 | 196 |
| 8 | 2,725 | 2,725 | 369 | 369 | 704 | 704 |
| 9 | 9,910 | 9,910 | 1,285 | 1,285 | 2,500 | 2,500 |
「自由」(回転と鏡像で同一視)がルールの使っている関係そのものだ。shapesOfSize() が 3 つの関係すべてで正しい数を出すので、正規形の実装が回転だけ/鏡像だけに寄っていないことが分かる。
ソルバーのほうは、チョコとは何の関係も無い数列に固定してある。盤を市松に塗り、ブロックを各色 1 枚までに制限すると、ダブルチョコはドミノ敷き詰めそのものになる(1 枚と 1 枚は必ず同じ形なので)。
| 盤 | ソルバー | ブロック総当たり | 公表値 |
|---|---|---|---|
| 2×4 | 5 | 5 | 5 |
| 2×7 | 21 | 21 | 21 |
| 3×4 | 11 | 11 | 11 |
| 4×4 | 36 | 36 | 36 |
| 4×6 | 281 | 281 | 281 |
| 6×6 | 6,728 | — | 6,728 |
2×n はフィボナッチ(A000045)、それ以外は A004003 / A099390。
最後に、自分で数えるしかなかった量。ブロックの「形」は各サイズに何種類あるか — 合同なポリオミノの非順序対で、互いに素で、和集合が連結なもの、平行移動で同一視。ジャンルが書かれている語彙の大きさだ。
| 各色の枚数 | ブロック形 | うち平行移動だけ | 使われる半分の形 |
|---|---|---|---|
| 1 | 2 | 2 | 1 |
| 2 | 16 | 8 | 1 |
| 3 | 104 | 32 | 2 |
| 4 | 678 | 126 | 5 |
| 5 | 3,182 | 498 | 12 |
2, 16, 104, 678, 3182 — 2026 年 9 月時点で OEIS を検索しても出てこない。
3 つのエンジン
カタログが全ての土台なので、それを知らないエンジン 2 つと突き合わせている。bruteByBlocks は最小の未確定マスを取り、そこから連結集合を全部育てる。bruteByBorders はブロックという言葉を一度も使わない — マスをラスタ順に歩き、各マスと一緒に到着する 2 本の境界を union-find で繋ぐか切るかを決め、成分がもう育てられなくなった瞬間にだけルールを検査する。
3 つとも解の集合で一致する(個数だけではなく)。境界エンジンだけは重複する表現を持つ(既に繋がっている隣に繋ぎ直しても分割は変わらない)ので、解を集合として集めている。この重複に最初に気づいたのはテストが落ちたからで、境界モデルを書くときの地味な落とし穴だと思う。
出荷 72 盤は、72 枚とも合法な答えを持ち、72 枚とも出荷した通りの答えを唯一解に持つ。全 27 テスト。
まとめ
「同じ枚数」と「同じ形」は、制約として種類が違う。数の条件は 1 マスの移動を生き延びることがあるが、形の条件は生き延びさせない。その差が測れる形で出た:枚数の合う連結集合 958,977 個のうち、形まで揃うのは 34,800 個(3.6%)。枚数を保ったまま幾何だけを壊す 17,860 通りの交換のうち、形の条件だけが止めているものが 83 通り。そして接するブロック対 1,445 組のうち、貼り合わせられるものは 0 組 — 弱いルールなら 241 組が曖昧性の証明になっていた。
- 実装: TypeScript + Vite、ランタイム依存ゼロ、27 テスト
- 生成: 答えを先に敷き、色分けを副産物にする。数字は「消せなくなるまで」削る局所最小
- 検証: 完全ソルバーと 2 つの素朴エンジンの解集合一致、A001168 / A000105 / A000988 の 3 通り再現、市松+各色 1 枚でドミノ敷き詰め数(A000045 / A004003 / A099390)に一致
デモとソースは冒頭のリンクから。記事中の数値はすべて npm run stats / npm run ledger の出力で、README とページ本文はその JSON から機械的に書き出している。
