How to Use Socket.IO to Build Your First Multiplayer Tic-Tac-Toe Game

Dipak Ahirav - Aug 22 - - Dev Community

Introduction

Real-time multiplayer games have a special charm that makes them both fun to play and intriguing to build. With Socket.IO, you can easily create such games with real-time communication between players. In this tutorial, I’ll walk you through building a simple multiplayer Tic-Tac-Toe game from scratch. By the end, you’ll have a working game and a solid understanding of how to handle real-time events using Socket.IO.

please subscribe to my YouTube channel to support my channel and get more web development tutorials.

Image description

What You'll Build

We’re going to build a Tic-Tac-Toe game where two players can join, take turns to make their moves, and see the game update in real-time. The backend will be powered by Node.js with Express and Socket.IO, while the frontend will use HTML, CSS, and JavaScript.

Prerequisites

Before diving in, make sure you have the following:

  • Node.js (v14 or later) and npm installed.
  • Basic knowledge of JavaScript, HTML, and CSS.

Step 1: Setting Up Your Environment

  1. Create Your Project Directory and Initialize npm: Start by creating a new directory for your project and initializing it with npm.
   mkdir tic-tac-toe-multiplayer
   cd tic-tac-toe-multiplayer
   npm init -y
Enter fullscreen mode Exit fullscreen mode
  1. Install Dependencies: We’ll need Express for our server and Socket.IO for real-time communication.
   npm install express socket.io
Enter fullscreen mode Exit fullscreen mode

Step 2: Building the Backend with Express and Socket.IO

Now, let's set up our backend.

  1. Set Up the Basic Server: Create a file named index.js in a src/ directory:
   const express = require('express');
   const http = require('http');
   const { Server } = require('socket.io');
   const path = require('path');

   const app = express();
   const server = http.createServer(app);
   const io = new Server(server);

   let players = [];
   let boardState = ['', '', '', '', '', '', '', '', ''];
   let currentPlayer = 'X';

   app.use(express.static(path.join(__dirname, '../public')));

   app.get('/', (req, res) => {
     res.sendFile(__dirname + '/public/index.html');
   });

   const checkWinner = () => {
     const winningCombinations = [
       [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 combination of winningCombinations) {
       const [a, b, c] = combination;
       if (boardState[a] && boardState[a] === boardState[b] && boardState[a] === boardState[c]) {
         return boardState[a];
       }
     }
     return boardState.includes('') ? null : 'Draw';
   };

   io.on('connection', (socket) => {
     console.log('A user connected:', socket.id);

     if (players.length < 2) {
       players.push({ id: socket.id, symbol: players.length === 0 ? 'X' : 'O' });
       socket.emit('assignSymbol', players[players.length - 1].symbol);
     } else {
       socket.emit('spectator');
     }

     io.emit('updatePlayers', players.map(player => player.symbol));

     socket.on('makeMove', (data) => {
       if (socket.id === players.find(player => player.symbol === currentPlayer).id && boardState[data.index] === '') {
         boardState[data.index] = data.player;
         io.emit('moveMade', data);
         const winner = checkWinner();
         if (winner) {
           io.emit('gameOver', { winner });
           boardState = ['', '', '', '', '', '', '', '', ''];
           currentPlayer = 'X';
         } else {
           currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
         }
       }
     });

     socket.on('disconnect', () => {
       console.log('A user disconnected:', socket.id);
       players = players.filter(player => player.id !== socket.id);
       io.emit('updatePlayers', players.map(player => player.symbol));
     });
   });

   server.listen(3000, () => {
     console.log('Server is running on http://localhost:3000');
   });
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We initialize an Express server and integrate Socket.IO for real-time communication.
  • We manage player connections and assign symbols ("X" or "O").
  • The server checks for winning conditions after each move and broadcasts updates to all connected clients.

Step 3: Creating the Frontend

Now, let’s create the frontend that players will interact with.

  1. Create the index.html File: Create an index.html file inside a public/ directory:
   <!DOCTYPE html>
   <html lang="en">
   <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Multiplayer Tic-Tac-Toe</title>
     <style>
       /* Basic styling for the game */
       #board {
         display: grid;
         grid-template-columns: repeat(3, 100px);
         grid-template-rows: repeat(3, 100px);
         gap: 5px;
       }
       .cell {
         width: 100px;
         height: 100px;
         font-size: 2em;
         display: flex;
         align-items: center;
         justify-content: center;
         border: 1px solid #333;
         cursor: pointer;
       }
       #status {
         margin-top: 20px;
         font-size: 1.5em;
       }
     </style>
   </head>
   <body>
     <h1>Tic-Tac-Toe</h1>
     <div id="board">
       <div class="cell" data-index="0"></div>
       <div class="cell" data-index="1"></div>
       <div class="cell" data-index="2"></div>
       <div class="cell" data-index="3"></div>
       <div class="cell" data-index="4"></div>
       <div class="cell" data-index="5"></div>
       <div class="cell" data-index="6"></div>
       <div class="cell" data-index="7"></div>
       <div class="cell" data-index="8"></div>
     </div>
     <div id="status"></div>

     <script src="/socket.io/socket.io.js"></script>
     <script>
       const socket = io();
       const board = document.getElementById('board');
       const cells = document.querySelectorAll('.cell');
       const status = document.getElementById('status');
       let mySymbol;
       let currentPlayer = 'X';

       socket.on('assignSymbol', (symbol) => {
         mySymbol = symbol;
         status.textContent = `You are Player ${symbol}`;
       });

       socket.on('spectator', () => {
         status.textContent = 'You are a spectator.';
       });

       socket.on('updatePlayers', (players) => {
         status.textContent = `Players: ${players.join(' vs ')}. Current player: ${currentPlayer}`;
       });

       cells.forEach(cell => {
         cell.addEventListener('click', () => {
           if (cell.textContent === '' && mySymbol === currentPlayer) {
             socket.emit('makeMove', {
               index: cell.getAttribute('data-index'),
               player: mySymbol
             });
           }
         });
       });

       socket.on('moveMade', (data) => {
         cells[data.index].textContent = data.player;
         currentPlayer = data.player === 'X' ? 'O' : 'X';
         status.textContent = `Current player: ${currentPlayer}`;
       });

       socket.on('gameOver', (data) => {
         if (data.winner === 'Draw') {
           alert('The game is a draw!');
         } else {
           alert(`Player ${data.winner} wins!`);
         }
         cells.forEach(cell => cell.textContent = '');
         currentPlayer = 'X';
       });
     </script>
   </body>
   </html>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • This HTML file creates the Tic-Tac-Toe board and displays the current game status.
  • JavaScript handles the communication with the server using Socket.IO, processes player moves, and updates the UI based on the game’s state.

