バトルシップ(Solitaire Battleships / Bimaru)を 5 段のソルバー内蔵でブラウザに実装した。盤が印刷するのは「艦隊の構成」と「行列の数字」の 2 つだが、このパズルを解けるものにしているのはどちらでもない。効いているのは「船どうしは接してはいけない、斜めも」という、盤のどこにも書かれない一文だ。ローカルには、船はまっすぐなので船マスの斜め 4 近傍はどの船にも属せず、1 マス置くたび水マスが最大 4 個タダで付く。グローバルには、8×8 標準艦隊で答え空間が 62.7 倍変わる(20,774,262,284 対 1,302,617,200,964)。答えの総数は行プロファイル DP で厳密に数えた:標準艦隊が最初に載るのは 7×7 の 406,664 通り、10×10 で 1,855,545,978,831,780、11×11 で 76,057,466,137,845,004(24,536,651 状態、518 秒)。艦隊指定を差し替えるだけで OEIS の A063443(利かないキング)と A006506(格子グラフの独立集合)に退化し、両方一致する。同じ DP の前向き重み × 後ろ向き完成数で「そのマスに船がある確率」が全空間について厳密に出る:10×10 で縁 24.52% 対中央 18.25%、8×8 では隅/中央が 2.50 倍。一方で数字は盤上でいちばん弱い情報だった。6×6 の全 894,296 答えを列挙すると、12 個の数字が全部見えていても中央値で 5 通り残り、数字だけで一意に決まるのは 8.22% だけ。8×8 では 0.5%、10×10 では 120 回引いて 0 回。梯子は count → touch → line → fleet → probe の 5 段で、10×10 では 58.8% → 80.5% → 80.6% → 100%。全 37 テスト。ソルバー内蔵パズル第 66 弾。
デモ: https://sen.ltd/portfolio/battleships/
リポジトリ: https://github.com/sen-ltd/battleships
ルール
バトルシップ(Solitaire Battleships / Bimaru)は、盤に隠された艦隊を当てるパズル。
- 艦隊の構成は既知。標準の 10×10 盤なら 戦艦 1(4 マス)、巡洋艦 2(3 マス)、駆逐艦 3(2 マス)、潜水艦 4(1 マス) の計 10 隻 20 マス
- 船は必ずまっすぐ(縦か横の連続マス)
- 船どうしは接してはいけない。斜めも
- 行・列の脇の数字は、その行・列の船マスの個数
- いくつかのマスは最初から公開されている。公開されるのは「何かある」ではなく駒の形——水、潜水艦、向きを持つ端、向きを言わない中央
ルールは Wikipedia の Battleship (puzzle) を一次資料として確認した。
The ships are placed so that no ship touches any other ship, not even diagonally.
この not even diagonally が、以下ずっと主役になる。
出荷している 6×6 盤の実例(> は右端の駒):
the board the answer
0 3 0 2 1 4 0 3 0 2 1 4
0 . . . . . . 0 . . . . . .
3 . . . . . > 3 . # . . # #
1 . . . . . . 1 . # . . . .
2 . . . . . . 2 . . . # . #
2 . . . . . . 2 . # . . . #
2 . . . . . . 2 . . . # . #
公開マスは 1 個、数字は 12 個。これで答えはただ 1 通りに決まる。出荷した 36 枚の 6×6 のうち 1 枚は公開マスが 0 個——12 個の数字だけで決まる。
盤に印刷されない条項が、実際には全部やっている
バトルシップが盤に印刷するのは 2 種類の情報だけだ。艦隊の構成と、行列の数字。どちらもこのパズルを解ける代物にしている条項ではない。
効いているのは「接してはいけない、斜めも」で、これは盤のどこにも書かれない。しかも二重に効く。
ローカルに効く。 船はまっすぐなので、船マスの斜め 4 近傍は、どの船にも属せない。同じ船なら曲がってしまうし、別の船なら接触になる。つまり船マスを 1 個置くたびに、水マスが最大 4 個タダで付いてくる。算術を 1 回もやる前に、だ。
グローバルに効く。 同じ艦隊で、斜め条項を落とした場合の答えの総数を数えるとこうなる。
| 盤と艦隊 | 斜めも含めて接触禁止 | 斜めなら接してよい | 「斜めも」の値段 |
|---|---|---|---|
| 5×5 | 1,428 | 31,196 | 21.8 倍 |
| 6×6 | 894,296 | 6,801,560 | 7.61 倍 |
| 7×7 | 52,038,088 | 206,437,716 | 3.97 倍 |
| 8×8 | 946,204,480 | 2,577,018,160 | 2.72 倍 |
| 8×8 標準艦隊 | 20,774,262,284 | 1,302,617,200,964 | 62.7 倍 |
(5×5〜8×8 は小艦隊 3,2,2,1,1,1。最終行だけ標準艦隊)
面白いのは倍率の動き方だ。同じ艦隊なら、盤が広くなるほど「斜めも」の価値は下がる(21.8 → 2.72 倍)。ところが同じ 8×8 に倍の量の船を詰め込むと 62.7 倍に跳ね上がる。この条項が配給しているのは船と船のあいだの空間なので、混んでいるほど高くつく。
答えの総数を最後の桁まで数える
数字を読む前の「答え」とは、塊がすべてまっすぐな連続マスで、艦隊とちょうど一致し、どの 2 隻も接していないようなマスの集合だ。これは数えられる。
行単位のプロファイル DP を書いた(src/count.ts)。行が安くなる理由はそのまま no-touch 条項だ。
- 1 行の中の長さ 2 以上の連続は、完成した横向きの船でしかありえない。上下に何か置けば接触か屈曲になるから
- したがって行の境界を跨げるのは、同じ列をまっすぐ下に伸びる 1 マスだけ
- 縦の船は、覆う全ての行で同じ列を占める
だからフロンティア(状態)は「上の行が使った列のビットマスク」+「そのうち単独マスの縦の伸び具合(1 マスあたり 3 ビット)」+「まだ置いていない船の在庫」だけで運べる。状態はすべて 1 個の整数に詰めてある。
| 盤 | 標準艦隊の配置数 | フロンティア状態数 | 時間 |
|---|---|---|---|
| 6×6 | 0 | 3,254 | 22 ms |
| 7×7 | 406,664 | 123,454 | 380 ms |
| 8×8 | 20,774,262,284 | 689,430 | 2.6 s |
| 9×9 | 15,624,844,160,880 | 2,643,550 | 16.1 s |
| 10×10 | 1,855,545,978,831,780 | 8,335,174 | 71.4 s |
| 11×11 | 76,057,466,137,845,004 | 24,536,651 | 517.6 s |
標準艦隊は「接触禁止の船マス 20 個」なので、6×6 には 1 通りも載らない。最初に載るのは 7×7 で、406,664 通り。実際に遊ぶ 10×10 盤には約 1,856 兆通りの隠れ場所がある。この数列を 2026 年 9 月に OEIS で検索しても該当は無い。
小さい盤では、DP とコードを共有しない全列挙(enumerate)と、さらに「全部分集合を舐めて直接判定する」総当たりの 2 本と突き合わせてある。
OEIS 2 本で検算する
自分で数えた数列が正しい保証は、他人が公開している数列と一致することだ。この DP は艦隊の指定を差し替えるだけで既知の 2 本に退化する。
- 船を全部 1 マスに縮めて隻数を無制限にすると、答えは「どの 2 マスも接していないマスの集合」=利かないキングの配置そのもの。これは A063443(本来の見出しは「n×n を 1×1 と 2×2 のタイルで敷き詰める場合の数」。2×2 タイルの左上隅がキングに対応する)
- そのうえで斜め条項を落とすと、格子グラフの独立集合 = A006506
| 盤 | 1 マス船・無制限 | A063443 | 斜め条項なし | A006506 |
|---|---|---|---|---|
| 3×3 | 35 | 一致 | 63 | 一致 |
| 5×5 | 6,427 | 一致 | 55,447 | 一致 |
| 7×7 | 12,727,570 | 一致 | 1,280,128,950 | 一致 |
| 8×8 | 1,355,115,601 | 一致 | 660,647,962,955 | 一致 |
| 9×9 | 269,718,819,131 | 一致 | — | — |
コードパスは同じで、fleet の指定だけが違う。テストにも入れてある。
艦隊は縁に寄る(厳密な周辺確率)
DP は前向きの重み(その状態に至る場合の数)を持っている。後ろ向きに 1 回舐めれば、各フロンティア状態から完成まで行ける場合の数も出る。両者を掛けて総数で割ると、
「そのマスに船がある確率」が、標本ではなく全答え空間にわたって厳密に出る。
| 盤 | 隅のマス | 縁のマス | 中央のマス | 隅 ÷ 中央 |
|---|---|---|---|---|
| 6×6 | 43.06% | 33.48% | 25.39% | 1.70 倍 |
| 8×8 | 53.66% | 42.09% | 21.42% | 2.50 倍 |
| 10×10 | 23.63% | 24.52% | 18.25% | 1.29 倍 |
1,855,545,978,831,780 通りすべてにわたって、10×10 の縁のマスには 24.52% の確率で船があり、中央には 18.25% しかない。理由はまた例の条項だ。中央の船マスは 8 近傍を空けさせられるが、隅の船マスは 3 近傍で済む。 縁は地価が安いので、艦隊はそこに集まる。同じ 20 隻分を 8×8 に詰めると隅/中央の差は 2.50 倍まで開く——混雑こそがこの条項の課税対象だからだ。
単調でないのは 10×10 の隅で、縁(24.52%)が隅(23.63%)をわずかに上回る。隅は 1 マスあたり最も安いが 4 個しかなく、そこから伸びた船はすぐ縁を離れる。余裕のある盤ではその損が勝つ。余裕の無い 6×6・8×8 では隅が素直に勝つ(43.06% 対 33.48%、53.66% 対 42.09%)。
ページにはこれをヒートマップで出してある。白紙の盤が、すでに答えの分布を教えている。
サンプラは一様
生成器は答えを一様に引く。後ろ向きの完成数で行ごとに重み付けして前向きに歩くだけで、棄却も再試行も無い。検算は上の厳密な周辺確率そのもので、10×10 を 20,000 回引いてどのマスも厳密値から 0.753% 以上ずれない(6×6 は 200,000 回で 0.215%、8×8 は 100,000 回で 0.498%)。
なお「出荷した 36 枚」のヒートマップも並べてあるが、こちらは何も読み取れない。1 マスあたりの 1σ サンプリング誤差が 7.3% あるのに対し、最大のズレが 16.0% しか無いからだ。解ける盤だけが出荷されるので選択効果はあり得るが、36 枚では示せない。左のグリッドだけが標本ではなく全空間だ。
数字は盤上でいちばん弱い情報
6×6(艦隊 3,2,2,1,1,1)なら答えが 894,296 通りしかなく、全部メモリに載る。だから 12 個の数字の値段を厳密に測れる。
| 6×6 の全答え空間 | |
|---|---|
| 配置の総数 | 894,296 |
| 相異なる「12 個の数字」の組 | 254,877 |
| 数字だけで一意に決まる配置 | 73,536(8.22%) |
| 残る答え(中央値) | 5 |
| 残る答え(最悪) | 36 |
数字を全部読んでも、典型的な 6×6 には答えが 5 通り残る。 算術だけで決まるのは 8.22% しかない。大きい盤ではもっと露骨だ。
| 盤 | 試行 | 数字だけで確定するマス(中央値) | 数字だけで完成する盤 | 残る答え(中央値、上限 12) |
|---|---|---|---|---|
| 6×6 | 300 | 33.3% | 9.0% | 5 |
| 8×8 | 200 | 23.4% | 0.5% | 12 |
| 10×10 | 120 | 28.0% | 0.0% | 12 |
10×10 では 120 回引いて 1 回も決まらなかった。このジャンルで数字はフィルタであって解ではない。
どの数字が強いか
全空間を「ある 1 行が何を印刷するか」で分類すると、値ごとの取り分が厳密に出る。直感に少し逆らう結果になった。
| 行が印刷する数 | 6×6 の答え空間に占める割合 |
|---|---|
| 0 | 17.38% |
| 1 | 30.17% |
| 2 | 27.98% |
| 3 | 17.99% |
| 4 | 5.83% |
| 5 | 0.65% |
いちばん鋭いのは大きい数字だ。 5 を印刷した行は空間の 0.65% しか残さない。そんなに混んだ行が珍しいからだ。0 は 17.38% を残す——フィルタとしてはずっと鈍い。
にもかかわらず、**0 は「その行を一目で全部確定できる唯一の値」**でもある。情報量と、使える情報量は別物だ。空間をいちばん削る数字と、手が動く数字は一致しない。
「形」を印刷することの値段
Bimaru が印刷する駒はカウンタではない。水・潜水艦・向きを持つ端・向きを言わない中央——どれも隣のマスについての主張だ。潜水艦は水 4 マスの言い換えだし、向きを持つ端は「水 3 マス+船 1 マス」だ。秘密を残すのは中央だけで、しかも軸がどちらかという 1 ビットしか残さない。
これは測れる。一様な答えを引き、全マスを公開してから、一意性が保たれる限りランダムな順に公開を消す。それを 2 方言でやる:Bimaru が実際に印刷する「形」と、船マスが「船である」としか言わない弱い方言と。
| 盤 | 試行 | 形で公開(中央値) | 占有だけ公開(中央値) | 形の値段 |
|---|---|---|---|---|
| 6×6 | 120 | 2(平均 1.54) | 2(平均 1.83) | 1.19 倍 |
| 8×8 | 60 | 2(平均 2.60) | 3(平均 3.35) | 1.29 倍 |
| 10×10 | 24 | 5(平均 4.67) | 5(平均 5.88) | 1.26 倍 |
10×10 で平均 4.67 個 対 5.88 個、1.26 倍。差は確かにあるが、思ったより小さい。形のヒントは近傍を丸ごと名指しするので 4〜5 マス分の価値がありそうに見えて、実際には 1/4 マス分しか上乗せしない。理由はもう分かる——その近傍のほとんどは、どうせ no-touch 条項が教えてくれるからだ。盤に印刷されない条項は、最強であるだけでなく、印刷された駒を部分的に冗長にするほど強い。
5 段の梯子
-
count— 行列の数字だけ。払い終わった行の残りは水、残りマス数がちょうど不足分に等しい行は全部船 -
touch— 印刷されない条項。船マスの斜め 4 近傍は常に水。片方の軸に隣がある船マスはもう片方の軸が水。旗艦と同じ長さまで伸びた連続は完成なので両端が水。向きを言わない中央は、片方の軸が閉じた瞬間(盤の端でもよい)に解決する -
line— 1 行(1 列)をノノグラムの行として読む。既知・数字・艦隊が持つ長さと矛盾しない埋め方を全部列挙し、全部が一致する部分だけ採る -
fleet— 艦隊は在庫だ。四方を封じられた塊は使用済みの船。残りの各長さについて置ける場所(バース)を全部挙げ、どの残存船も届かないマスは水。バース数が残隻数と一致したらその長さは確定。未完成の塊はどれかのバースに育つので、候補バース全部が一致する部分は強制 -
probe— 1 マス仮定して下の段を回し、盤が死んだら反対を採る
| 段 | 6×6 確定マス | 6×6 完成 | 8×8 確定マス | 8×8 完成 | 10×10 確定マス | 10×10 完成 |
|---|---|---|---|---|---|---|
count |
76.2% | 9/36 | 48.9% | 0/36 | 58.8% | 0/36 |
touch |
89.7% | 20/36 | 81.0% | 6/36 | 80.5% | 2/36 |
line |
90.3% | 20/36 | 83.8% | 7/36 | 80.6% | 2/36 |
fleet |
100.0% | 36/36 | 100.0% | 36/36 | 100.0% | 36/36 |
probe |
100.0% | 36/36 | 100.0% | 36/36 | 100.0% | 36/36 |
出荷盤は全部 fleet 段で終わる。生成器が「探索なしで閉じられる限り」公開マスを削っているので、これは構成上そうなる。読むべきは登り方だ。10×10 では数字だけで 58.8%、印刷されない no-touch 条項を足すと 80.5%、行を読むと 80.6%、在庫が残りを閉じる。
同じ段を「枝刈り」として値付けし直す
完全探索を回し、分岐点の間に指定の段までしか伝播させない。上限は 200,000 分岐。
| 伝播を許す段 | 6×6 分岐点(中央値) | 6×6 最悪 | 8×8 分岐点(中央値) | 8×8 最悪 |
|---|---|---|---|---|
count まで |
4 | 67 | 805 | 19,613 |
touch まで |
0 | 10 | 3 | 24 |
line まで |
0 | 10 | 3 | 22 |
fleet まで |
0 | 0 | 0 | 0 |
8×8 で count だけなら中央値 805 分岐、touch を足すと 3。答え空間の表では 8×8 標準艦隊で 62.7 倍だった条項が、枝刈りとしては 268 倍効いている。条項の値段は会計の仕方で変わる。両方測らないと分からない。
3 つの誤読
ルールを取り違えたときに何が起きるかも、同じソルバーにフラグを渡して測った。
| 誤読 | 盤 | 意図した答えが依然合法 | 残る答え(中央値、上限 12) | まだ一意 |
|---|---|---|---|---|
| 斜めなら接してよい | 6×6 | 36/36 | 1 | 27/36 |
| 艦隊は決まっていない | 6×6 | 36/36 | 1 | 20/36 |
| 公開マスは「船がある」としか言わない | 6×6 | 36/36 | 2 | 18/36 |
| 斜めなら接してよい | 8×8 | 36/36 | 2 | 18/36 |
| 艦隊は決まっていない | 8×8 | 36/36 | 2 | 13/36 |
| 公開マスは「船がある」としか言わない | 8×8 | 36/36 | 4 | 8/36 |
3 つとも寛容な誤読だ。制約を緩めるだけなので、意図した答えは常に合法のままで、最後まで何も間違っていないように見える。壊れるのは一意性の方だ。「斜めも」を忘れると 8×8 の 36 枚中 18 枚しか一意でなくなり、印刷された駒を単なるカウンタとして読むと 8 枚まで落ちる。
行き詰まるのではなく、答えが複数ある盤を渡されて、どれが作意か分からなくなる。 守るべきはそこだ。
テスト
全 37 テスト(npx vitest run)。主なもの:
- DP 対 全列挙 対 総当たり: 小さい盤で、行プロファイル DP・DFS 列挙・「全部分集合を舐めて判定」の 3 本が同じ数を出す
-
探索 対 全列挙: 5×5 の実盤で、5 段どの段で伝播しても
solveが数える答えの数が全列挙と厳密に一致する。段のどれかが間違ったことを証明したら探索は答えを取りこぼして「一意」と嘘をつくので、これが健全性の本丸 -
OEIS 2 本:
counts.jsonの数列と、その場で計算し直した値の両方を A063443 / A006506 と突き合わせる - サンプラ: 引いた配置が全部合法であること、周辺確率が全列挙の実測と 9 桁一致すること、40,000 回引いて厳密値から 2% 以内であること
- 出荷盤: 答えが厳密に 1 通りで、それが作意の答えであること。数字が答えと一致すること。公開した駒が答えの通りの形であること。どの公開マスを 1 つ消しても盤が壊れること
- 梯子: どの段も答えと矛盾することを証明しないこと、段を上げるほど確定マスが減らないこと
- 段ごとの規則: 今回の実装で一度バグらせた「不足分と残マス数が一致する行は全部船」を含め、各段の個別規則を単独で検証
ページと README の数値は全部 counts.json / stats.json から生成している(npm run notes)。手で転記した数字はゼロだ。
まとめ
- バトルシップが盤に印刷するのは艦隊と数字だが、解いているのはどちらでもない
- 「接してはいけない、斜めも」——盤に書かれないこの一語が、8×8 標準艦隊で答え空間を 62.7 倍、枝刈りとして 268 倍動かす
- 標準艦隊の隠れ場所は 10×10 で 1,855,545,978,831,780 通り。6×6 には 1 通りも無い
- 全空間の厳密な周辺確率が言うには、艦隊は縁に寄る。中央のマスは 8 近傍を空けさせられ、隅は 3 近傍で済むから
- 数字は弱い。 6×6 で全部読んでも中央値 5 通り残り、10×10 では 120 回引いて 1 回も決まらなかった
- 印刷された「形」の上乗せは 1.26 倍しかない。近傍のことは、どうせ no-touch 条項が教えてくれるから
ソルバー内蔵パズル第 66 弾。MIT ライセンス。
