Javascript 簡単にマインスイーパーを作ってみる【初心者向け】

# はじめに
プログラミング初学者の学習用にマインスイーパーを作ってみました。
嘘です、マインスイーパーやったことなかったので、変なものができました。

以下説明のため作成した簡易版のURLです。
（ページ末尾に本気モードのURLもあります）

https://kokekokko0116.github.io/MineSweeperMaze/


# 概要

この記事内で使用している技術は以下のとおりです。
・２重配列
・ループ文
・条件分岐
・クリックイベント
・ランダム関数

マインスイーパー作成必要な処理は以下のとおりです。
・ボード作成処理
・爆弾配置処理
・周囲の爆弾の数をパネルに表示する処理
・パネルクリック処理

# ２重配列・２重ループ文
２重配列・２重ループ文の説明です
※理解している方は読み飛ばしてください

### ２重配列

`縦をy軸` `横をx軸`とすると
`hoge[y][x]`という形で呼び出せます
```
let sample =[
    [0-0, 0-1, 0-2],
    [1-0, 1-1, 1-2],
    [2-0, 2-1, 2-2],
];
```

例えば、上記のような配列があった場合、
`sample[0][2] = '0-2'`
`sample[1][0] = '1-0'`
`sample[2][1] = '2-1'`
となります。

### ２重ループ文

y軸のループ内にx軸のループを記述することで、y軸の階層ごとにx軸のループを回すことが可能です。

```
for(let y = 0; y < 3; y++){
    for(let x = 0; x < 3; x++){
        sample[y][x] = y-x;
    }
}
```


# ボード作成処理

ゲームボード作成には２重配列とループ文を使用しています。
まずは9*9マスのボードを作成します。
ボードの中心からスタート可能で、外側に出ることができればゲームクリアという感じにしたいので、
`board[4][4]（ボードの中心）　=　1`に設定しています
```
// ボード（9*9マス）作成
let board = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
];
```

その後２重のループ文を使って、`board`の要素を一つずつ読み込みゲームボードを作成します。

```
<table id="board" style="text-align: center;"></table>

(中略)

// ボードを生成する処理
let BoardTable = document.getElementById("board");
for (let y = 0; y < 9; y++) {
    let tr = document.createElement("tr");
    for (let x = 0; x < 9; x++) {
        let td = document.createElement("td");
        td.style.width = "50px";
        td.style.height = "50px";
        td.style.border = "solid 1px black";
        td.id = y + '-' + x;
        if (board[y][x] == 1) {
            td.style.backgroundColor = "white";
        } else {
            td.style.backgroundColor = "silver";
        }
        // 生成
        tr.appendChild(td);
    }
    BoardTable.appendChild(tr);
}
```
これにより、`<table id="board" style="text-align: center;">`内に次のようなボードが描画されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959999/851fb305-4060-c079-e1a4-a04e6670a0f8.png" width=45% >


# 爆弾配置処理


先ほど作成した9*9マスのゲームボードにランダムに爆弾を配置する処理を作成します。
爆弾の配置は`let mines[]`という配列にループ文とランダム関数を使って保存していきます。
ランダム関数`Math.floor(Math.random() * 100);`は0~99のランダムな数字を生成します。

そのため、以下の条件分岐で各マスに15%の確率で爆弾が設置される処理を記述できます。
`if(Math.floor(Math.random() * 100) < 15)`

```
let mines = []; // からの配列を作成
for (let y = 0; y < 9; y++) {
    mines[y] = []; // y行目の配列を作成
    for (let x = 0; x < 9; x++) {
        if (board[y][x] == 1) {
            mines[y][x] = 0;
        }
        else {
            rand = Math.floor(Math.random() * 100); // 0~99 までのランダムな数
            if (rand < 15) {
                mines[y][x] = 1; // 爆弾あり
            } else {
                mines[y][x] = 0; // 爆弾なし
            }
        }
    }
}
```

これで各マスに爆弾があるマス、ないマスを作成することができました。

