DEV Community

Diseyi
Diseyi

Posted on

Implementing Tic Tac Toe in Vue

Tictac-toe
Tic Tac Toe game used to be one of my favourite paper games in primary school. It was known as 'X' and 'O', I don’t quite remember how often I won, but I definitely enjoyed playing it. Recently, my friend @eteimz challenged me to build this game with Vue. He built the react version. You can check it out here reactoe.

For this tutorial, I will be using the Vue Composition API Style. In the github repository linked below, you can find the code for both Composition and Options API.

We start by initializing the states of our game:

  • winner to store the winner's value,
  • isTie: this stores a boolean value, false in the beginning and if no winner, is set to true,
  • gameover: this holds a boolean value, false at the beginning and is set to true when a winner emerges or isTie is true,
  • currentplayer stores the value of the current player
  • a 3*3 array, also known as a matrix.
<script setup lang="ts">
import { ref, reactive } from 'vue';

const winner = ref<string | null>(null);
const isTie = ref(false);
const gameover = ref(false);
const currentPlayer = ref('X');

let board = reactive([
  ['', '', ''],
  ['', '', ''],
  ['', '', '']
]);
</script>
Enter fullscreen mode Exit fullscreen mode

Next step is our template

  • Inside the board div, the first div with a class of rows represents the rows which include an inner div that represents the cells in each row. A click event is attached to the cell.
<template>
  <div>
    <h1 class="">TicTac Game</h1>
    <p class=""> Current Player: <span class=""> {{ currentPlayer }} </span> </p>
      <div class="row" v-for="(row, rowIndex) of board" :key="rowIndex">
        <div class="cell" v-for="(cell, cellIndex) of row" :key="cellIndex"
          :class="{ 'cell-x': cell === 'X', 'cell-o': cell === 'O' }" :disabled="cell !== null"
          @click="playMove(rowIndex, cellIndex)">
          {{ cell }}
        </div>
      </div>

    <div class="">
      <p v-if="winner">{{ winner }} wins!</p>
      <p v-else-if="isTie">It's a tie!</p>
      <button @click="reset">Reset Game</button>
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

The next step is creating the functions for our game, we start with the checkTie. This function checks if the current game is in a tied state.

The checkTie function uses a nested loop to iterate over each cell on the 3x3 game board. It starts by initializing two variables i and j to zero and increments them until they reach three. The i and j variables represent the row and column index of each cell on the board.

With each iteration of the loop, the function checks if the cell at the current i and j position is empty, if empty, the function returns false.

If the loop completes without finding any empty cell, the function returns true resulting in a tied state between the players.

const checkTie = () => {
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
      if (!board[i][j]) {
        return false;
      }
    }
  }
  return true;
}
Enter fullscreen mode Exit fullscreen mode

Our next step is the checkWin function. This function checks all the possible ways a player can win, and returns true if the current player has won and false otherwise.

Inside the function, the for loop checks each row and column of the board to see if all the cells in the row or column match the current player's value. This is done by using the every() method on each row and column to check if every cell in that row or column is equal to the current value.

If a row or column is found where every cell matches the current player's value, the function returns true to indicate that the current player has won the game.

If no winning row or column is found, the function checks the two diagonals of the board to see if they are all equal to the current player's value. If a diagonal is found where all cells are equal to the current value, the function returns true to indicate that the current player has won the game.

If no winning row, column, or diagonal is found, the function returns false to indicate that the game is not yet won by the current player.

const checkWin = () => {
  const a = currentPlayer.value;

  for (let i = 0; i < 3; i++) {
    if (board[i].every(cell => cell === a)) return true;
    if (board.every(row => row[i] === a)) return true;
  }

  // Check diagonals
  if (board[0][0] === a && board[1][1] === a && board[2][2] === a) return true;
  if (board[0][2] === a && board[1][1] === a && board[2][0] === a) return true;

  return false;
};
Enter fullscreen mode Exit fullscreen mode

Our next function is the playMove function. This function updates the board when a player makes a move, it takes in two arguments, row and col, which represent the coordinates of the cell that the player clicks.

The function first checks if the cell is empty (!board[row][col]) and if there is no winner yet (!winner.value). If both of these conditions are true, then it proceeds to update the game board with the current player's value.

The checkWin function is then called to check if the current player has won the game. If the current player has won, the winner variable is updated with the current player's value. If not, the checkTie function is called to check if the game is tied. If the game is tied, the isTie variable is set to true.

If the game is not won or tied, the currentPlayer variable is updated to the other player's title. This is done using a ternary operator that checks if the current player is 'X'. If so, it switches to 'O', and vice versa.

const playMove = (row: number, col: number) => {
  if (!board[row][col] && !winner.value) {
    board[row][col] = currentPlayer.value;
    if (checkWin()) {
      winner.value = currentPlayer.value;
    } else if (checkTie()) {
      isTie.value = true;
    } else {
      currentPlayer.value = currentPlayer.value === 'X' ? 'O' : 'X';
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, we have the reset function, to reset the state of our game to its default state.

const reset = () => {
  board = [
    ['', '', ''],
    ['', '', ''],
    ['', '', '']
  ];
  currentPlayer.value = 'X';
  gameover.value = false;
  winner.value = null;
}
Enter fullscreen mode Exit fullscreen mode

Here's the css used for this game

body {
  margin: 0;
  display: flex;
  min-width: 320px;
  min-height: 100vh;
}


#app {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

.tic-tac-toe {
  text-align: center;
}

.board {
  display: inline-block;
  margin-top: 20px;
}

.row {
  clear: both;
}

.cell {
  width: 50px;
  height: 50px;
  float: left;
  margin-right: -1px;
  margin-bottom: -1px;
  line-height: 50px;
  text-align: center;
  border: 1px solid #bbb;
  cursor: pointer;
  font-size: 40px;
}

.cell-x {
  color: #f00;
}

.cell-o {
  color: #00f;
}

button {
  margin-top: 20px;
  font-size: 16px;
  padding: 10px;
  border-radius: 5px;
  background-color: #ccc;
  border: none;
  cursor: pointer;
}

Enter fullscreen mode Exit fullscreen mode

Link to the the github repo tictac game repo

Top comments (1)

Collapse
 
eteimz profile image
Youdiowei Eteimorde

This was a nice read. Thanks for the shout out 😊