初手で負けないマインスイーパー:Claude Codeと決めた盤の大きさと旗の操作

子どもでも遊べるボードゲームのアプリを、Claude Code に手伝ってもらいながら作っています。3つ目のゲームとしてマインスイーパーを足すとき、最初の1手で地雷を踏んで終わる作りだけは避けたいと考えました。この記事では、初手の安全をどう作ったか、旗の操作で何を選んだか、ChatGPT のレビューで出てきた不備までを書きます。

作りたかったもの

1つのアプリを開くと、遊ぶゲームを選べる形にしています。すでに2本入っていて、その3つ目としてマインスイーパーを足すのが今回の作業でした。

Claude Code に頼む前に、できあがりの条件を自分の言葉で並べました。

  • ゲームを選んで遊べる
  • 地雷が配置される
  • 最初の1手で地雷を踏まない
  • 旗を立てられる
  • 0のマスは連鎖して開く
  • 勝敗が決まる
  • 難易度が動く
  • テストが通っている

条件を先に決めておくと、あとで「できた」と言えるかどうかを自分で判断できます。

難易度は2種類にしました。「はじめて」が5×5で地雷3個、「なれてきた」が6×6で地雷5個です。

盤をこれ以上大きくしなかったのには理由があります。縦向きのiPhoneでは、6列なら1マスが約54ポイントになります。ポイントは画面の大きさの単位です。7列にすると約46ポイントまで縮みます。Appleは、押せる部分を44×44ポイント以上にすることを勧めています。46ポイントはその目安のすぐ上で、余裕がありません。指の太さは大人も子どもも変えられないので、列を増やさずに6列までとしました。

Claude Code への頼み方

上の条件を箇条書きのまま渡して、実装を Claude Code に頼みました。実際に打ち込んだ指示の文は残していないので、ここには載せません。やったことは「条件を並べて渡し、作ってもらう」だけです。

もう1つ、先に決めて渡したことがあります。既存の2本と、どこまで部品を共有するかです。共有するのは3つだけにしました。ボタンの見た目、読みやすい幅で並べる処理、音を鳴らす処理です(順に PrimaryButtonStyle、readableColumn、SoundPlaying)。逆に、駒や位置、盤とマスの表示、対戦相手の思考など、対戦ゲームのために作った部品は流用していません。

できあがったものは、そのまま信じずに ChatGPT にも見てもらいました。ここで、今回の変更とは別のところに残っていた不備が1つ出てきました。

つまずいたところ

一番迷ったのは、最初の1手です。ふつうに作ると、先に地雷を置いてから盤を見せることになります。すると、運が悪ければ1手目で地雷を踏んで終わります。「はじめて」と名付けた盤で最初のタップが終わりになるのは、遊ぶ人にはただの理不尽です。

2つ目は旗の置き方です。マインスイーパーでは、地雷がありそうなマスに旗を立てます。多くのアプリは長押しで旗を立てます。ただ、長押しは画面のどこにも書かれていないので、初めて触る子どもには見つけられません。ふつうのタップとの区別もあいまいで、開けたいだけなのに旗が立つことが起きます。

3つ目は、テストが書けない形になっていたことです。難易度を enum にしていたのが原因でした。enum は、あらかじめ決めた選択肢だけを持てる型です。「地雷が多すぎる盤」はその選択肢にないので、設定として表現できません。それなのにコードの中には、地雷が多すぎるときに止める確認が入っていました。この確認は通ることのない道になり、テストで確かめられません。

4つ目は、ChatGPT の指摘で見つかった既存の不備です。タイトル画面が、1人で遊ぶゲームにも「2人対戦で始める」という使わない情報を渡していました。コードでは GameStart(mode: .twoPlayers) の部分です。動いてはいますが、意味のない値でした。

どう判断したか

最初の1手については、地雷を置くタイミングを後ろにずらしました。盤を見せる時点では、地雷を決めません。最初にタップされたマスと、その周囲8マスを避けて地雷を置きます。こうすると、どこを押しても1手目では負けません。

この流れを図にします。地雷の配置が、最初のタップより後ろに来ているところが要点です。

flowchart TD
  A["盤を表示(地雷はまだ決めない)"] --> B["最初のタップ"]
  B --> C["そのマスと周囲8マスを避けて地雷を置く"]
  C --> D["0のマスは連鎖して開く"]
  D --> E["2手目以降はふつうに進む"]

旗から始める人のことも考えました。旗を立てただけでは地雷を置きません。そのため、先に旗を立ててから開いた場合も、初手の安全はそのまま保たれます。

旗の操作は、長押しをやめました。盤の下に「あける」「はた」の切替を常に出しておき、押して切り替える形です。いま選ばれている側は、色と枠の両方で示します。色だけで示すと、色の見え方が違う人には伝わらないためです。「もういっかい」を押したときは、必ず「あける」に戻します。前回の続きで旗のまま始まると、最初のタップの意味が変わってしまうからです。

