Skip to main content

Step 2 - Click Detection: Placing Symbols

Let's make the game interactive by detecting mouse clicks and placing symbols on the board!

Understanding Click-to-Grid Conversion

When you click the canvas:

  1. Get mouse position (mouseX, mouseY)
  2. Convert to grid position (which cell was clicked?)
  3. Place current player's symbol
  4. Switch to other player

Step 1: Add mousePressed Function

Add this function at the bottom of your code:

function mousePressed() {
// Convert mouse position to grid position
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);

console.log("Clicked cell:", i, j);
}

Breaking it down

  • mousePressed() - Special p5.js function called once when mouse is clicked
  • floor(mouseX / cellSize) - Converts X pixel to column number
    • If mouseX = 50, and cellSize = 133.33, then 50 / 133.33 = 0.375
    • floor(0.375) = 0 (first column)
    • If mouseX = 200, then 200 / 133.33 = 1.5
    • floor(1.5) = 1 (second column)
  • floor(mouseY / cellSize) - Same for Y coordinate to get row
  • console.log() - Shows in console which cell was clicked

Test it: Click different cells and watch the console - you should see numbers 0-2!

Step 2: Check if Cell is Empty

Before placing a symbol, make sure the cell isn't already occupied:

function mousePressed() {
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);

// Make sure click is within bounds
if (i >= 0 && i < 3 && j >= 0 && j < 3) {
// Check if cell is empty
if (board[i][j] === '') {
console.log("Cell is empty! Can place", currentPlayer);
} else {
console.log("Cell already has:", board[i][j]);
}
}
}

Breaking it down

  • if (i >= 0 && i < 3 && j >= 0 && j < 3) - Bounds checking
    • Makes sure click is inside the 3×3 grid
    • Prevents errors if click is outside canvas
  • if (board[i][j] === '') - Check if cell is empty
    • Empty cells have empty string ''
    • Only allow placing symbol in empty cells
    • Prevents overwriting existing symbols

Test it: Click on empty cells, then click the same cell again - console shows different messages!

Step 3: Place the Symbol

Now actually place the symbol on the board:

function mousePressed() {
let i = floor(mouseX / cellSize);
let j = floor(mouseY / cellSize);

if (i >= 0 && i < 3 && j >= 0 && j < 3) {
if (board[i][j] === '') {
// Place current player's symbol
board[i][j] = currentPlayer;
console.log(currentPlayer, "placed at", i, j);
}
}
}

Breaking it down

  • board[i][j] = currentPlayer - Sets cell to 'X' or 'O'
  • This modifies the board array
  • drawBoard() in draw() automatically shows the new symbol
  • Symbol appears immediately!

Test it: Click cells - X's should appear! But we can only place X's right now...

Step 4: Switch Players After Each Move

Let's alternate between X and O:

function mousePressed() {
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;

// Switch to other player
if (currentPlayer === 'X') {
currentPlayer = 'O';
} else {
currentPlayer = 'X';
}
}
}
}

Breaking it down

  • After placing a symbol, we switch players
  • if (currentPlayer === 'X') - If X just played
    • currentPlayer = 'O' - Make it O's turn
  • else - If O just played
    • currentPlayer = 'X' - Make it X's turn
  • Players automatically alternate!

Test it: Click cells - should alternate between red X's and blue O's!

Step 5: Display Current Player

Let's show whose turn it is:

function draw() {
background(255);

// 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();

// Display current player
fill(0);
noStroke();
textSize(24);
textAlign(CENTER);
text("Current Player: " + currentPlayer, width / 2, 30);
}

Breaking it down

  • fill(0) - Black text
  • noStroke() - Remove outline from text
  • textSize(24) - Readable size
  • textAlign(CENTER) - Center the text horizontally
  • text("Current Player: " + currentPlayer, width / 2, 30) - Shows "Current Player: X" or "Current Player: O"
    • width / 2 = center of canvas (200)
    • 30 = 30 pixels from top

Test it: You should see "Current Player: X" at the top, changing after each move!

Step 6: Add Hover Effect

Let's show which cell you're about to click:

function draw() {
background(255);

// Highlight hovered cell
if (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); // Light gray, semi-transparent
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();

// Display current player
fill(0);
noStroke();
textSize(24);
textAlign(CENTER);
text("Current Player: " + currentPlayer, width / 2, 30);
}

Breaking it down

Display current player:

fill(0);
noStroke();
textSize(24);
textAlign(CENTER);
text("Current Player: " + currentPlayer, width / 2, 30);
  • Shows "Current Player: X" or "Current Player: O" at the top
  • width / 2 = center of canvas (200)
  • 30 = 30 pixels from top
  • Changes after each move

Hover effect:

if (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);
}
}
  • Checks if mouse is over the canvas
  • Converts mouse position to grid cell (i, j)
  • If cell is empty, highlights it with semi-transparent gray
  • fill(200, 200, 200, 100) - Light gray with transparency (100 = alpha)
  • Hover effect draws BEFORE grid lines so lines stay on top

Test it: Move your mouse over empty cells - they should highlight!

Complete Step 2 Code

Here's everything together:

let cellSize = 400 / 3;
let board = [
['', '', ''],
['', '', ''],
['', '', '']
];
let currentPlayer = 'X';

function setup() {
createCanvas(400, 400);
}

function draw() {
background(255);

// Highlight hovered cell
if (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();

// Display current player
fill(0);
noStroke();
textSize(24);
textAlign(CENTER);
text("Current Player: " + currentPlayer, width / 2, 30);
}

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);
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);
noFill();
circle(x, y, cellSize / 2);
}

function mousePressed() {
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;

// Switch to other player
if (currentPlayer === 'X') {
currentPlayer = 'O';
} else {
currentPlayer = 'X';
}
}
}
}

What You Learned

In this step, you:

  • ✅ Detected mouse clicks on the canvas
  • ✅ Converted pixel coordinates to grid coordinates
  • ✅ Checked if cells are empty before placing
  • ✅ Placed symbols on the board
  • ✅ Switched players automatically
  • ✅ Displayed current player
  • ✅ Added hover effect for better UX

What's Next?

You can now play the game! But there's no way to win yet - the game continues forever. Next, we'll add win detection to check if someone has won.

Coming up: Detecting winners and ties!


Next Step: Step 3 - Win Detection: Checking for Winners