Step 4: Running and Testing Your Game

Now that we’ve set up both the backend and frontend, it’s time to run and test your game.

  1. Start the Server: Run the following command in your project directory:
   node src/index.js
Enter fullscreen mode Exit fullscreen mode

Your server should start and be accessible at http://localhost:3000.

  1. Test the Game with Multiple Users:
    • Open multiple tabs or different browsers (e.g.,

Chrome and Firefox) and navigate to http://localhost:3000.

  • You can also use an incognito/private window to simulate different users.
  • Play the game by clicking on the cells and observe how the moves are reflected in real-time across all open tabs.

Bonus Tip: If you want to see the game side by side, place the browser windows next to each other on your screen.

Step 5: Extending and Improving the Game

Congratulations! You’ve successfully built a multiplayer Tic-Tac-Toe game. Here are a few ideas to extend and improve the game:

  • Add a score tracker: Keep track of wins and losses for each player.
  • Enhance the UI/UX: Improve the look and feel of the game with better styling or animations.
  • Allow spectators: Let more than two players join and watch the game in real-time.
  • Deploy your game: Deploy the game on platforms like Heroku to share it with others online.

Git HUB Project Link

https://github.com/Dipak-Ahirav/tic-tac-toe-multiplayer/tree/master

Conclusion

Building real-time multiplayer games with Socket.IO is not only fun but also a great way to understand how real-time communication works on the web. You’ve now built a simple yet fully functional multiplayer Tic-Tac-Toe game, complete with player identification, move tracking, and win/loss detection.

If you found this tutorial helpful, feel free to follow me for more content, and don’t forget to share your version of the game. Happy coding!

Code Repository

The full source code for this project is available on GitHub (insert your GitHub link here).

Call to Action

If you enjoyed this tutorial or have any questions, leave a comment below. Also, follow me for more tutorials like this one!


Start Your JavaScript Journey

If you're new to JavaScript or want a refresher, visit my blog on BuyMeACoffee to get started with the basics.

👉 Introduction to JavaScript: Your First Steps in Coding

Series Index

Part Title Link
1 Ditch Passwords: Add Facial Recognition to Your Website with FACEIO Read
2 The Ultimate Git Command Cheatsheet Read
3 Top 12 JavaScript Resources for Learning and Mastery Read
4 Angular vs. React: A Comprehensive Comparison Read
5 Top 10 JavaScript Best Practices for Writing Clean Code Read
6 Top 20 JavaScript Tricks and Tips for Every Developer 🚀 Read
7 8 Exciting New JavaScript Concepts You Need to Know Read
8 Top 7 Tips for Managing State in JavaScript Applications Read
9 🔒 Essential Node.js Security Best Practices Read
10 10 Best Practices for Optimizing Angular Performance Read
11 Top 10 React Performance Optimization Techniques Read
12 Top 15 JavaScript Projects to Boost Your Portfolio Read
13 6 Repositories To Master Node.js Read
14 Best 6 Repositories To Master Next.js Read
15 Top 5 JavaScript Libraries for Building Interactive UI Read
16 Top 3 JavaScript Concepts Every Developer Should Know Read
17 20 Ways to Improve Node.js Performance at Scale Read
18 Boost Your Node.js App Performance with Compression Middleware Read
19 Understanding Dijkstra's Algorithm: A Step-by-Step Guide Read
20 Understanding NPM and NVM: Essential Tools for Node.js Development Read

Follow and Subscribe:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player