Help us understand the problem. What is going on with this article?

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

先日、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);
});

実行結果

solve.gif

解説

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

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.fromArray-likeなオブジェクトを配列にしてくれるので、このあとの処理が楽になります。

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

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

cellDoms[0].click();

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

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

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

セル情報にはmineというプロパティがあり、該当セルが地雷の場合はこちらに10という数値が入っているようでした。
そのため、flatCells[i].mine10の場合は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の使い方を学べたのが大きかったです。

参考

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away