Step 4 - Complete Code: Restart and Polish
Let's add restart functionality and final polish to complete our Tic-Tac-Toe game!
Step 1: Add Restart with Keyboard
Let's allow players to restart with the SPACE key:
function keyPressed() {
if (key === ' ' && gameOver) {
resetGame();
}
}
function resetGame() {
// Clear the board
board = [
['', '', ''],
['', '', ''],
['', '', '']
];
// Reset game state
currentPlayer = 'X';
gameOver = false;
winner = '';
winningLine = null;
}
Breaking it down
keyPressed()- Called when any key is pressedif (key === ' ')- Check if SPACE bar was pressed&& gameOver- Only allow restart when game is overresetGame()- Resets everything to starting state:- Clear all symbols from board
- Set currentPlayer back to 'X'
- Set gameOver to false
- Clear winner and winningLine
Test it: Finish a game, then press SPACE - you should be able to play again!
Step 2: Add Restart Instructions
Update the game over message to tell players they can restart:
function draw() {
// ... existing code ...
// Display current player or game over message
fill(0);
noStroke();
textSize(24);
textAlign(CENTER);
if (gameOver) {
if (winner === 'tie') {
text("It's a Tie!", width / 2, 30);
} else {
text(winner + " Wins!", width / 2, 30);
}
// Add restart instructions
textSize(16);
text("Press SPACE to play again", width / 2, height - 20);
} else {
text("Current Player: " + currentPlayer, width / 2, 30);
}
}
Breaking it down
- After displaying winner/tie message
textSize(16)- Smaller text for instructionstext("Press SPACE to play again", width / 2, height - 20)- Bottom of screenheight - 20= 20 pixels from bottom
Test it: Finish a game - you should see the restart message at the bottom!
Step 3: Add Fade Animation for Game Over
Let's add a semi-transparent overlay when game ends:
function draw() {
background(255);
// Highlight hovered cell
if (!gameOver && mouseX >= 0 && mouseX < width && mouseY >= 0 && mouseY < height) {
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);
if (i >= 0 && i < 3 && j >= 0 && j < 3) {
if (board[i][j] === '') {
fill(200, 200, 200, 100);
noStroke();
rect(i * cellSize, j * cellSize, cellSize, cellSize);
}
}
}
// Draw grid lines
stroke(0);
strokeWeight(4);
line(cellSize, 0, cellSize, height);
line(cellSize * 2, 0, cellSize * 2, height);
line(0, cellSize, width, cellSize);
line(0, cellSize * 2, width, cellSize * 2);
// Draw X's and O's
drawBoard();
// Draw winning line if game is over
if (gameOver) {
drawWinningLine();
// Add semi-transparent overlay
fill(255, 255, 255, 200);
noStroke();
rect(0, 0, width, height);
// Redraw the board on top of overlay
drawBoard();
drawWinningLine();
}
// Display current player or game over message
fill(0);
noStroke();
textSize(24);
textAlign(CENTER);
if (gameOver) {
if (winner === 'tie') {
text("It's a Tie!", width / 2, 30);
} else {
text(winner + " Wins!", width / 2, 30);
}
textSize(16);
text("Press SPACE to play again", width / 2, height - 20);
} else {
text("Current Player: " + currentPlayer, width / 2, 30);
}
}
Breaking it down
Hover effect:
if (!gameOver && mouseX >= 0 && mouseX < width && mouseY >= 0 && mouseY < height) {
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);
if (i >= 0 && i < 3 && j >= 0 && j < 3 && board[i][j] === '') {
fill(200, 200, 200, 100);
rect(i * cellSize, j * cellSize, cellSize, cellSize);
}
}
- Only shows when game is still active
- Highlights which cell the mouse is over
Game over overlay:
if (gameOver) {
drawWinningLine();
fill(255, 255, 255, 200); // Semi-transparent white
rect(0, 0, width, height); // Cover entire board
drawBoard(); // Redraw on top of overlay
drawWinningLine();
}
- When game ends, creates a fade effect
- Overlay makes board appear slightly faded
- Redraws board and winning line on top to highlight winner
Game over message:
if (gameOver) {
if (winner === 'tie') {
text("It's a Tie!", width / 2, 30);
} else {
text(winner + " Wins!", width / 2, 30);
}
text("Press SPACE to play again", width / 2, height - 20);
} else {
text("Current Player: " + currentPlayer, width / 2, 30);
}
- Displays winner or current player
- Shows restart instructions when game is over
Step 4: Improve Symbol Drawing
Let's make X's and O's slightly more animated:
function drawX(x, y) {
let offset = cellSize / 4;
stroke(255, 0, 0);
strokeWeight(8);
strokeCap(ROUND); // Rounded ends
line(x - offset, y - offset, x + offset, y + offset);
line(x + offset, y - offset, x - offset, y + offset);
}
function drawO(x, y) {
stroke(0, 0, 255);
strokeWeight(8);
strokeCap(ROUND); // Rounded ends
noFill();
circle(x, y, cellSize / 2);
}
Breaking it down
strokeCap(ROUND)- Makes line ends rounded instead of square- Gives symbols a smoother, more polished look
Step 5: Add Score Tracking
Let's keep track of wins across multiple games:
let cellSize = 400 / 3;
let board = [
['', '', ''],
['', '', ''],
['', '', '']
];
let currentPlayer = 'X';
let gameOver = false;
let winner = '';
let winningLine = null;
let xWins = 0;
let oWins = 0;
let ties = 0;
Update mousePressed() to increment scores:
function mousePressed() {
if (gameOver) return;
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);
if (i >= 0 && i < 3 && j >= 0 && j < 3) {
if (board[i][j] === '') {
board[i][j] = currentPlayer;
// Check for winner
let result = checkWinner();
if (result !== null) {
gameOver = true;
winner = result;
// Update scores
if (winner === 'X') {
xWins++;
} else if (winner === 'O') {
oWins++;
}
} else if (checkTie()) {
gameOver = true;
winner = 'tie';
ties++; // Update tie count
} else {
// Switch to other player
if (currentPlayer === 'X') {
currentPlayer = 'O';
} else {
currentPlayer = 'X';
}
}
}
}
}
Display scores at the bottom:
function draw() {
// ... existing code ...
// Display current player or game over message
fill(0);
noStroke();
textSize(24);
textAlign(CENTER);
if (gameOver) {
if (winner === 'tie') {
text("It's a Tie!", width / 2, 30);
} else {
text(winner + " Wins!", width / 2, 30);
}
textSize(16);
text("Press SPACE to play again", width / 2, height - 20);
} else {
text("Current Player: " + currentPlayer, width / 2, 30);
}
// Display score at bottom
textSize(18);
textAlign(LEFT);
text("X: " + xWins, 10, height - 10);
textAlign(CENTER);
text("Ties: " + ties, width / 2, height - 10);
textAlign(RIGHT);
text("O: " + oWins, width - 10, height - 10);
}
Breaking it down
- Three score variables:
xWins,oWins,ties - Increment appropriate score when game ends
- Display at bottom:
- Left side: X wins
- Center: Ties
- Right side: O wins
- Scores persist across game restarts!
Step 6: Add Cursor Change on Hover
Make cursor change to pointer when hovering empty cells:
function draw() {
background(255);
// Change cursor based on hover
let hovering = false;
if (!gameOver && mouseX >= 0 && mouseX < width && mouseY >= 0 && mouseY < height) {
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);
if (i >= 0 && i < 3 && j >= 0 && j < 3) {
if (board[i][j] === '') {
hovering = true;
fill(200, 200, 200, 100);
noStroke();
rect(i * cellSize, j * cellSize, cellSize, cellSize);
}
}
}
// Set cursor
if (hovering) {
cursor(HAND);
} else {
cursor(ARROW);
}
// ... rest of draw code ...
}
Breaking it down
- Track if mouse is over an empty cell with
hoveringvariable cursor(HAND)- Shows pointing hand cursorcursor(ARROW)- Normal arrow cursor- Gives visual feedback that cells are clickable
Complete Final Code
Here's the complete, polished Tic-Tac-Toe game:
let cellSize = 400 / 3;
let board = [
['', '', ''],
['', '', ''],
['', '', '']
];
let currentPlayer = 'X';
let gameOver = false;
let winner = '';
let winningLine = null;
let xWins = 0;
let oWins = 0;
let ties = 0;
function setup() {
createCanvas(400, 400);
}
function draw() {
background(255);
// Change cursor and highlight hovered cell
let hovering = false;
if (!gameOver && mouseX >= 0 && mouseX < width && mouseY >= 0 && mouseY < height) {
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);
if (i >= 0 && i < 3 && j >= 0 && j < 3) {
if (board[i][j] === '') {
hovering = true;
fill(200, 200, 200, 100);
noStroke();
rect(i * cellSize, j * cellSize, cellSize, cellSize);
}
}
}
if (hovering) {
cursor(HAND);
} else {
cursor(ARROW);
}
// Draw grid lines
stroke(0);
strokeWeight(4);
line(cellSize, 0, cellSize, height);
line(cellSize * 2, 0, cellSize * 2, height);
line(0, cellSize, width, cellSize);
line(0, cellSize * 2, width, cellSize * 2);
// Draw X's and O's
drawBoard();
// Draw winning line and overlay if game is over
if (gameOver) {
drawWinningLine();
fill(255, 255, 255, 200);
noStroke();
rect(0, 0, width, height);
drawBoard();
drawWinningLine();
}
// Display current player or game over message
fill(0);
noStroke();
textSize(24);
textAlign(CENTER);
if (gameOver) {
if (winner === 'tie') {
text("It's a Tie!", width / 2, 30);
} else {
text(winner + " Wins!", width / 2, 30);
}
textSize(16);
text("Press SPACE to play again", width / 2, height - 20);
} else {
text("Current Player: " + currentPlayer, width / 2, 30);
}
// Display scores
textSize(18);
textAlign(LEFT);
text("X: " + xWins, 10, height - 10);
textAlign(CENTER);
text("Ties: " + ties, width / 2, height - 10);
textAlign(RIGHT);
text("O: " + oWins, width - 10, height - 10);
}
function drawBoard() {
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let x = i * cellSize + cellSize / 2;
let y = j * cellSize + cellSize / 2;
let symbol = board[i][j];
if (symbol === 'X') {
drawX(x, y);
} else if (symbol === 'O') {
drawO(x, y);
}
}
}
}
function drawX(x, y) {
let offset = cellSize / 4;
stroke(255, 0, 0);
strokeWeight(8);
strokeCap(ROUND);
line(x - offset, y - offset, x + offset, y + offset);
line(x + offset, y - offset, x - offset, y + offset);
}
function drawO(x, y) {
stroke(0, 0, 255);
strokeWeight(8);
strokeCap(ROUND);
noFill();
circle(x, y, cellSize / 2);
}
function mousePressed() {
if (gameOver) return;
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);
if (i >= 0 && i < 3 && j >= 0 && j < 3) {
if (board[i][j] === '') {
board[i][j] = currentPlayer;
let result = checkWinner();
if (result !== null) {
gameOver = true;
winner = result;
if (winner === 'X') {
xWins++;
} else if (winner === 'O') {
oWins++;
}
} else if (checkTie()) {
gameOver = true;
winner = 'tie';
ties++;
} else {
if (currentPlayer === 'X') {
currentPlayer = 'O';
} else {
currentPlayer = 'X';
}
}
}
}
}
function keyPressed() {
if (key === ' ' && gameOver) {
resetGame();
}
}
function resetGame() {
board = [
['', '', ''],
['', '', ''],
['', '', '']
];
currentPlayer = 'X';
gameOver = false;
winner = '';
winningLine = null;
}
function checkWinner() {
// Check rows
for (let i = 0; i < 3; i++) {
if (board[i][0] !== '' &&
board[i][0] === board[i][1] &&
board[i][1] === board[i][2]) {
winningLine = {type: 'row', index: i};
return board[i][0];
}
}
// Check columns
for (let j = 0; j < 3; j++) {
if (board[0][j] !== '' &&
board[0][j] === board[1][j] &&
board[1][j] === board[2][j]) {
winningLine = {type: 'col', index: j};
return board[0][j];
}
}
// Check diagonal (top-left to bottom-right)
if (board[0][0] !== '' &&
board[0][0] === board[1][1] &&
board[1][1] === board[2][2]) {
winningLine = {type: 'diag', index: 0};
return board[0][0];
}
// Check diagonal (top-right to bottom-left)
if (board[2][0] !== '' &&
board[2][0] === board[1][1] &&
board[1][1] === board[0][2]) {
winningLine = {type: 'diag', index: 1};
return board[2][0];
}
return null;
}
function checkTie() {
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[i][j] === '') {
return false;
}
}
}
return true;
}
function drawWinningLine() {
if (winningLine === null) return;
stroke(0, 255, 0);
strokeWeight(8);
if (winningLine.type === 'row') {
let x = winningLine.index * cellSize + cellSize / 2;
line(x, 0, x, height); // Vertical line through column
} else if (winningLine.type === 'col') {
let y = winningLine.index * cellSize + cellSize / 2;
line(0, y, width, y); // Horizontal line through row
} else if (winningLine.type === 'diag') {
if (winningLine.index === 0) {
line(0, 0, width, height); // Top-left to bottom-right
} else {
line(width, 0, 0, height); // Top-right to bottom-left
}
}
}
What You Built
Congratulations! You now have a fully functional Tic-Tac-Toe game with:
- ✅ Clean 3×3 game board with grid lines
- ✅ Red X's and blue O's
- ✅ Click to place symbols
- ✅ Hover effects showing which cell you're about to click
- ✅ Turn switching between players
- ✅ Win detection for all 8 possible winning lines
- ✅ Tie detection when board is full
- ✅ Green line highlighting the winning symbols
- ✅ Semi-transparent overlay on game over
- ✅ Score tracking across multiple games
- ✅ Restart with SPACE key
- ✅ Clear instructions and feedback
- ✅ Polished visuals with rounded line caps
- ✅ Cursor changes on hover
What You Learned
Throughout this tutorial, you learned:
Game Development Concepts
- Grid-based coordinate systems
- 2D arrays for game boards
- Turn-based game logic
- Win condition checking
- Game state management
p5.js Skills
- Canvas setup and drawing
- Mouse input with
mousePressed() - Keyboard input with
keyPressed() - Drawing shapes and lines
- Text styling and alignment
- Cursor customization
- Transparency and overlays
Programming Techniques
- Nested loops for 2D grids
- Converting pixel coordinates to grid positions
- Comprehensive condition checking
- Modular function design
- State management with boolean flags
Ideas to Expand
Want to keep improving your game? Try these challenges:
Easy Additions
- Add sound effects for clicks and wins
- Change colors and themes
- Make the board bigger (4×4 or 5×5)
- Add animations when symbols appear
- Different symbols instead of X and O
Medium Challenges
- Add a timer for each turn
- Highlight the last move made
- Add player names instead of X and O
- Save high scores to localStorage
- Add different difficulty levels
Advanced Features
- Add computer AI opponent
- Make AI unbeatable (minimax algorithm)
- Create a tournament mode
- Add online multiplayer
- Make different board sizes selectable
Sharing Your Game
Ready to share? Here's how:
- Save your code - Click File → Save in the p5.js editor
- Share the link - Copy the URL from your browser
- Embed in website - Use File → Share → Embed
- Download - File → Download to save locally
What's Next?
Now that you've built Tic-Tac-Toe, you're ready for more complex games!
Check out our other tutorials:
- Cookie Clicker - Incremental game mechanics
- Worm/Snake - Movement and collision
- Pong - Two-player arcade action
Or explore the Common Elements reference to learn more techniques!
Congratulations! 🎉
You've successfully built a complete Tic-Tac-Toe game! This is a major milestone in your game development journey.
You started with an empty canvas and built:
- A grid system
- Interactive gameplay
- Win detection logic
- Polish and feedback
These skills transfer to any game you want to build. Keep coding, keep experimenting, and most importantly - have fun! ⭕❌
Tutorial Complete! Check out more at the tutorials page