
This tutorial will guide you step-by-step through building a fully functional Tic-Tac-Toe game using Phaser.js. By the end, you’ll understand how to set up your development environment, create interactive grids, handle player input, check for wins or draws, and polish your game for a smooth user experience.
What is Phaser.js?
Phaser.js is a fast, free, and open-source HTML5 game framework used to build browser-based games. It provides tools for creating graphics, handling physics, managing input, and playing sounds. Phaser simplifies complex game logic by offering pre-built systems, allowing developers to focus on gameplay rather than low-level coding.
Some benefits of Phaser.js include:
Cross-platform compatibility: Works on desktop, mobile, and web browsers.
Lightweight and flexible: Perfect for both small projects and large-scale games.
Beginner-friendly: Easy to learn with clear documentation and examples.
Supports animations, physics, and assets: Ideal for visual and interactive gameplay.
Setting Up Your Development Environment
Before writing any code, you’ll need a few basic tools:
A text editor: Visual Studio Code or Sublime Text are good choices.
A local web server: Phaser games require a server environment to run properly. You can use Node.js, Python, or even VS Code’s Live Server extension.
Phaser.js library: Download the Phaser.js file from its official package or use a CDN link.
Create a new project folder named tic-tac-toe-phaser. Inside it, make the following structure:
tic-tac-toe-phaser/
│
├── index.html
├── main.js
└── assets/
Now, let’s begin coding.
Step 1: Setting Up the HTML File
Open index.html and set up a simple structure to load Phaser.js and your game script.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tic-Tac-Toe in Phaser.js</title>
<script src="https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.js"></script>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
This file connects your Phaser game engine and the JavaScript file where you’ll write the actual game logic.
Step 2: Creating the Game Configuration
Open main.js and start by initializing a Phaser game configuration.
const config = {
type: Phaser.AUTO,
width: 600,
height: 600,
backgroundColor: '#ffffff',
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
This creates a 600x600 pixel game window with a white background. The scene will have three main functions:
preload: Load any assets if needed.
create: Initialize the game board.
update: Handle continuous game logic (though Tic-Tac-Toe doesn’t need much here).
Step 3: Designing the Game Grid
In Tic-Tac-Toe, the board consists of a 3x3 grid. You can draw it using simple Phaser graphics.
function preload() {}
let graphics;
let grid = [];
let currentPlayer = 'X';
let gameOver = false;
function create() {
graphics = this.add.graphics({ lineStyle: { width: 5, color: 0x000000 } });
// Draw vertical lines
graphics.strokeLineShape(new Phaser.Geom.Line(200, 0, 200, 600));
graphics.strokeLineShape(new Phaser.Geom.Line(400, 0, 400, 600));
// Draw horizontal lines
graphics.strokeLineShape(new Phaser.Geom.Line(0, 200, 600, 200));
graphics.strokeLineShape(new Phaser.Geom.Line(0, 400, 600, 400));
// Create 3x3 grid data
for (let row = 0; row < 3; row++) {
grid[row] = [];
for (let col = 0; col < 3; col++) {
grid[row][col] = '';
}
}
this.input.on('pointerdown', (pointer) => handleClick(pointer, this));
}
Here we’re drawing the grid lines and initializing an empty 3x3 matrix to store moves.
Step 4: Handling Player Clicks
Now we’ll capture where the player clicks and place an “X” or “O” accordingly.
function handleClick(pointer, scene) {
if (gameOver) return;
const col = Math.floor(pointer.x / 200);
const row = Math.floor(pointer.y / 200);
if (grid[row][col] === '') {
grid[row][col] = currentPlayer;
drawSymbol(scene, row, col, currentPlayer);
if (checkWinner()) {
scene.add.text(150, 550, `${currentPlayer} Wins!`, { fontSize: '32px', color: '#ff0000' });
gameOver = true;
return;
} else if (isDraw()) {
scene.add.text(200, 550, `It's a Draw!`, { fontSize: '32px', color: '#0000ff' });
gameOver = true;
return;
}
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
}
}
This function determines which cell was clicked and places a symbol if it’s empty. Then it checks for a win or draw.
Step 5: Drawing X and O
Now, create a function to display symbols at the right spot.
function drawSymbol(scene, row, col, player) {
const x = col * 200 + 100;
const y = row * 200 + 100;
if (player === 'X') {
scene.add.text(x - 30, y - 50, 'X', { fontSize: '100px', color: '#000000' });
} else {
scene.add.text(x - 30, y - 50, 'O', { fontSize: '100px', color: '#ff0000' });
}
}
This function adds a large X or O text centered in the selected cell.
Step 6: Checking for a Winner
We’ll now write the logic that checks all possible winning combinations.
function checkWinner() {
// Check rows and columns
for (let i = 0; i < 3; i++) {
if (grid[i][0] && grid[i][0] === grid[i][1] && grid[i][1] === grid[i][2]) return true;
if (grid[0][i] && grid[0][i] === grid[1][i] && grid[1][i] === grid[2][i]) return true;
}
// Check diagonals
if (grid[0][0] && grid[0][0] === grid[1][1] && grid[1][1] === grid[2][2]) return true;
if (grid[0][2] && grid[0][2] === grid[1][1] && grid[1][1] === grid[2][0]) return true;
return false;
}
This code verifies whether any row, column, or diagonal has the same non-empty value — meaning a player has won.
Step 7: Checking for a Draw
If no one wins but all cells are filled, it’s a draw.
function isDraw() {
return grid.flat().every(cell => cell !== '');
}
This function checks whether every cell in the grid is occupied.
Step 8: Restarting the Game
To make the game replayable, add a restart feature.
function resetGame(scene) {
grid = [['', '', ''], ['', '', ''], ['', '', '']];
currentPlayer = 'X';
gameOver = false;
scene.scene.restart();
}
You can call this function when the game ends or by adding a restart button.
Step 9: Adding Finishing Touches
Enhance your game visually and functionally:
Add background color changes when players win.
Include sound effects for clicks or wins.
Display a score counter for each player.
Animate the winning line across the board.
These small features can make your game more engaging and professional.
Step 10: Testing and Debugging
Before finalizing, test your game on different browsers and devices to ensure smooth performance. Check for:
Proper scaling of the grid.
Accurate click detection on all cells.
Correct display of symbols.
Proper game-over logic and restart functionality.
Phaser.js runs efficiently in all modern browsers, but testing ensures that everything works seamlessly.
Tips for Expanding the Game
Once you have a basic Tic-Tac-Toe working, you can experiment with more advanced concepts:
Add AI Opponent: Implement a computer-controlled player using the minimax algorithm for single-player mode.
Multiplayer Online: Use WebSocket or Firebase for real-time online gameplay.
Mobile Optimization: Adjust canvas size for small screens and use touch-friendly UI.
Themes and Skins: Allow players to choose between light and dark themes or different symbol designs.
Leaderboard System: Track player wins and store scores locally or in a database.
Why Phaser.js is Ideal for Simple Games
Phaser.js strikes a perfect balance between simplicity and power. It’s great for small games like Tic-Tac-Toe, Pong, or Memory Match, yet robust enough for platformers and RPGs. Its event system, asset management, and rendering capabilities make it one of the best frameworks for rapid game development.
Conclusion
Building a Tic-Tac-Toe game using Phaser.js is a fun and rewarding project that helps you grasp essential concepts in game design and JavaScript programming. You’ve learned how to create a grid, handle user input, check for winners, and design a clean interface—all fundamental skills for any aspiring game developer.
As you continue exploring Phaser.js, try building other simple games to strengthen your skills. Each new project will deepen your understanding of game loops, input handling, and creative logic. Soon, you’ll be ready to tackle more complex game mechanics and design projects with confidence.