はじめに
React を勉強しているけど、良い題材がないって人は結構いるんじゃないかなと思っています。
TODO リストを作った後はどうしよう、みたいな。
そこで提案です。
マインスイーパーを作ろう!
マインスイーパーとは
昔の Windows に初期装備されていた地雷探しゲームです。
ググってみると懐かしい画像がたくさん出てきます。
遊び方としては、n 行 * m 列 のマスが表示されていて、マスをクリックして開いていきます。
開いたマスに爆弾があるとゲームオーバーです。
爆弾ではないマスを開くと、いくつの爆弾に隣接しているかの数字が表示されます。
数字をヒントに爆弾以外のマスを全て開き終わるとクリアです。
開いていないマスには爆弾があるかもしれないという目印の旗を立てることができます。
詳細なルール: wikipedia
マインスイーパーを作ってみることで以下の学びを得られる可能性があります。
- 状態管理をどうするか
- 二重になっているリストの取り扱い
- ゲームっぽく見せるためのスタイリング
- 盤面作成とクリック時のアルゴリズム
仕様
ルールに則って以下のような仕様で作成します
- 行数 n は input で指定可能
- 列数 m は input で指定可能
- 爆弾数 l は input で指定可能
- START ボタンクリックで盤面が表示される
- マスをクリックして開く
- 開いたマスが爆弾だったらゲームオーバー
- 初手に爆弾を引くとただの運ゲーなので、初手爆弾にはならないように実装する
- 開いたマスが爆弾以外だったら隣接する爆弾の数を表示
- 隣接する爆弾がない場合、開いたマスに隣接するマスも開く
- その開いたマスに隣接する爆弾がない場合、さらに隣接するマスも開く
- endless...
- 旗が立っていても開く
- その開いたマスに隣接する爆弾がない場合、さらに隣接するマスも開く
- 隣接する爆弾がない場合、開いたマスに隣接するマスも開く
- 爆弾以外の全てのマスを開くとゲームクリア
- 開いたマスが爆弾だったらゲームオーバー
- マスを右クリックして旗を立てる
- 既に旗が立っている場合は旗を消す
余力がある場合
私は余力がなかったので以下の一つ目(一番簡単なやつ)しか実装していません。
- 「初級」「中級」「上級」ボタンを用意し、クリックすると以下の設定になる
- 初級: 行数 9, 列数 9, 爆弾数 10
- 初級: 行数 16, 列数 16, 爆弾数 40
- 初級: 行数 16, 列数 30, 爆弾数 99
- 既に開かれた数字マスを右クリックしたとき、マスの数字と同数の旗と隣接している場合に限り、マスに隣接する全ての閉じたマスを開くことができる
- 一手戻る、一手進むボタンの実装
- 盤面作成時の乱数をシード固定できるようにする
参考実装
私の実装です(参考になるかは不明)。
デプロイしてあるので実際に遊べます↓
ゲームの状態には class ではなく object を使うようにしました。
盤面クリックなどで状態を変化させる時は、関数の引数として Game オブジェクトを渡す形です。
// こんな感じ
click_cell(game)
// もしクラスで書くとこんな感じ
game.click_cell()
なんとなく状態管理するときに object の方が楽かな〜と思って class にするのはやめたのですが、ちょっとコードが読みづらくなった気もします。
でも class にした時って状態管理ライブライにインスタンスを渡すのが良いのかとかベストプラクティスがわからないので、もし詳しい方がいたら教えてください
状態管理には jotai を使わせていただきました。
あと無駄なレンダリングを抑えるためにあれこれやっていたときに遭遇して悩まされたんですが、これってもしかして当たり前の挙動なんでしょうか。
終わりに
人によっていろんな実装があると思います。
他の人の実装も見てみたい(切実)。
もし実装された方がいらっしゃいましたらコメントくださると嬉しいです。
見に行きます。