# 周囲の爆弾の数をパネルに表示する処理

### 爆弾カウント処理
それでは、先ほど設置した爆弾が周囲８マスに何個あるかカウントしてパネルに表示する処理を作成します。
まずは周囲８マスの爆弾の個数をカウントする処理を作りましょう

```
// 周りの爆弾の数を数える処理
let minesAround = [];
for (let y = 0; y < 9; y++) {
    minesAround[y] = [];
    for (let x = 0; x < 9; x++) {
        let counter = 0;
        if (y != 0 && x != 0) counter += mines[y - 1][x - 1];   // 左上
        if (y != 0) counter += mines[y - 1][x];                 // 上
        if (y != 0 && x != 8) counter += mines[y - 1][x + 1];   // 右上
        if (x != 0) counter += mines[y][x - 1];                 // 左
        if (x != 8) counter += mines[y][x + 1];                 // 右
        if (y != 8 && x != 0) counter += mines[y + 1][x - 1];   // 左下
        if (y != 8) counter += mines[y + 1][x];                 // 下
        if (y != 8 && x != 8) counter += mines[y + 1][x + 1];   // 右下
        minesAround[y][x] = counter;
    }
}
```

`minesAround[]`という配列にカウントを格納していくことでマスの位置ごとにカウントが分かります
`panel[y][x]`を中心とした周囲８マスを調べます
周囲8マスにアクセスしたい場合以下のルールを参照してください
```
y軸上で上方向は`y-1` 下方向は`y+1`
x軸上で左方向は`x-1` 右方向は`x+1`
```
必ずしも周囲に8マスある訳ではないため、以下のルールも参照しておきましょう
```
例えば、該当マスが panel[0][8](一番右上)の場合
y軸上方向 x軸右方向にマスは存在しません
そういう場合を考慮して以下のような条件分岐を作ります

if(y == 0) // y軸の一番上の時
if(x == 8) // x軸の一番右の時
```
今回のコードでは上記の条件分岐に該当しない場合に処理を行う設定にしています。

### カウントをパネルに表示する処理
`showCounter(y, x)`という関数を作成しています。
関数に y , x の情報を渡すことで、
どのマスの周囲8マスのパネルにカウントを表示すればいいのかを指示することができます。
```
function showCounter(y, x) {
    let counter = 0;
    for (let y = 0; y < 9; y++) {
        for (let x = 0; x < 9; x++) {
            if (board[y][x] == 1) {
                if (y != 0 && x != 0) {
                    let td = document.getElementById((y - 1) + '-' + (x - 1));
                    td.innerText = minesAround[y - 1][x - 1];
                }
                if (y != 0) {
                    let td = document.getElementById((y - 1) + '-' + (x));
                    td.innerText = minesAround[y - 1][x];
                }
                if (y != 0 && x != 8) {
                    let td = document.getElementById((y - 1) + '-' + (x + 1));
                    td.innerText = minesAround[y - 1][x + 1];
                }
                if (x != 0) {
                    let td = document.getElementById((y) + '-' + (x - 1));
                    td.innerText = minesAround[y][x - 1];
                }
                if (x != 8) {
                    let td = document.getElementById((y) + '-' + (x + 1));
                    td.innerText = minesAround[y][x + 1];
                }
                if (y != 8 && x != 0) {
                    let td = document.getElementById((y + 1) + '-' + (x - 1));
                    td.innerText = minesAround[y + 1][x - 1];
                }
                if (y != 8) {
                    let td = document.getElementById((y + 1) + '-' + (x));
                    td.innerText = minesAround[y + 1][x];
                }
                if (y != 8 && x != 8) {
                    let td = document.getElementById((y + 1) + '-' + (x + 1));
                    td.innerText = minesAround[y + 1][x + 1];
                }
            }
        }
    }
}

showCounter(4, 4); // スタート地点の周囲のカウントを表示
```

`showCounter(4, 4)`をことで行うことで
スタート地点の周囲8マスのカウンターは表示された状態になっています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959999/69867b5e-77cf-7d7c-eb5a-c67f25837f24.png" width=50% >

