自己再生型・魔インスイーパを解く

先日、Qiitaのトレンドで「[自己再生型・魔インスイーパを創る](https://qiita.com/sakdor/items/4af6d3fae735c37a2eeb)」という記事を見つけました。

非常に面白い発想だと思い、自分も早速プレイしてみましたが残念ながらマインスイーパー自体に慣れておらず、全然クリアすることができませんでした。

そこで、ちょっとした~~チート~~✨エンジニアリング✨を用いて解くことにしました。

## 解くためのコード

デベロッパーツールを開いた状態でhttps://s4kd0r.net/game/ma_insweeper/ にアクセスし、以下のコードを実行すると自動的に地雷にフラグを建てることができます(Google Chrome バージョン: 75.0.3770.142にて確認)。

```javascript
// 二次元配列になっている`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);
});
```

## 実行結果

![solve.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94164/2ff8bd8b-6eb2-f025-0188-87a39553700f.gif)

## 解説

それではコードの解説を行っていきます。

### 1. 二次元配列になっている`cell`を一次元配列化

「魔インスイーパー」では、プログラムで使用している変数がグローバルに露出しています。
そのため、`cell`変数から地雷情報を確認することができます。

```javascript
cell[0][0] // {"mine": 0, "dig": false, "check": false, "flag": false}
```

上記コードでわかるように、`cell`は二次元配列となっています。
そのため、後続のコードで扱いやすくするために以下のコードを用いて二次元配列を一次元配列に変換しています。

```javascript
const flatCells = cell.flatMap(m => m);
```

(余談: 記事を書いている際に気づきましたが、以下のコードの方が良いと思います。)

```javascript
const flatCells = cell.flat();
```

### 2. マインスイーパーのセル用のDOMをすべて取得

「魔インスイーパー」では、セルをDOMで表現しており、クリックなどの処理をシンプルに実装しています。
フラグを建てるためには地雷が埋まっているセルを右クリックする必要があるため、まずはセル用のDOMをすべて取得します。

```javascript
const cellDoms = Array.from(document.querySelectorAll('div[role=button]'));
```

`Array.from`は`Array-like`なオブジェクトを配列にしてくれるので、このあとの処理が楽になります。

### 3. 地雷を生成するために一箇所クリック

「魔インスイーパー」では、初回クリック時に地雷の位置が確定します。
逆に言うと、一度もクリックしなければクリアすることができません😱
そのため、`HTMLElement#click`メソッドを用いて適当なセル(今回は一番左上のセル)をクリックしておきます。

```javascript
cellDoms[0].click();
```

### 4. 地雷が埋まっているセルのDOMのみ取り出す

`3. 地雷を生成するために一箇所クリック`で地雷の場所が確定できたため、次に地雷が埋まっているセルに対応したDOMを取得します。

`2. マインスイーパーのセル用のDOMをすべて取得`で取得したDOM配列から`filter`を用いて取得しています。

セル情報には`mine`というプロパティがあり、該当セルが地雷の場合はこちらに`10`という数値が入っているようでした。
そのため、`flatCells[i].mine`が`10`の場合は`i`番目のDOMは地雷が埋まっているセルのDOMということになります。

`filter`処理を実装したのが以下の行です。

```javascript
const mineCellDoms = cellDoms.filter((_, i) => flatCells[i].mine === 10);
```

### 5. いざ禁忌を破り、魔との契約

ここまで来たらあとは地雷が埋まっているセルに対応するすべてのDOMに対して、右クリックを行えば良いだけです。

JavaScriptにおいて、あるDOMのイベントを発火させるためには`dispatchEvent`というメソッドを呼び出す必要があります。
引数には「`Event`クラスのインスタンス(と便宜上呼ぶ)」を渡す必要があり、インスタンスは`new Event(イベント名)`で作成することができます。

また、今回は地雷が埋まっているセルへのフラグ建てを`setTimeout`を用いて時間差で行うようにしています。
これによって少しずつフラグが建っていくため、「ちょっとしたハッカーっぽさ」を感じることができます(個人差あり)。

```javascript
// 地雷が埋まっているすべてのDOMに対して
mineCellDoms.forEach((mineCellDom, i) => {
  // 5ミリ秒ごとに`contextmenu`イベントを発火(フラグを建てる)
  setTimeout(() => {
    mineCellDom.dispatchEvent(new Event('contextmenu'));
  }, i * 5);
});
```

以上で、コードの解説を終わります。

## 感想

「自己再生するマインスイーパー」と聞いてどういうものなのか全く想像できず、最初は「面白いのか…?」と思っていました。
しかし、実際にプレイしてみると少しずつ再生していくことで焦りが演出されていたり、一度開放した部分でもどこが安全なのかがわからなかったりするのが面白かったです。

自動ソルバ(と呼べる代物ではないかもしれないが…)を実装するに当たり、`dispatchEvent`の使い方を学べたのが大きかったです。

## 参考

- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
- https://developer.mozilla.org/ja/docs/Web/API/EventTarget/dispatchEvent
- https://developer.mozilla.org/ja/docs/Web/Guide/Events/Creating_and_triggering_events