Skip to main content

Step 3 - Win Detection: Checking for Winners

Let's add the logic to detect when someone wins or when there's a tie!

Understanding Win Conditions

In Tic-Tac-Toe, there are 8 ways to win:

  • 3 rows (horizontal)
  • 3 columns (vertical)
  • 2 diagonals

A player wins when they get 3 of their symbols in any of these lines.

Step 1: Add Game State Variables

First, let's track if the game is over:

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

Breaking it down

  • gameOver = false - Tracks if game is finished
  • winner = '' - Stores who won ('X', 'O', or 'tie')
  • We'll set these when we detect a win or tie

Step 2: Create Check Winner Function

Add this function to check all winning conditions:

function checkWinner() {
// Check rows
for (let j = 0; j < 3; j++) {
if (board[0][j] !== '' &&
board[0][j] === board[1][j] &&
board[1][j] === board[2][j]) {
return board[0][j];
}
}

// Check columns
for (let i = 0; i < 3; i++) {
if (board[i][0] !== '' &&
board[i][0] === board[i][1] &&
board[i][1] === board[i][2]) {
return board[i][0];
}
}

// Check diagonal (top-left to bottom-right)
if (board[0][0] !== '' &&
board[0][0] === board[1][1] &&
board[1][1] === board[2][2]) {
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]) {
return board[2][0];
}

// No winner yet
return null;
}

Breaking it down

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]) {
return board[i][0];
}
}
  • Loop through each row (i = 0, 1, 2)
  • board[i][0] !== '' - First cell in row is not empty
  • board[i][0] === board[i][1] - First cell equals second cell
  • board[i][1] === board[i][2] - Second cell equals third cell
  • If all three match, return the symbol ('X' or 'O')

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]) {
return board[0][j];
}
}
  • Loop through each column (j = 0, 1, 2)
  • Check if all three cells in column match
  • Same logic as rows, but checking vertically

Check diagonal (top-left to bottom-right):

if (board[0][0] !== '' && 
board[0][0] === board[1][1] &&
board[1][1] === board[2][2]) {
return board[0][0];
}
  • Check positions: [0,0], [1,1], [2,2]
  • This is the \ diagonal

Check diagonal (top-right to bottom-left):

if (board[2][0] !== '' && 
board[2][0] === board[1][1] &&
board[1][1] === board[0][2]) {
return board[2][0];
}
  • Check positions: [2,0], [1,1], [0,2]
  • This is the / diagonal

Return null if no winner:

  • If none of the winning conditions are met, return null

Step 3: Check for Tie

Add a function to check if the board is full:

function checkTie() {
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[i][j] === '') {
return false; // Found an empty cell
}
}
}
return true; // No empty cells = board is full
}

Breaking it down

  • Loop through all cells
  • If any cell is empty (''), return false - game can continue
  • If all cells are filled, return true - board is full
  • A full board with no winner = tie game

Step 4: Call Win Detection After Each Move

Update the mousePressed() function to check for wins:

function mousePressed() {
// Don't allow moves if game is over
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;
console.log(winner, "wins!");
} else if (checkTie()) {
gameOver = true;
winner = 'tie';
console.log("It's a tie!");
} else {
// Switch to other player
if (currentPlayer === 'X') {
currentPlayer = 'O';
} else {
currentPlayer = 'X';
}
}
}
}
}

Breaking it down

  • if (gameOver) return; - Prevent moves after game ends
  • After placing a symbol:
    1. checkWinner() - Check if current player won
    2. If someone won:
      • gameOver = true - Stop the game
      • winner = result - Store who won
    3. If no winner, check for tie:
      • checkTie() - Is board full?
      • If full: winner = 'tie'
    4. If no winner and no tie:
      • Switch to other player (game continues)

Test it: Play a game and try to get 3 in a row - check the console!

Step 5: Display Game Over Message

Update the draw() function to show the winner:

function draw() {
background(255);

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

// 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);
}
} 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);
}
}
  • if (!gameOver && ...) - Only show hover effect if game is still active
  • Calculates which cell the mouse is over using floor()
  • If cell is empty, highlights it with a semi-transparent gray rectangle

Game over message:

if (gameOver) {
if (winner === 'tie') {
text("It's a Tie!", width / 2, 30);
} else {
text(winner + " Wins!", width / 2, 30);
}
} else {
text("Current Player: " + currentPlayer, width / 2, 30);
}
  • If game is over and it's a tie: show "It's a Tie!"
  • If game is over with a winner: show "X Wins!" or "O Wins!"
  • If game is still active: show "Current Player: X" or "Current Player: O"

Test it: Play a full game - the winner should display at the top!

Step 6: Add Visual Feedback for Winner

Let's highlight the winning line:

let winningLine = null;  // Add at top with other variables

Update checkWinner() to store the winning line:

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;
}

Add function to draw the winning line:

function drawWinningLine() {
if (winningLine === null) return;

stroke(0, 255, 0); // Green line
strokeWeight(8);

if (winningLine.type === 'row') {
let y = winningLine.index * cellSize + cellSize / 2;
line(0, y, width, y);
} else if (winningLine.type === 'col') {
let x = winningLine.index * cellSize + cellSize / 2;
line(x, 0, x, height);
} 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
}
}
}

Call it in draw() after drawing the board:

function draw() {
// ... existing code ...

// Draw X's and O's
drawBoard();

// Draw winning line if game is over
if (gameOver) {
drawWinningLine();
}

// Display current player or game over message
// ... rest of code ...
}

Breaking it down

  • winningLine = {type: 'row', index: i} - Store which row won
  • Types: 'row', 'col', or 'diag'
  • Index: which row/column (0-2), or which diagonal (0 or 1)
  • drawWinningLine() draws a green line through the winning cells
  • Line drawn after symbols so it appears on top

Test it: Win a game - you should see a green line through the winning symbols!

Complete Step 3 Code

Here's everything together:

let cellSize = 400 / 3;
let board = [
['', '', ''],
['', '', ''],
['', '', '']
];
let currentPlayer = 'X';
let gameOver = false;
let winner = '';
let winningLine = null;

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

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

// 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);
}
} else {
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() {
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;
} else if (checkTie()) {
gameOver = true;
winner = 'tie';
} else {
// Switch to other player
if (currentPlayer === 'X') {
currentPlayer = 'O';
} else {
currentPlayer = 'X';
}
}
}
}
}

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 Learned

In this step, you:

  • ✅ Added win detection for all 8 possible winning lines
  • ✅ Checked rows, columns, and diagonals
  • ✅ Added tie detection when board is full
  • ✅ Prevented moves after game ends
  • ✅ Displayed winner or tie message
  • ✅ Drew a green line through winning symbols
  • ✅ Learned about comprehensive condition checking

What's Next?

The game is almost complete! But once a game ends, you can't play again without refreshing. Next, we'll add a restart button and some final polish.

Coming up: Restart functionality and final touches!


Next Step: Step 4 - Complete Code: Restart and Polish