開く処理は、0のマスから連鎖して開きます。旗が立っているマスは、連鎖でも開きません。地雷以外を全部開けば成功で、旗を立てきる必要はありません。

テストが書けない問題は、盤の設定を難易度から切り離して解きました。マスの数と地雷の数を MinesweeperConfiguration という別のものにしました。難易度は、その設定を選ぶだけの役目にしました。設定を自由に作れるので、「地雷が多すぎる盤」もテストの中で作れます。通ることのない確認ではなくなりました。

タイトル画面は、渡す情報の形を作り直しました。対戦ゲームの入口は対戦ゲームだけを表すようにして、マインスイーパーは専用の入口から直接ゲーム画面へ進みます。使わない対戦モードを渡す場所が無くなりました。

分かれ道を図にします。1人で遊ぶゲームが、対戦用の入口を通らなくなったところが変わった点です。

flowchart LR
  T["タイトル画面"] --> G["GameStart(対戦ゲーム)"]
  T --> M["MinesweeperStart"]
  G --> G1["対戦ゲームの画面"]
  M --> M1["マインスイーパーの画面"]

時間の計測と、難易度ごとのベスト記録も入れました。保存先は端末の中だけで、外には送りません。iOSの UserDefaults を使います。アプリの小さな設定を端末に保存する仕組みです。この仕組みを使うアプリは、プライバシーマニフェスト(PrivacyInfo.xcprivacy)というファイルで理由を宣言します。利用理由 CA92.1、収集するデータなし、トラッキングなしと書きました。CA92.1 は、アプリ自身しか読めない情報の読み書きに使う理由の番号です。

書式が崩れていないかは、コマンドで確かめられます。書き方が正しければ OK と表示されます。中身が正しいかどうかまでは見ません。

plutil -lint PrivacyInfo.xcprivacy

できたもの

iPhone 17 Pro のシミュレータでテストを実行し、103件のテストが全部通りました。シミュレータは、パソコンの中でiPhoneを再現して動かす仕組みです。テストは11のまとまりに分かれていて、内訳は既存が70、新しく書いたものが33です。既存のテストは1件も緩めていません。ビルドの警告も0件でした。

初手の安全は、押す場所を5通り(角・辺・中央)、乱数を30通り変えて確かめました。最初のマスとその周囲8マスに地雷が来ないこと、必ず連鎖して複数のマスが開くことを見ています。さらに画面側でも、実際の乱数を使って200回繰り返し、最初のタップで負けないことを確認しました。

地雷が多すぎる設定も確かめました。5×5に17個を渡すと拒否され、ちょうど置ける上限の16個は通ります。テストが書ける形にした効き目が、ここに出ています。

旗のまわりは、条件の境目だけを並べてテストしました。境目とは、できることとできないことが切り替わるところです。

  • 最初に旗を立てても、地雷が置かれない
  • 連鎖が旗のマスを開かない
  • 旗のマスを開こうとすると拒否される
  • 開いているマスに旗を立てられない
  • 勝敗が決まったあとは、開くことも旗も拒否される
  • 「もういっかい」で状態が消え、操作が「あける」に戻る

勝てることも確かめました。両方の難易度で乱数を20通り変え、地雷以外を順に開くと必ず成功で終わります。

時間と記録は、テスト用の時計で時間を進めて確認しました。

  • 最初のマスを開いてから数え始める
  • クリアしたときに確定し、ベスト記録に残る
  • 前より遅ければ更新されない
  • 失敗したときは記録しない
  • 記録が難易度ごとに分かれる

プライバシーマニフェストは、書式と、ビルド後のアプリに入っていることを確かめました。ゲーム選択画面に3つのゲームが並ぶことも、シミュレータの画面で確認しています。

1つ、数えて安心した数字があります。最初の1手で盤が全部開いて、そのままクリアになる割合です。それぞれ20000回試したところ、「はじめて」で3.9%、「なれてきた」で0.3%でした。まれな当たりの範囲なので、設計は変えていません。

まだ手が付いていないこともあります。実際に盤をタップする操作の確認、時間表示が1秒ごとに進むこと、iPadでの表示、VoiceOver(画面を読み上げる機能)での読み上げです。これは次のタスクに回しました。

まとめ

  • 理不尽をコードの都合で残さない。「最初の1手で負ける」は、地雷を置くタイミングをタップより後ろにずらすだけで無くなりました。AIに頼むときも、こういう条件は先に言葉にしておくと伝わります。
  • 隠れた操作は、初めての人には無いのと同じです。長押しをやめて、画面に出ている切替にしました。色だけでなく枠でも示す、という細かいところも先に決めました。
  • テストできない形は、先に形を直す。難易度と盤の設定を分けただけで、「地雷が多すぎる」を試せるようになりました。ChatGPT のレビューで出た既存の不備も、動いていても直しておくと後が楽です。

次は、実際に盤をタップする操作の確認と、iPadやVoiceOverでの見え方・読み上げを片付けます。

参考にした資料

この記事は、筆者の開発記録をもとにAI(Claude)が下書きし、筆者が確認して公開しています。