マインスイーパーを作る
方針
- Javascriptで作る
- Reactを使う。
パネルを一気に開くやつ
- フラッドフィルアルゴリズムが使えそう
- 斜めは開けない。
- 爆弾があるマスはあけない。
- 数字があるマス自体は開くが、その隣接マスは開かない。
- 既に空いてるマスの隣接マスまでは調べなくて良い(?)
データ管理クラス
データ管理のためのクラスはこんな感じにする。
セル
1マスに相当する。
重ね合わせの状態表現するのはなかなか難しい。
データの整合性の担保は後述するFieldクラスの方におまかせする。
export type Cell = {
Open: boolean // 開いているか
Flag: boolean // 旗が立っているか
Bomb: boolean // 爆弾があるか
Count: number // 周囲に爆弾が何個あるか
}
フィールド
あんまりオブジェクト指向な感じにはせず、とにかくメソッドは新規オブジェクトを返してイミュータブルな感じで使う方向性で行く。インスタンスメソッドである必然性はほぼなくなってしまった。
export class Field {
// セルの一覧
public get Cells(): Cells(): Readonly<Cell[]>
// マスを開ける
public Open(index: number): Field {}
// 旗を立てる
public PutFlag(index: number): Field{}
// 旗を消す
public RemoveFlag(index: number): Field {}
// 駆除成功したか
public IsComplete(): boolean {}
// 爆死したか
public IsGameOver(): boolean {}
// 縦横の幅
public Size(): number {}
// ランダムなマップを取得
public static GetRandomField(size: number, bomb: number): Field{}
}
コンポーネント
こんな感じにする。
GameElement
└FieldElement
├CellElement
├...
└CellElement
とりあえず動くようになった。
それっぽくなってきた。
デジタルっぽいフォントはこれ使った。
効果音はこれ。
隠し機能の画像はDalle2を利用。
そして完成
モバイル対応
モバイル端末では右クリックができないのでこんなUIを表示するようにした。
このUIはタッチした時のみ表示され、マウスでクリックした場合は表示されない。
つまったところ
タッチデバイスの判定
タッチデバイスの判定に苦労した。
タッチ対応のノートパソコンではこのUIは出て欲しくない。タッチしかできない端末でのみ表示されてほしい。
e.pointerType === "touch"
最初試したのはこれ。これはChromeのDev Toolsでは正しく動作したが、なぜか自分のiPhone(iOS15.6)では正しく動作してくれなかった。Can I useではiOS13以降は使えると書いてあるのにおかしい。
window.matchMedia('(hover: none) and (pointer: coarse)').matches
次に試したのはこれ。こっちは正しく動いた。
CSSのメディアクエリーでポインティングデバイスの「粗さ」を見るらしい。指でタッチする場合はマウスほど正確にタッチできない。だからこれでタッチ判定する。
iOSでタッチイベントが期待どおりに上手く動かないことがある気がする
表題の通り、気のせいかiOSでタッチイベントが期待どおりに上手く動かないことがある気がする。
どうもセルの真ん中をクリックするときに動いてくれてないことが多い。
これは推測だが、クリックした場所に文字列があると選択イベントでクリックが上書きされるのではないか。この現象はuser-select: none;
でも解決しない。
文字を透明なrectで覆うことで現象が起きなくなった。