Building a Simple Vue.js Interactive Tic-Tac-Toe Game: A Beginner’s Guide

Written by

in

In the world of web development, creating interactive games is a fantastic way to learn and apply fundamental programming concepts. Today, we’re diving into Vue.js, a progressive JavaScript framework, to build a classic: Tic-Tac-Toe. This project is perfect for beginners because it allows you to grasp core Vue.js principles like component creation, state management, and event handling in a fun, tangible way. By the end of this tutorial, you’ll have a fully functional Tic-Tac-Toe game and a solid understanding of how to build interactive web applications with Vue.js.

Why Build a Tic-Tac-Toe Game?

Tic-Tac-Toe provides an excellent learning opportunity for several reasons:

  • Simplicity: The game’s rules are straightforward, making it easy to focus on the coding aspects.
  • Interactive Nature: It requires user interaction, letting you practice handling clicks, updating the UI, and managing game logic.
  • Component-Based Design: You can break down the game into reusable components, which is a core concept in Vue.js.
  • State Management: You’ll learn how to manage the game’s state (who’s turn it is, the board layout, etc.) and update the UI accordingly.

This project is ideal if you’re new to Vue.js or web development in general. It offers a clear path to understanding the framework and building interactive web applications.

Prerequisites

Before we begin, make sure you have the following:

  • Basic HTML, CSS, and JavaScript knowledge: Familiarity with these languages is essential to understand the code.
  • Node.js and npm (or yarn) installed: These are required to set up and manage your Vue.js project. You can download Node.js from nodejs.org.
  • A code editor: Visual Studio Code, Sublime Text, or any other editor you prefer.

Setting Up Your Vue.js Project

Let’s start by creating a new Vue.js project using the Vue CLI (Command Line Interface). Open your terminal or command prompt and run the following commands:

npm install -g @vue/cli
vue create tic-tac-toe-game

During the project creation process, you’ll be prompted to select a preset. Choose the “Default (Vue 3) ([Vue 3] babel, eslint)” option. This sets up a basic project structure with the necessary tools for development.

After the project is created, navigate into the project directory:

cd tic-tac-toe-game

And then, start the development server:

npm run serve

This will start a development server, and you can access your project in your web browser, typically at http://localhost:8080/.

Project Structure Overview

Before diving into the code, let’s understand the project structure created by the Vue CLI. The most important files and directories are:

  • `src/` directory: This is where you’ll spend most of your time. It contains:
  • `App.vue`: The main component of your application.
  • `components/`: This directory will hold your reusable components (e.g., the `Square` component).
  • `main.js`: The entry point of your application, where you initialize Vue.
  • `public/` directory: This contains static assets like `index.html`.
  • `package.json`: This file lists your project’s dependencies and scripts.

Building the Game Components

Now, let’s create the components for our Tic-Tac-Toe game. We’ll need two main components: `Square` and `Board`.

1. The Square Component

The `Square` component represents a single square on the Tic-Tac-Toe board. Create a new file named `Square.vue` inside the `src/components/` directory. Add the following code:

<template>
 <button class="square" @click="onClick">
  {{ value }}
 </button>
</template>

<script>
 export default {
  props: {
   value: { // The value displayed in the square ('X', 'O', or null)
    type: String,
    default: null,
   },
   index: {
    type: Number,
    required: true,
   }
  },
  methods: {
   onClick() {
    this.$emit('square-click', this.index);
   },
  },
 };
</script>

<style scoped>
 .square {
  width: 100px;
  height: 100px;
  font-size: 5em;
  text-align: center;
  border: 1px solid #ccc;
  background-color: #fff;
  cursor: pointer;
  outline: none;
 }

 .square:focus {
  background-color: #eee;
 }
</style>

Let’s break down the code:

  • `<template>`: Defines the structure of the component. It contains a button element that represents the square. The `{{ value }}` displays the value of the square (‘X’, ‘O’, or null).
  • `@click=”onClick”`: This is an event listener that calls the `onClick` method when the button is clicked.
  • `<script>`: Contains the JavaScript logic for the component.
  • `props: { value: { … } }`: Defines a prop named `value`. Props are used to pass data from parent components to child components. In this case, the `value` prop holds the value to be displayed in the square (‘X’, ‘O’, or null). The index prop holds the index of the square on the board.
  • `methods: { onClick() { … } }`: Defines the `onClick` method, which is called when the square is clicked. It emits a custom event `square-click` with the square’s index, which is handled by the parent component (the board).
  • `<style scoped>`: Contains the CSS styles specific to this component. `scoped` ensures that the styles only apply to this component.

2. The Board Component

The `Board` component represents the Tic-Tac-Toe board and manages the game logic. Create a new file named `Board.vue` inside the `src/components/` directory. Add the following code:

<template>
 <div class="board">
  <div class="board-row" v-for="(row, rowIndex) in board" :key="rowIndex">
   <Square
    v-for="(square, squareIndex) in row"
    :key="squareIndex"
    :value="square"
    :index="rowIndex * 3 + squareIndex"
    @square-click="handleSquareClick"
   />
  </div>
  <p v-if="winner">Winner: {{ winner }}</p>
  <p v-else-if="isBoardFull">It's a draw!</p>
  <p>Next player: {{ currentPlayer }}</p<
  <button v-if="winner || isBoardFull" @click="resetGame">Restart</button>
 </div>
</template>

