[Javascript] マルバツゲームを写経してみた

今回、JSの学習のため、マルバツゲームをを写経してみたので
全コードをアップしておきます（自分用の備考録）。

# 参考（写経）元
[マルバツゲームを作ってみよう](https://www.hypertextcandy.com/tic-tac-toe-1)
※アロー関数で記述したり、コードのまとめ方を変更したりしていますが、ほぼ同じ内容です

# Javascript

```Javascript:app.js
// 各種定義
let flag = false;
let counter = 9;
let winningLine = null;
const squares = document.querySelectorAll('.square');
const squaresArray = [].slice.call(squares); // IE11対策
const messages = document.querySelectorAll('.message-list li');
const messagesArray = [].slice.call(messages); // IE11対策
const resetBtn = document.querySelector('#reset-btn');


// メッセージの切り替え関数
const setMessage = (id) => {
    messagesArray.forEach((message) => {
        if (message.id === id) {
            message.classList.remove('js-hidden');
        } else {
            message.classList.add('js-hidden');
        }
    });
}


// 勝利判定のパターン関数
const filterById = (targetArray, idArray) => {
    return targetArray.filter((e) => {
        return (e.id === idArray[0] || e.id === idArray[1] || e.id === idArray[2]);
    });
}
// 勝利判定パターン
const line1 = filterById(squaresArray, ['1-1', '1-2', '1-3']);
const line2 = filterById(squaresArray, ['2-1', '2-2', '2-3']);
const line3 = filterById(squaresArray, ['3-1', '3-2', '3-3']);
const line4 = filterById(squaresArray, ['1-1', '2-1', '3-1']);
const line5 = filterById(squaresArray, ['1-2', '2-2', '3-2']);
const line6 = filterById(squaresArray, ['1-3', '2-3', '3-3']);
const line7 = filterById(squaresArray, ['1-1', '2-2', '3-3']);
const line8 = filterById(squaresArray, ['1-3', '2-2', '3-1']);
const lineArray = [line1, line2, line3, line4, line5, line6, line7, line8];
// 勝利判定関数
const isWinner = (symbol) => {
    // some: 1つでも条件を満たしていればTrueを返す
    const result = lineArray.some((line) => {
        // every: 全て条件を満たしていればTrueを返す
        const subResult = line.every((square) => {
            if (symbol === 'maru') {
                return square.classList.contains('js-maru-checked');
            } else 
            if (symbol === 'batsu') {
                return square.classList.contains('js-batsu-checked');
            }
        });

        if (subResult) { winningLine = line }

        return subResult;
    });
    return result;
}


// ゲーム終了時の関数
const gameOver = () => {
    // 全てのマスをクリック不可にする
    squaresArray.forEach((square) => {
        square.classList.add('js-unclickable');
    });

    // 勝った時のマス目をハイライトする
    if (winningLine) {
        winningLine.forEach((square) => {
            square.classList.add('js-highLight');
        });
    }

    //　リセットボタン表示
    resetBtn.classList.remove('js-hidden');
}


// ゲームの初期化の関数
const initGame = () => {
    flag = false;
    counter = 9;
    winningLine = null;
    squaresArray.forEach((square) => {
        square.classList.remove('js-maru-checked');
        square.classList.remove('js-batsu-checked');
        square.classList.remove('js-unclickable');
        square.classList.remove('js-highLight');
    });
    setMessage('batsu-turn');
    resetBtn.classList.add('js-hidden');
}
resetBtn.addEventListener('click', function() {
    initGame();
});


//　マスをクリックした時のイベント発火
squaresArray.forEach((square) => {
    square.addEventListener('click', () => {
        if (flag === true) {
            square.classList.add('js-maru-checked');
            square.classList.add('js-unclickable');

            if (isWinner('maru')) {
                setMessage('maru-win');
                gameOver();
                return;
            }

            setMessage('batsu-turn');
            flag = false;
            
        } else {
            square.classList.add('js-batsu-checked');
            square.classList.add('js-unclickable');

            if (isWinner('batsu')) {
                setMessage('batsu-win');
                gameOver();
                return;
            }

            setMessage('maru-turn');
            flag = true;
        }

        counter--;
        // 引き分け判定
        if (counter === 0) {
            setMessage('draw');
            gameOver();
        }
    });
});
```

# HTML

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>[Sample]○×ゲーム</title>
    <link href="app.css" rel="stylesheet" />
  </head>
  <body>
    <div class="wrapper">
      <div class="game-container">
        <a href="https://www.hypertextcandy.com/tic-tac-toe-1" target="_blank">参考: URL</a>
        <div class="message-container">
          <ul class="message-list">
            <li id="maru-turn" class="js-hidden">
              <span class="maru">○</span>の番
            </li>
            <li id="batsu-turn">
              <span class="batsu">×</span>の番
            </li>
            <li id="maru-win" class="js-hidden">
              <span class="maru">○</span>の勝ち！
            </li>
            <li id="batsu-win" class="js-hidden">
              <span class="batsu">×</span>の勝ち！
            </li>
            <li id="draw" class="js-hidden">
              引き分け
            </li>
          </ul>
        </div>

        <div class="squares-container">
          <div class="squares-box">
            <!-- 1行め -->
            <div id="1-1" class="square"></div>
            <div id="1-2" class="square"></div>
            <div id="1-3" class="square"></div>
            <!-- 2行め -->
            <div id="2-1" class="square"></div>
            <div id="2-2" class="square"></div>
            <div id="2-3" class="square"></div>
            <!-- 3行め -->
            <div id="3-1" class="square"></div>
            <div id="3-2" class="square"></div>
            <div id="3-3" class="square"></div>
          </div>
        </div>

        <div class="btn-container">
          <div class="js-hidden" id="reset-btn">
            <span class="btn btn-reset">もう一回遊ぶ</span>
          </div>
        </div>

      </div>
    </div>
    <script src="app.js"></script>
  </body>
</html>
```
# CSS

```css:app.css
html {
  box-sizing: border-box;
  font-size: 16px;
}
*, *::before, *::after {
  box-sizing: border-box;
}
li {
  list-style: none;
}
body {
  margin: 0;
  line-height: normal;
}
h1 {
  margin: 20px 0;
  font-size: 2rem;
  font-weight: bold;
}
.wrapper {
  max-width: 1024px;
  margin: 0 auto;
  padding: 0 10px;
  text-align: center;
}
.game-container {
  padding: 60px 0;
}
.message-container {
  margin-bottom: 20px;
  font-size: 1.25rem;
  font-weight: bold;
}
.batsu {
  color: #00b0f0;
}
.maru {
  color: #ff0000;
}
.js-hidden {
  display: none;
}
.squares-container {
  margin: 0 auto;
  width: 202px;
}
.squares-box {
  width: 202px;
  height: 202px;
  display: flex;
  flex-wrap: wrap;
  border: solid 2px #333;
}
.square {
  position: relative;
  width: calc(198px / 3);
  height: calc(198px / 3);
  border: solid 2px #333;
}
.js-maru-checked::before {
  content: '';
  width: 50px;
  height: 50px;
  border: solid 8px #ff0000;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.js-batsu-checked::before {
  content: '';
  width: 50px;
  height: 8px;
  margin: auto;
  background-color: #00b0f0;
  border-radius: 4px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(45deg);
}
.js-batsu-checked::after {
  content: '';
  width: 8px;
  height: 50px;
  margin: auto;
  background-color: #00b0f0;
  border-radius: 4px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(45deg);
}
.btn-container {
  padding-top: 40px;
}
.btn {
  display: inline-block;
  width: 150px;
  padding: 10px 20px;
  border-radius: 5px;
  cursor: pointer;
}
.btn-reset {
  color: #fff;
  background-color: #ffc000;
  font-weight: bold;
}
.btn-reset:hover {
  background-color: #ffd347;
  transition-duration: 0.4s;
}

/* JS連動 */
.js-unclickable {
  pointer-events: none;
}
.js-highLight {
  background-color: #fff2cc;
}
```