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.
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
- 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
- Install Dependencies: We’ll need Express for our server and Socket.IO for real-time communication.
npm install express socket.io
Step 2: Building the Backend with Express and Socket.IO
Now, let's set up our backend.
-
Set Up the Basic Server:
Create a file named
index.js
in asrc/
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');
});
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.
-
Create the
index.html
File: Create anindex.html
file inside apublic/
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>
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.
- Start the Server: Run the following command in your project directory:
node src/index.js
Your server should start and be accessible at http://localhost:3000
.
-
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:
- YouTube: devDive with Dipak
- Website: Dipak Ahirav
- Whatsapp Channel:DevDiveWithDipak
- Email: dipaksahirav@gmail.com
- LinkedIn: Dipak Ahirav