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

Reactのチュートリアルの三目並べを素のJavaScriptに

前回のjQueryで作ったReactチュートリアルを素のJavaScriptにしてみます。すべてのブラウザで動くかは試してません。Edge(chromium)で確認しました。

マス目に数値を表示する

index.html
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="css/index.css" />
    <script src="js/index.js"></script>
  </head>
  <body>
    <div id="root">
      <div class="game">
        <div class="gmae-board">
          <div>
            <div class="board-row">
              <button class="square"></button><button class="square"></button
              ><button class="square"></button>
            </div>
            <div class="board-row">
              <button class="square"></button><button class="square"></button
              ><button class="square"></button>
            </div>
            <div class="board-row">
              <button class="square"></button><button class="square"></button
              ><button class="square"></button>
            </div>
          </div>
        </div>
        <div class="game-info">
          <div>次の手番: X</div>
          <div>
            <li><button>Go to game start</button></li>
            <!-- <li><button>Go to move #1</button></li> -->
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
index.css
body {
  font: 14px 'Century Gothic', Futura, sans-serif;
  margin: 20px;
}

ol,
ul {
  padding-left: 30px;
}

.board-row:after {
  clear: both;
  content: '';
  display: table;
}

.status {
  margin-bottom: 10px;
}

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

.game {
  display: flex;
  flex-direction: row;
}

.game-info {
  margin-left: 20px;
}
index.js
(function() {
  // DOM読み込み後
  window.addEventListener("DOMContentLoaded", () => {
    document.querySelectorAll(".square").forEach((element, index) => {
      element.textContent = index;
    });
  });
})();

image.png

querySelectorAllがなかった時代は、getElementsByClassNameで取得してforとかでぐるぐる回していたんでしょうね。

XとOを入力できるようにする

index.js
(function() {
  // DOM読み込み後
  window.addEventListener("DOMContentLoaded", () => {
    let xIsNext = true;
    const status = document.querySelector(".game-info :first-child"); // つなげて書くとNGみたい
    document.querySelectorAll(".square").forEach((element, index) => {
      // クリックイベント
      element.addEventListener("click", squareClick);
    });

    function squareClick(event) {
      event.target.textContent = xIsNext ? "X" : "O";
      status.textContent = "次の手番: " + (xIsNext ? "O" : "X");
      xIsNext = !xIsNext;
    }
  });
})();

image.png

循環参照が起きると思ってクリック処理を分けてみました!
MDNのメモリ管理には、「もはや問題ではありません」と書かれているけど、どうなんだろ・・・。

履歴なしの完成までもっていく

index.js
(function() {
  // DOM読み込み後
  window.addEventListener("DOMContentLoaded", () => {
    const squares = new Array(9).fill(null);
    let xIsNext = true;
    const status = document.querySelector(".game-info :first-child"); // つなげて書くとNGみたい
    document.querySelectorAll(".square").forEach((element, index) => {
      // クリックイベント
      element.addEventListener("click", squareClick.bind(null, index));
    });

    function squareClick(index, event) {
      if (calculateWinner(squares) || squares[index]) {
        return;
      }

      squares[index] = xIsNext ? "X" : "O";
      event.target.textContent = squares[index];

      // 勝利判定
      if (calculateWinner(squares)) {
        status.textContent = "勝者: " + squares[index];
      } else {
        status.textContent = "次の手番: " + (xIsNext ? "O" : "X");
      }

      xIsNext = !xIsNext;
    }

    // 勝敗判定関数(公式チュートリアルから拝借)
    function calculateWinner(squares) {
      const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
      ];
      for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];
        if (
          squares[a] &&
          squares[a] === squares[b] &&
          squares[a] === squares[c]
        ) {
          return squares[a];
        }
      }
      return null;
    }
  });
})();

image.png

もはや循環参照とかわからんとです。
前回は「勝者:〇〇」って実装してなかったので、今回しました。

履歴機能を持たせる

cssとhtmlは最初と一緒です。

index.js
(function() {
  // DOM読み込み後
  window.addEventListener("DOMContentLoaded", () => {
    const history = [
      { squares: new Array(9).fill(null), nextStatus: "次の手番: X" } // 履歴
    ];
    let stepNumber = 0; // 現在表示している履歴のインデックス
    const status = document.querySelector(".game-info :first-child"); // つなげて書くとNGみたい

    // Go to game start ボタン
    document
      .querySelector(".game-info li > button")
      .addEventListener("click", historyButtonClick.bind(null, 0));

    // 全square
    document.querySelectorAll(".square").forEach((element, index) => {
      // クリックイベント
      element.addEventListener("click", squareClick.bind(null, index));
    });

    function squareClick(index, event) {
      // 現在のhistory
      const current = history[stepNumber];
      const squares = current.squares.concat(); // コピー

      if (calculateWinner(squares) || squares[index]) {
        return;
      }

      squares[index] = stepNumber % 2 === 0 ? "X" : "O";
      event.target.textContent = squares[index];

      // 勝利判定
      if (calculateWinner(squares)) {
        status.textContent = "勝者: " + squares[index];
      } else {
        status.textContent = "次の手番: " + (stepNumber % 2 === 0 ? "O" : "X");
      }

      // 現在のstemNumberより後ろの履歴と履歴ボタン削除
      const lis = document.querySelectorAll(".game-info li");
      const removeCount = history.length - (stepNumber + 1);
      for (let i = 1; i <= removeCount; i++) {
        lis[stepNumber + i].parentNode.removeChild(lis[stepNumber + i]);
        history.pop();
      }

      // 新しい要素追加
      history.push({ squares: squares, nextStatus: status.textContent });
      stepNumber++;

      createHistoryButton(stepNumber);
    }

    function createHistoryButton(index) {
      // 履歴ボタン
      const button = document.createElement("button");
      button.textContent = "Go to move #" + index;
      button.addEventListener("click", historyButtonClick.bind(null, index)); // クリックイベント

      // 履歴ボタンの親
      const li = document.createElement("li");
      li.appendChild(button); // 履歴ボタンを追加

      // liの親に追加
      document.querySelector(".game-info").appendChild(li);
    }

    function historyButtonClick(index) {
      stepNumber = index;

      // 手番
      status.textContent = history[stepNumber].nextStatus;

      // マス目を全て上書く
      const domSquares = document.querySelectorAll(".square");
      history[stepNumber].squares.forEach((value, index) => {
        domSquares[index].textContent = value;
      });
    }

    // 勝敗判定関数(公式チュートリアルから拝借)
    function calculateWinner(squares) {
      const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
      ];
      for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];
        if (
          squares[a] &&
          squares[a] === squares[b] &&
          squares[a] === squares[c]
        ) {
          return squares[a];
        }
      }
      return null;
    }
  });
})();

image.png
手番はマス目の配列と一緒に保持するようにしました。
こっちの方が楽でした。

感想

jQueryの時のソースと、バグりまくった経験があったからか、割と早く作れた。むしろこっちの方が内容に無駄がないかも・・・?

でも相変わらず見にくい。

Why not register and get more from Qiita?
  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
No 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
ユーザーは見つかりませんでした