先日、Qiitaのトレンドで「自己再生型・魔インスイーパを創る」という記事を見つけました。
非常に面白い発想だと思い、自分も早速プレイしてみましたが残念ながらマインスイーパー自体に慣れておらず、全然クリアすることができませんでした。
そこで、ちょっとしたチート✨エンジニアリング✨を用いて解くことにしました。
解くためのコード
デベロッパーツールを開いた状態でhttps://s4kd0r.net/game/ma_insweeper/ にアクセスし、以下のコードを実行すると自動的に地雷にフラグを建てることができます(Google Chrome バージョン: 75.0.3770.142にて確認)。
// 二次元配列になっている`cell`を一次元配列化
const flatCells = cell.flatMap(m => m);
// マインスイーパーのセル用のDOMをすべて取得
const cellDoms = Array.from(document.querySelectorAll('div[role=button]'));
// 地雷を生成するために一箇所クリック
cellDoms[0].click();
// 地雷が埋まっているセルのDOMのみ取り出す
const mineCellDoms = cellDoms.filter((_, i) => flatCells[i].mine === 10);
// 地雷が埋まっているすべてのDOMに対して
mineCellDoms.forEach((mineCellDom, i) => {
// 5ミリ秒ごとに`contextmenu`イベントを発火(フラグを建てる)
setTimeout(() => {
mineCellDom.dispatchEvent(new Event('contextmenu'));
}, i * 5);
});
実行結果
解説
それではコードの解説を行っていきます。
1. 二次元配列になっているcell
を一次元配列化
「魔インスイーパー」では、プログラムで使用している変数がグローバルに露出しています。
そのため、cell
変数から地雷情報を確認することができます。
cell[0][0] // {"mine": 0, "dig": false, "check": false, "flag": false}
上記コードでわかるように、cell
は二次元配列となっています。
そのため、後続のコードで扱いやすくするために以下のコードを用いて二次元配列を一次元配列に変換しています。
const flatCells = cell.flatMap(m => m);
(余談: 記事を書いている際に気づきましたが、以下のコードの方が良いと思います。)
const flatCells = cell.flat();
2. マインスイーパーのセル用のDOMをすべて取得
「魔インスイーパー」では、セルをDOMで表現しており、クリックなどの処理をシンプルに実装しています。
フラグを建てるためには地雷が埋まっているセルを右クリックする必要があるため、まずはセル用のDOMをすべて取得します。
const cellDoms = Array.from(document.querySelectorAll('div[role=button]'));
Array.from
はArray-like
なオブジェクトを配列にしてくれるので、このあとの処理が楽になります。
3. 地雷を生成するために一箇所クリック
「魔インスイーパー」では、初回クリック時に地雷の位置が確定します。
逆に言うと、一度もクリックしなければクリアすることができません😱
そのため、HTMLElement#click
メソッドを用いて適当なセル(今回は一番左上のセル)をクリックしておきます。
cellDoms[0].click();
4. 地雷が埋まっているセルのDOMのみ取り出す
3. 地雷を生成するために一箇所クリック
で地雷の場所が確定できたため、次に地雷が埋まっているセルに対応したDOMを取得します。
2. マインスイーパーのセル用のDOMをすべて取得
で取得したDOM配列からfilter
を用いて取得しています。
セル情報にはmine
というプロパティがあり、該当セルが地雷の場合はこちらに10
という数値が入っているようでした。
そのため、flatCells[i].mine
が10
の場合はi
番目のDOMは地雷が埋まっているセルのDOMということになります。
filter
処理を実装したのが以下の行です。
const mineCellDoms = cellDoms.filter((_, i) => flatCells[i].mine === 10);
5. いざ禁忌を破り、魔との契約
ここまで来たらあとは地雷が埋まっているセルに対応するすべてのDOMに対して、右クリックを行えば良いだけです。
JavaScriptにおいて、あるDOMのイベントを発火させるためにはdispatchEvent
というメソッドを呼び出す必要があります。
引数には「Event
クラスのインスタンス(と便宜上呼ぶ)」を渡す必要があり、インスタンスはnew Event(イベント名)
で作成することができます。
また、今回は地雷が埋まっているセルへのフラグ建てをsetTimeout
を用いて時間差で行うようにしています。
これによって少しずつフラグが建っていくため、「ちょっとしたハッカーっぽさ」を感じることができます(個人差あり)。
// 地雷が埋まっているすべてのDOMに対して
mineCellDoms.forEach((mineCellDom, i) => {
// 5ミリ秒ごとに`contextmenu`イベントを発火(フラグを建てる)
setTimeout(() => {
mineCellDom.dispatchEvent(new Event('contextmenu'));
}, i * 5);
});
以上で、コードの解説を終わります。
感想
「自己再生するマインスイーパー」と聞いてどういうものなのか全く想像できず、最初は「面白いのか…?」と思っていました。
しかし、実際にプレイしてみると少しずつ再生していくことで焦りが演出されていたり、一度開放した部分でもどこが安全なのかがわからなかったりするのが面白かったです。
自動ソルバ(と呼べる代物ではないかもしれないが…)を実装するに当たり、dispatchEvent
の使い方を学べたのが大きかったです。