JavaScript/Exercises/TicTacToe

From Wikibooks, open books for an open world
Jump to navigation Jump to search




TicTacToe

TicTacToe is a game for two players. They choose fields out of a 3 x 3 chessboard.

First, we need an HTML file plus CSS to realize the user interface. It shall contain:

  • A caption
  • A container with nine buttons arranged in a 3 x 3 chessboard
  • Two button Start X and a button Start O to decide which user starts
  • A Reset button
  • A textfield to give the users feedbacks about the status of the game

The HTML might look like this:

Click to see solution
<!DOCTYPE html>
<html>
<head>
  <title>TicTacToe</title>
  <script>
  // ...
  </script>

  <style>
    .container {
      display: grid;
      grid-template-columns: 32% 32% 32%;
      grid-template-rows:    6em 6em 6em;
      gap: 1%;
      background-color: aliceblue;
      margin: 2em;
      padding: 2em;
    }
    .cell {
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .button {
      height:2.4em;
      width: 2.4em;
      font-size: 2em;
      background-color: aqua;
    }
    .containerCmd {
      display: flex;
      justify-content: flex-end;
    }
    .buttonCmd {
      padding: 0.6em 2em 0.6em 2em;
      font-size: 1em;
      margin-right: 2em;
    }
    .feedback {
      padding: 0.8em 1em 0.8em 1em;
      margin: 1em;
      font-size: 1.4em;
      background-color: green;
    }
  </style>

</head>

<body>

  <h1 style="text-align: center;">TicTacToe</h1>

  <!--  The container with the nine clickable buttons  -->
  <div class="container">
    <div class="cell"><button id="b1" class="button" disabled /></div>
    <div class="cell"><button id="b2" class="button" disabled /></div>
    <div class="cell"><button id="b3" class="button" disabled /></div>
    <div class="cell"><button id="b4" class="button" disabled /></div>
    <div class="cell"><button id="b5" class="button" disabled /></div>
    <div class="cell"><button id="b6" class="button" disabled /></div>
    <div class="cell"><button id="b7" class="button" disabled /></div>
    <div class="cell"><button id="b8" class="button" disabled /></div>
    <div class="cell"><button id="b9" class="button" disabled /></div>
  </div>

  <!-- buttons for start and reset the game -->
  <div class="containerCmd">
    <button class="buttonCmd" id="startX">Start: X</button>
    <button class="buttonCmd" id="startO">Start: O</button>
    <p style="padding-right:3em"></p> <!-- a small spacer -->
    <button class="buttonCmd">Reset</button>
  </div>

  <!-- feedback from the script to the players -->
  <p id="feedback" class="feedback">Click to one of the 'Start' buttons</p>

</body>
</html>


Next, you develop the app's logic by adding events to buttons and functions within the script element.

  • When the game starts or will be reset, the nine X/O buttons and the feedback area must be cleared.
  • It is helpful when the nine X/O buttons call the same event handler. event.target.id delivers the ID of the button and via document.getElementById(event.target.id) this button is reachable.
  • Whenever one of the nine X/O buttons is clicked, this button must be protected against further clicks elem.disabled = true.
  • We need a function that decides whether a line, a row, or a diagonal (8 possibilities) contains 3 'X's respectively 3 'O's.
  • Consider the case that no one wins.

All in all, the app might look like this:

Click to see solution
<!DOCTYPE html>
<html>
<head>
  <title>TicTacToe</title>
  <script>
  "use strict";

  // global variable for X/O
  let user = "";

  // --------  start: decide which user has the first click  ----------------
  function startXO(userXO) {
    if (userXO === "X") {
      user = "X";
    } else {
      user = "O";
    }
    // disable / enable certain buttons
    document.getElementById("startX").disabled = true;
    document.getElementById("startO").disabled = true;
    ["b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9"]
      .forEach((button) => {
        document.getElementById(button).disabled = false;
      });
    document.getElementById("feedback").innerHTML = "";
  }

  // --------  regular action  ----------------------------------------------
  function buttonClicked(event) {
    const elem = document.getElementById(event.target.id);
    elem.innerHTML = user;
    elem.disabled = true;

    // check for end of game
    switch (isFinished()) {
    case "tie":
      document.getElementById("feedback").innerHTML = "No winner. Tie.";
      ["b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9"]
        .forEach((button) => {
          document.getElementById(button).disabled = true;
        });
      break;

    case true:
      document.getElementById("feedback").innerHTML = "The winner is: " + user;
      ["b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9"]
        .forEach((button) => {
          document.getElementById(button).disabled = true;
        });
      break;

    default:
      // toggle user and go on
      if (user === "X") {
        user = "O";
      } else {
        user = "X";
      }
    }
  }
  
  // --------  check for end of game ----------------------
  function isFinished() {
    const xo_b1 =  document.getElementById("b1").innerHTML;
    const xo_b2 =  document.getElementById("b2").innerHTML;
    const xo_b3 =  document.getElementById("b3").innerHTML;
    const xo_b4 =  document.getElementById("b4").innerHTML;
    const xo_b5 =  document.getElementById("b5").innerHTML;
    const xo_b6 =  document.getElementById("b6").innerHTML;
    const xo_b7 =  document.getElementById("b7").innerHTML;
    const xo_b8 =  document.getElementById("b8").innerHTML;
    const xo_b9 =  document.getElementById("b9").innerHTML;

    // check for 'tie' in a loop over all buttons
    let tmp = 0;
    [xo_b1, xo_b2, xo_b3, xo_b4, xo_b5, xo_b6, xo_b7, xo_b8, xo_b9]
      .forEach((elem) => {if (elem !== "") tmp++});
    if (tmp === 9) {
      return "tie";
    }

    // check for winner
    if (             // horizontal
        (xo_b1 === user && xo_b2 === user && xo_b3 === user) ||
        (xo_b4 === user && xo_b5 === user && xo_b6 === user) ||
        (xo_b7 === user && xo_b8 === user && xo_b9 === user) ||
                     // vertical
        (xo_b1 === user && xo_b4 === user && xo_b7 === user) ||
        (xo_b2 === user && xo_b5 === user && xo_b8 === user) ||
        (xo_b3 === user && xo_b6 === user && xo_b9 === user) ||
                     // diagonal
        (xo_b1 === user && xo_b5 === user && xo_b9 === user) ||
        (xo_b3 === user && xo_b5 === user && xo_b7 === user)
       )
    {
      return true;
    } else {
      return false;
    }
  }

  // --------  reset game  -------------------------------------
  function reset() {

    // disable / enable certain buttons
    ["b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9"]
      .forEach((button) => {
        document.getElementById(button).innerHTML = "";
        document.getElementById(button).disabled = true;
      });
    document.getElementById("feedback").innerHTML = "Click to 'Start'";
    document.getElementById("startX").disabled = false;
    document.getElementById("startO").disabled = false;
  }
  </script>

  <style>
    .container {
      display: grid;
      grid-template-columns: 32% 32% 32%;
      grid-template-rows:    6em 6em 6em;
      gap: 1%;
      background-color: aliceblue;
      margin: 2em;
      padding: 2em;
    }
    .cell {
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .button {
      height:2.4em;
      width: 2.4em;
      font-size: 2em;
      background-color: aqua;
    }
    .containerCmd {
      display: flex;
      justify-content: flex-end;
    }
    .buttonCmd {
      padding: 0.6em 2em 0.6em 2em;
      font-size: 1em;
      margin-right: 2em;
    }
    .feedback {
      padding: 0.8em 1em 0.8em 1em;
      margin: 1em;
      font-size: 1.4em;
      background-color: green;
    }
  </style>

</head>

<body>

  <h1 style="text-align: center;">TicTacToe</h1>

  <!--  The container with the nine clickable buttons  -->
  <div class="container">
    <div class="cell"><button id="b1" class="button" onclick="buttonClicked(event)" disabled /></div>
    <div class="cell"><button id="b2" class="button" onclick="buttonClicked(event)" disabled /></div>
    <div class="cell"><button id="b3" class="button" onclick="buttonClicked(event)" disabled /></div>
    <div class="cell"><button id="b4" class="button" onclick="buttonClicked(event)" disabled /></div>
    <div class="cell"><button id="b5" class="button" onclick="buttonClicked(event)" disabled /></div>
    <div class="cell"><button id="b6" class="button" onclick="buttonClicked(event)" disabled /></div>
    <div class="cell"><button id="b7" class="button" onclick="buttonClicked(event)" disabled /></div>
    <div class="cell"><button id="b8" class="button" onclick="buttonClicked(event)" disabled /></div>
    <div class="cell"><button id="b9" class="button" onclick="buttonClicked(event)" disabled /></div>
  </div>

  <!-- buttons for start and reset the game -->
  <div class="containerCmd">
    <button class="buttonCmd" id="startX" onClick="startXO('X')">Start: X</button>
    <button class="buttonCmd" id="startO" onClick="startXO('O')">Start: O</button>
    <p style="padding-right:3em"></p> <!-- a small spacer -->
    <button class="buttonCmd" onClick="reset()">Reset</button>
  </div>

  <!-- feedback from the script to the players -->
  <p id="feedback" class="feedback">Click to one of the 'Start' buttons</p>

</body>
</html>