<script>
 import Square from './Square.vue';

 export default {
  components: {
   Square,
  },
  data() {
   return {
    board: [
     [null, null, null],
     [null, null, null],
     [null, null, null],
    ],
    currentPlayer: 'X',
    winner: null,
   };
  },
  computed: {
   isBoardFull() {
    return this.board.every(row => row.every(square => square !== null));
   },
  },
  methods: {
   handleSquareClick(index) {
    if (this.winner || this.board[Math.floor(index / 3)][index % 3] !== null) {
     return;
    }

    const row = Math.floor(index / 3);
    const col = index % 3;

    this.board[row][col] = this.currentPlayer;
    this.checkForWinner();
    this.currentPlayer = this.currentPlayer === 'X' ? 'O' : 'X';
   },

   checkForWinner() {
    const lines = [
     [0, 1, 2], [3, 4, 5], [6, 7, 8], // Rows
     [0, 3, 6], [1, 4, 7], [2, 5, 8], // Columns
     [0, 4, 8], [2, 4, 6], // Diagonals
    ];

    for (let i = 0; i < lines.length; i++) {
     const [a, b, c] = lines[i];
     const boardValues = this.board.flat();
     if (boardValues[a] && boardValues[a] === boardValues[b] && boardValues[a] === boardValues[c]) {
      this.winner = this.currentPlayer;
      break;
     }
    }
   },

   resetGame() {
    this.board = [
     [null, null, null],
     [null, null, null],
     [null, null, null],
    ];
    this.currentPlayer = 'X';
    this.winner = null;
   },
  },
 };
</script>

<style scoped>
 .board {
  display: flex;
  flex-direction: column;
  align-items: center;
 }

 .board-row {
  display: flex;
 }
</style>

Let’s break down the code:

  • `<template>`: Defines the structure of the component. It uses nested `v-for` loops to render the squares. It also displays the winner or the current player.
  • `<script>`: Contains the JavaScript logic for the component.
  • `components: { Square }`: Imports and registers the `Square` component.
  • `data() { … }`: Defines the reactive data for the component:
  • `board: […]`: A 2D array representing the Tic-Tac-Toe board. Initially, all squares are `null`.
  • `currentPlayer: ‘X’`: Tracks the current player.
  • `winner: null`: Stores the winner (‘X’, ‘O’, or null).
  • `computed: { isBoardFull() { … } }`: Defines a computed property that checks if the board is full.
  • `methods: { … }`: Defines the methods for the component:
  • `handleSquareClick(index)`: Handles a click on a square. It updates the board, checks for a winner, and switches to the next player.
  • `checkForWinner()`: Checks if there is a winner by comparing all possible winning combinations.
  • `resetGame()`: Resets the game to its initial state.
  • `<style scoped>`: Contains the CSS styles specific to this component.

3. Integrating the Board Component in App.vue

Now, let’s integrate the `Board` component into the main `App.vue` component. Open `src/App.vue` and replace its content with the following:

<template>
 <div id="app">
  <h1>Tic-Tac-Toe</h1>
  <Board />
 </div>
</template>

<script>
 import Board from './components/Board.vue';

 export default {
  components: {
   Board,
  },
 };
</script>

<style>
 #app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
 }
</style>

Here, we import the `Board` component and render it within the `App.vue` template. This is the main container for our game.

Testing Your Game

At this point, you should have a basic, functional Tic-Tac-Toe game. Open your web browser and navigate to http://localhost:8080/. You should see the Tic-Tac-Toe board. Click on the squares to play the game. The board should update, and the game should detect a winner or a draw.

Common Mistakes and How to Fix Them

As you develop your Tic-Tac-Toe game, you might encounter some common issues. Here are a few and how to fix them:

  • Squares Not Updating: If the squares don’t update when clicked, check that you’re correctly emitting the `square-click` event from the `Square` component and that the `Board` component is listening for this event and calling the `handleSquareClick` method. Make sure the value of the square is being correctly passed from the board to the square.
  • Winner Not Being Detected: Double-check the logic in the `checkForWinner` method. Ensure you’ve covered all possible winning combinations and that the comparison logic is correct. Debug by logging the values of the board to the console.
  • Game Not Resetting: Verify that the `resetGame` method is correctly resetting the `board`, `currentPlayer`, and `winner` data properties. Also, ensure the reset button is correctly connected to the `resetGame` method.
  • Styling Issues: If the game’s appearance is not as expected, carefully review your CSS styles. Make sure you’re using the correct selectors and properties. Use your browser’s developer tools to inspect the elements and see if the styles are being applied correctly. Remember that `scoped` styles only apply within the component.

Adding More Features (Optional)

Once you have a working game, you can enhance it with additional features:

  • Scoreboard: Add a scoreboard to track the number of wins for each player.
  • AI Opponent: Implement an AI to play against the user.
  • Game Modes: Allow the user to select different game modes (e.g., against the computer or two-player mode).
  • Difficulty Levels: If you add an AI, implement different difficulty levels.
  • Sound Effects: Add sound effects to make the game more engaging.

Summary / Key Takeaways

In this tutorial, we’ve successfully built a fully functional Tic-Tac-Toe game using Vue.js. You’ve learned how to create components, manage state, handle events, and create a basic interactive web application. The Tic-Tac-Toe game serves as an excellent entry point for beginners to learn Vue.js by providing practical experience with core concepts. By breaking down the game into components and managing the state effectively, you’ve gained valuable skills in web development. Remember to practice and experiment with the code to solidify your understanding. As you continue to build projects, you’ll become more comfortable with Vue.js and web development in general. The knowledge gained from this project can be applied to many other interactive web applications. You can now confidently tackle more complex projects and continue your journey in web development. Congratulations on completing your first Vue.js game!

The journey of learning web development is continuous, with each project providing new insights and opportunities for growth. Embrace the challenges, experiment with different features, and never stop learning. The skills gained from building this simple game can be applied in numerous ways, from creating more sophisticated games to developing interactive web applications for various purposes. Keep exploring, keep building, and enjoy the process of bringing your ideas to life through code.