# クリック関数作成

クリック時に行う処理
・周囲8マスのカウンターを表示
・上下左右4マスをクリック可能にする
・ボードの端にたどり着いたらゲームクリア
・パネルが爆弾だった場合ゲームオーバー

```
function ClickPanel(y, x) {
    board[y][x] = 1;
    let td = document.getElementById(y + '-' + x);
    td.style.backgroundColor = "white";
    if (mines[y][x] == 1) {
        td.innerText = "爆";
        alert("ゲームオーバー");
    } else {
        td.innerText = '';
        showCounter(y, x); // 関数呼び出し
        if (y == 0 || x == 0 || y == 8 || x == 8) {
            let screen = document.getElementById("screen");
            screen.innerText = "ゲームクリア次のステージに進む";
        }
    }
}
```

`ClickPanel(y, x)`関数内で`showCounter(y, x)`を呼び出すことで、
周囲8マスのカウンターを表示することができます。

上下左右の4マスをクリック可能にする処理は`showCounter(y, x)`内に追記します

以下追記後のコードです
```
function showCounter(y, x) {
    let counter = 0;
    for (let y = 0; y < 9; y++) {
        for (let x = 0; x < 9; x++) {
            if (board[y][x] == 1) {
                if (y != 0 && x != 0) {
                    let td = document.getElementById((y - 1) + '-' + (x - 1));
                    td.innerText = minesAround[y - 1][x - 1];
                }
                if (y != 0) {
                    let td = document.getElementById((y - 1) + '-' + (x));
                    td.innerText = minesAround[y - 1][x];
                    td.onclick = function () {
                        ClickPanel(y - 1, x);
                    };
                    td.style.cursor = "pointer";
                }
                if (y != 0 && x != 8) {
                    let td = document.getElementById((y - 1) + '-' + (x + 1));
                    td.innerText = minesAround[y - 1][x + 1];
                }
                if (x != 0) {
                    let td = document.getElementById((y) + '-' + (x - 1));
                    td.innerText = minesAround[y][x - 1];
                    td.onclick = function () {
                        ClickPanel(y, x - 1);
                    };
                    td.style.cursor = "pointer";
                }
                if (x != 8) {
                    let td = document.getElementById((y) + '-' + (x + 1));
                    td.innerText = minesAround[y][x + 1];
                    td.onclick = function () {
                        ClickPanel(y, x + 1);
                    };
                    td.style.cursor = "pointer";
                }
                if (y != 8 && x != 0) {
                    let td = document.getElementById((y + 1) + '-' + (x - 1));
                    td.innerText = minesAround[y + 1][x - 1];
                }
                if (y != 8) {
                    let td = document.getElementById((y + 1) + '-' + (x));
                    td.innerText = minesAround[y + 1][x];
                    td.onclick = function () {
                        ClickPanel(y + 1, x);
                    };
                    td.style.cursor = "pointer";
                }
                if (y != 8 && x != 8) {
                    let td = document.getElementById((y + 1) + '-' + (x + 1));
                    td.innerText = minesAround[y + 1][x + 1];
                }
            }
        }
    }
}
```

これでマインスイーパーに必要な処理の作成が完了しました。

### GitHub
以下はgithubのコードです、参考にして見てください。

https://github.com/kokekokko0116/MineSweeperMaze


# 最後に

配列、ループ文、条件分岐を覚えるとコードの書きやすさが変わります。
今回のサンプルコードを使って色々遊んでみてください。

サンプルコード改造例として、
マインスイーパーhardモードのURLも貼っておきますので遊んでみてください

https://kokekokko0116.github.io/MineSweeperMaze/maze

読んでいただきありがとうございました！



# 宣伝
普段は株式会社バイネームで開発業務を行っています。
興味を持っていただけたら以下のリンクからアクセスして見てください！

https://byn.jp


https://byn.jp/flyer/4fc47013-4622-4aa3-be1a-08e91d680b9b