html+css+jsの3ファイルだけで本格的マインスイーパーを作った (Qiita内で遊べるよ!!)

html,CSS,JavaScriptの3ファイルのみを使ってマインスイーパーを作った。
[CodePen](https://codepen.io/td12734/pen/BaBEYmM)上に書いたので、ページに埋め込む事で**Qiitaの記事内でも遊べます**。

# マインスイーパーで遊ぼう!
<p class="codepen" data-height="540" data-theme-id="0" data-default-tab="result" data-user="td12734" data-slug-hash="BaBEYmM" style="height: 541px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Minesweeper">
  <span>See the Pen <a href="https://codepen.io/td12734/pen/BaBEYmM">
  Minesweeper</a> by T.D (<a href="https://codepen.io/td12734">@td12734</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

[大きい画面で遊びたい方はこちら。](https://codepen.io/td12734/full/BaBEYmM)

## 遊び方

- 左クリック：ボタンを押す、マスを開く
- 右クリック：マスの上に旗を立てる、マスの上の旗を取り除く
- キーボード：カスタムゲーム設定画面で数値を入力する

地雷がないマスを全て開けるゲームです。

## ゲームルール

- Easy
 - 9*9マス、10地雷
 - Windows版の初級と同様
- Normal
 - 16*16マス、40地雷
 - Windows版の中級と同様
- Hard
 - 30*16マス、99地雷
 - Windows版の上級と同様
- Lunatic
 - 40*25マス、225地雷
 - オリジナルの鬼畜難易度
- Custom
 - 縦横サイズはそれぞれ2~99、地雷の数は1~(マス数-1)まで自由に設定可能
 - **沼**

<details><summary>細かい仕様など</summary><div>

- マスを開けた時、隣接するマス全てに地雷がない時は隣接するマスも自動で開ける
- マスを開けた時、隣接するマスに地雷が存在する時は地雷の数を表示する
- 旗が立っているマスの上でクリックしても何も発生しない
- クリアした後、ボタンからツイート可能
- 最初に開けたマスには地雷が無い
- 地雷では無いマスが9マス以上のとき、最初に開けた周囲のマスには地雷が無い
- ゲーム画面が1画面に収まらない場合はスクロールバーが出現する
- カスタムゲームのランダムでは地雷の個数は250個以下、地雷の割合は25%以下に抑えられる

</div></details>

# 今回目指したこと
ただ作るのもつまらないのでクオリティが高いゲームを作ることにした。
そこで以下の3つの機能を実装して本格的なゲームを実現した。

1. 複数の難易度を選択可能
2. 行列や地雷の数をユーザーが自由に設定可能
3. クリア後にツイッターでシェア可能

コーディングはコードの短さよりも見やすさ、実装のしやすさを優先している。

# ソースコード
最初にある通り、html,CSS,JavaScriptの3ファイルのみを使った。
`pngなどの外部ファイルは一切使わずに`マインスイーパーの完成させた。
[ソースコードの内容はCodePenを参照。](https://codepen.io/td12734/pen/BaBEYmM)

## html
ボタンや数値入力などのゲームのUIを設定している。
スタイルはCSS、ボタン関数などはjsから呼び出し、このファイルはUIの追加時以外は触らなくて済むようにしている。

## CSS
ボタンなどのUIのデザインやテキストサイズなどを設定している。
特筆するような事は無いが、16進数のカラーコードによる色指定は個人的に好みでは無いので名前で色指定している。

## JavaScript
マインスイーパーの実装を行なっている。
今回のゲームは結構多機能だが300行+α程度で済んだ。
以下、実装においてハマった所や注意が必要な所のみを掻い摘んで解説する。
とりあえず全文読みたい人は`initGame()`関数から読み進めると良いかも。

### 周りに地雷が無いマスを開けた時、周りのマスも開ける

当初はマスの`onclick()`内で`clickNoMine()`と`openAroundCell()`の処理も書き、`openAroundCell()`では各マスの`onclick()`を呼んでいた。

```js:変更前のopenAroundCell
if (cell.column > 0 && cell.row > 0) gameTable.rows[cell.column - 1].cells[cell.row - 1].onclick();
if (cell.column > 0) gameTable.rows[cell.column - 1].cells[cell.row].onclick();
if (cell.column > 0 && cell.row < rowLen - 1) gameTable.rows[cell.column - 1].cells[cell.row + 1].onclick();
...

```

一見すると問題無さそうだが、実行すると`too much recursion`エラーが出た。
調べてみたところ、再帰処理が多すぎるとエラーが出るようだ。
なので現在のようにonclick自体は呼ばないようにしてエラーを回避した。

### wavファイルを再生したい

地雷を踏んだ時の爆発音と地雷以外の全てのマスを開けた時のファンファーレ音を再生したい。
だが、CodePenにそれらはアップロードできず、直リンクで外部サイトから取ってくるのも迷惑が掛かる。
そこで今回は音声データを`Data URI`に変換し、変換した文字列をソースコードに埋め込んで音を再生する手法を取った。

***ん?html、css、jsの3ファイルだけ使うと書いてあるのに音声ファイルを使うのはタイトル詐欺だって???***
***今回使ったのは`Data URI`であって`ファイル`ではないからセーフなんDA☆***

**`Data URIを使った音声再生のやり方`**

1. 再生したい音声ファイルを入手する
 - 今回は[効果音ラボ](https://soundeffect-lab.info/)様のファイルをお借りしました。この場を借りて感謝します。
2. ファイルをData URIに変換する
 - [Data URI Scheme 変換](https://hamayapp.appspot.com/static/data_uri_conv.html)を使うのが楽です。
3. 変換した文字列をソースコードに埋め込む
4. `new Audio({Data URI文字列}).play();`で音を再生する

### タブを変えるとタイマー停止が動作しない
当初は`setInterval`と`clearInterval`を使ってタイマーを実装していたのだが、タブの切り替えなどを行うと`clearInterval`が動作しない。
なのでやや不恰好だが`isGameActive`がtrueなら`setTimeout`し続ける手法に変えた。