Building TicTacToe Using Vanilla JavaScript

Author profile picture

@daniel-wesegoDaniel Wesego

Loved by God

I am sure you have played tictactoe as a child. In this article, we will build tictactoe using plain JavaScript step by step. Let's go!
I recommend you to exercise what you see so that you will get the most out of this article. The source code can be found at my Github repository. Open your favorite IDE, mine is VSCode, and open a new project folder. We will create the files one by one. This will be our directory structure:
|- index.html
|- /js
  |- main.js
|- /stylesheet
  |- main.css
Let's start with the index.html. In the body of the html, we will add the title, form for filling player names, a submit button to play, container for clickable boxes, and restart button. Remember to also put links to your stylesheet at the top and your script at the bottom. The container to play the game will be invisible at first. After the players fill the form and click submit, we will make the container visible.
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="styles/styles.css">
  <title>TICTACTOE</title>
</head>

<body>
  <h2 class="text-center mt-4">Tic<span class="mid-title">Tac</span>Toe</h2>
  <h5 class="text-center"> Enter players' names and click play to start the game!</h5>
  <div class="container">
    <form class="player-info mb-4">
      <div class="form-group">
        <label for="player1">Player 1 (X)</label>
        <input type="text" class="form-control" id="player1" placeholder="Name of player 1">
      </div>

      <div class="form-group">
        <label for="player2">Player 2 (O)</label>
        <input type="text" class="form-control" id="player2" placeholder="Name of player 2">
      </div>

      <button type="submit" class="btn btn-primary">Play</button>
    </form>
    <div class="col-4 offset-4 place hidden">
    <div class="alert alert-primary game-status text-center">
      <h4>Board:</h4>
    </div>

    <div class="container mx-auto">
      <div id="board">
        <div class="cell text-center display-4"></div>
        <div class="cell text-center display-4"></div>
        <div class="cell text-center display-4"></div>
        <div class="cell text-center display-4"></div>
        <div class="cell text-center display-4"></div>
        <div class="cell text-center display-4"></div>
        <div class="cell text-center display-4"></div>
        <div class="cell text-center display-4"></div>
        <div class="cell text-center display-4"></div>
      </div>
    </div>
    <button id="reset" class="mt-4 text-center btn btn-primary">Restart</button>
  </div>
  </div>
  <script src="js/main.js"></script>
</body>

</html>
In the main.css file, we will add our styles. I've used bootswatch. It is a customized version of bootstrap. You can your own style if you want. Go ahead to their website, choose a theme and download the minified css and put that at the top the css file. I have added my own style after shown below. You have to add the bootswatch minified css at the top of this file.
#board {
  border: 2px solid black;
  display: flex;
  flex-wrap: wrap;
  height: 270px;
  width: 245px;
}

.cell {
  border: 2px solid black;
  height: 80px;
  width: 80px;
}

.hidden {
  display: none;
}

.player-info, .game-status {
  width: 80%;
}

