3つめは Battleship(バトルシップ)。2人で遊ぶ「船しずめ」ゲームです。 自分の船の場所は相手に絶対見せないのに、攻撃が当たったか外れたかはウソをつけない。ゼロ知識証明のすごさが、ゲームで体感できます。
何を作るの?
- 2人のプレイヤーが、自分の船の位置を秘密のままコミットする
- 交互に攻撃し、相手は「当たり(HIT)/外れ(MISS)」を答える
- 先に決められた数を当てた方が勝ち
何を学べるの?
- 隠した状態(private state):船の場所はチェーンに出さない
- ズルの検出:当たっているのに「MISS」とウソをついたら、契約が見破る
- 状態マシン:手番の順番を
enumで管理する - テスト:Vitest で「成功も失敗も」全部テストする
たとえ話:手品のタネは見せない
手品師は、タネを見せずに「ちゃんと成立している」ことを観客に納得させますよね。
Battleship も同じ。船の場所(タネ)は見せずに、「いまの答えはルール通り正しい」ことだけを証明します。
しかも、ウソをつこうとすると assert が見破って取引が失敗します。
契約の中身(本物・短い)
状態を enum で表します。
公開ステートと、秘密の入口(witness):
船の場所は、そのままではなくハッシュにしてコミットします。
そして手番のチェックは assert で守ります。
ズルの検出(イメージ):当たっているのに MISS と答えると、assert(...) が「Cheat Detected」で取引を止めます。
テストも学べる
Battleship にはテストの作りかたのページもあります。
- ローカル網(Docker の proof server・indexer・node)で動かす
- 「成功するはず」と「失敗するはず(ズル)」の両方をテスト
- 例:
expect(state.turn).toEqual(TurnState.PLAYER_1_SHOOT)のように状態を確認
進む前に理解しておくこと
- Bulletin Board(秘密の本人確認の感覚)
- ゼロ知識証明(見せずに証明)
- Compact の書きかた(
enum/Set/assert/witness)
開発者として理解すべきこと
- 秘密の状態をハッシュでコミットし、ZK で「正しさ」だけ示す典型
- ルール違反(ズル)を
assertで契約自身に検出させる設計 - 手番などの流れは 状態マシン(enum)で管理する
- テスト(成功も失敗も)を書いて、契約の正しさを守る
公式Docsではどこ?
- Battleship: contract(外部リンク・別タブで開きます)
- Battleship: tests(外部リンク・別タブで開きます)
- ソース:example-battleship(外部リンク・別タブで開きます)
今日のまとめ
- Battleship=船を見せずに、当たり外れだけ正直に証明するゲーム
- 学べること:隠した状態・ズル検出・状態マシン・テスト
- 秘密はハッシュでコミット、ルールは
assertで守る
今はここだけでOK
「船の場所は見せない。でもウソはつけない」——この一文に Battleship の全部がつまっています。
✍️ 白紙練習
答えを見る前に、いったん自分の頭と手から出してみよう。手で書くと「わかったつもり」がはがれます。
状態を表す3つの enum(BoardState / ShotState / TurnState)を書いてみよう
船の場所をハッシュにしてコミットする circuit commitBoardSpace を書いてみよう
Battleship の核心を、自分の言葉で説明してみよう(船の場所と当たり外れの関係)
船の場所は相手に絶対に見せない(秘密の状態)のに、攻撃が当たったか外れたかにはウソをつけない、というゲームです。船の場所はそのままチェーンに出さず、ハッシュにしてコミットします。当たっているのに「MISS」とウソをつこうとすると assert が見破って取引が失敗するので、「場所は見せない、でもウソはつけない」が成立します。
船の場所は、どうやってコミットする?
そのままではなく、ハッシュにしてコミットします。
当たっているのに MISS とウソをつくと、どうなる?
assert(…) が「Cheat Detected」で取引を止めます(ズルが契約自身に検出される)。
手番の順番は何で管理する?
enum(状態マシン)で管理します。TurnState の PLAYER_1_SHOOT などで番を表します。
witness localSk() と witness localCheckBoard(x) はそれぞれ何のための入口?
localSk() は自分の秘密カギを返す入口、localCheckBoard(x) は撃たれた場所が HIT か MISS かを返す入口です。
テストでは何を確認する? Vitest で何をテストする?
「成功するはず」と「失敗するはず(ズル)」の両方をテストします。例として state.turn が TurnState.PLAYER_1_SHOOT になっているかなど、状態を確認します。
つぎに読むページ
➡️ ブラウザの DApp と本番デプロイへ。Leaderboard チュートリアル