.mid-title {
  color: red;
}
Ok, now let's go to the main part, main.js file. All methods below this paragraph are JavaScript methods that will be in the main.js file.Let's start with the playerFactory. The playerFactory will be the factory for players. Factories are like classes. You can also use class instead of factory but factories are preferred. You can read more about classes and factories here. The playerFactory will accept the player name and the mark ('X' or 'O'). It will have a method to play when it's the player's turn. The playTurn method will accept a board which will be the game board and the cell. It will find the index of the cell and return it if it's empty or it will return null if it's occupied.
const playerFactory = (name, mark) => {
  const playTurn = (board, cell) => {
    const idx = board.cells.findIndex(position => position === cell);
    if (board.boardArray[idx] === '') {
      board.render();
      return idx;
    }
    return null;
  };

  return { name, mark, playTurn };
};
Next, we will make the board module. A module is similar to a factory in JavaScript but they are like static classes. They can be called without being instantiated. We will have the board array to hold the 9 positions in the game. We will use DOM manipulation to select and the values of html elements. The render method will make the value of the html cells equal to the board array. The reset method will reset the board making all values to an empty string. The checkWin method will check the winning positions in a tictactoe game. For example, if you put continuous 'X' in a row, you will win. If we take the first row, that position will be the first, second and third. But since arrays start from zero, we will have to deduct one from each and it will be zero, one, two or [0, 1, 2] which you see as the first entry in the winArrays variable. The winArray variable is filled with all those positions and then it will check if they are occupied with the same value 'X' or 'O'. Of course, they should be all 'X' or 'O'.
const boardModule = (() => {
  let boardArray = ['', '', '', '', '', '', '', '', ''];
  const gameBoard = document.querySelector('#board');
  const cells = Array.from(document.querySelectorAll('.cell'));
  let winner = null;

  const render = () => {
    boardArray.forEach((mark, idx) => {
      cells[idx].textContent = boardArray[idx];
    });
  };

  const reset = () => {
    boardArray = ['', '', '', '', '', '', '', '', ''];
  };

  const checkWin = () => {
    const winArrays = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [0, 4, 8],
      [2, 4, 6],
    ];

    winArrays.forEach((combo) => {
      if (boardArray[combo[0]]
        && boardArray[combo[0]] === boardArray[combo[1]]
        && boardArray[combo[0]] === boardArray[combo[2]]) {
        winner = 'current';
      }
    });
    return winner || (boardArray.includes('') ? null : 'Tie');
  };

  return {
    render, gameBoard, cells, boardArray, checkWin, reset,
  };
})();
Let's continue to the gamePlay module, which will be the last module. In this module, we will interact with the DOM to get the player names and form. The switchTurn method will switch turns between the players as the name describes. The gameRound method will control of the game round. It will check if there is a winner or if there is a tie. If none of the two happen, it will call the switchTurn method defined above. The gameInit method, which is the only method returned at the end, will initialize the game.
const gamePlay = (() => {
  const playerOneName = document.querySelector('#player1');
  const playerTwoName = document.querySelector('#player2');
  const form = document.querySelector('.player-info');
  const resetBtn = document.querySelector('#reset');
  let currentPlayer;
  let playerOne;
  let playerTwo;

  const switchTurn = () => {
    currentPlayer = currentPlayer === playerOne ? playerTwo : playerOne;
  };

  const gameRound = () => {
    const board = boardModule;
    const gameStatus = document.querySelector('.game-status');
    if (currentPlayer.name !== '') {
      gameStatus.textContent = `${currentPlayer.name}'s Turn`;
    } else {
      gameStatus.textContent = 'Board: ';
    }

    board.gameBoard.addEventListener('click', (event) => {
      event.preventDefault();
      const play = currentPlayer.playTurn(board, event.target);
      if (play !== null) {
        board.boardArray[play] = `${currentPlayer.mark}`;
        board.render();
        const winStatus = board.checkWin();
        if (winStatus === 'Tie') {
          gameStatus.textContent = 'Tie!';
        } else if (winStatus === null) {
          switchTurn();
          gameStatus.textContent = `${currentPlayer.name}'s Turn`;
        } else {
          gameStatus.textContent = `Winner is ${currentPlayer.name}`;
          board.reset();
          board.render();
        }
      }
    });
  };

  const gameInit = () => {
    if (playerOneName.value !== '' && playerTwoName.value !== '') {
      playerOne = playerFactory(playerOneName.value, 'X');
      playerTwo = playerFactory(playerTwoName.value, 'O');
      currentPlayer = playerOne;
      gameRound();
    }
  };

  form.addEventListener('submit', (event) => {
    event.preventDefault();
    if (playerOneName.value !== '' && playerTwoName.value !== '') {
      gameInit();
      form.classList.add('hidden');
      document.querySelector('.place').classList.remove('hidden');
    } else {
      window.location.reload();
    }
  });

  resetBtn.addEventListener('click', () => {
    document.querySelector('.game-status').textContent = 'Board: ';
    document.querySelector('#player1').value = '';
    document.querySelector('#player2').value = '';
    window.location.reload();
  });
  return {
    gameInit,
  };
})();
Finally, we will call the gameInit method. It will initialize the game. We can start playing after this.
gamePlay.gameInit();
So now you can enjoy the game. Please follow me on Github if you like the article.

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!