Create a tic-tac-toe game in React.js

Aneeqa Khan - Jul 25 - - Dev Community

Today, while scrolling through Pinterest, I came across an adorable tic-tac-toe board (a Chocolate board) that I instantly wanted to create. I thought, why not build it using CSS and turn it into a game as well?

First, I created a React app using the create-react-app command and removed all the default code from App.js.

Create a Game Board Component

I started with a game board designed to look like chocolate, with Marshmallow and Strawberry as the players. (I know, it sounds delicious! 😋).

Gameboard

The game board displays a grid of Cell components, each representing a cell in the tic-tac-toe game. The Cell components are responsible for rendering individual cells and handling user interactions.

Here is an overview of the code below.

Message and Restart Button

  • Shows the current game message or the winning message if there is one.
  • Includes an image of a restart button. Clicking this image triggers handleRestartGame resetting the game.

Functions

1. handleRestartGame

Resets the game by clearing the cells array, setting the turn back to "marshmallow", and clearing the winning message.

2. checkScore

Check the current state of the cells to see if there’s a winning combination based on predefined winning combos (rows, columns, diagonals).

Updates winningMsg if a player has won or if the game is a draw.

3. makeRain

Positions the raindrop randomly across the screen and sets an animation duration.

Sets the content of the raindrop based on the winningMsg. For example, if "Strawberry Wins!" is displayed, the raindrop will show a strawberry emoji.

Adds the raindrop to the document body and remove it after 5 seconds.

4. startRain

Starts a rain animation by setting up an interval that repeatedly calls makeRain every 300 milliseconds.

Stops the animation after 10 seconds by clearing the interval.

State Variables

cells An array with 9 empty strings representing the tic-tac-toe grid. Each element corresponds to a cell in the grid.

go A string that indicates whose turn it is (“marshmallow” or “strawberry”).

winningMsg A string that holds the message when there’s a winner or if the game is a draw (“Marshmallow Wins!”, “Strawberry Wins!”, or “It’s a Draw!”).

rainIntervalId Stores the ID of the interval used for creating the rain animation. This allows you to clear the interval when needed.

import React, { useEffect, useState } from "react";
import restart from "./asset/refresh.png";
import Cell from "./components/Cell";

const App = () => {
  const [cells, setCells] = useState(["", "", "", "", "", "", "", "", ""]);
  const [go, setGo] = useState("marshmallow");
  const [winningMsg, setWinningMsg] = useState(null);
  const [rainIntervalId, setRainIntervalId] = useState(null);

  const message = "it is now " + go + "'s go.";

  useEffect(() => {
    checkScore();
  }, [cells]);

  useEffect(() => {
    if(winningMsg) {
      startRain();
    }
  }, [winningMsg])

  const handleRestartGame = () => {
    setCells(["", "", "", "", "", "", "", "", ""]);
    setGo("marshmallow");
    setWinningMsg(null);
    if (rainIntervalId) {
      clearInterval(rainIntervalId);
      setRainIntervalId(null);
    }
  };

  const checkScore = () => {
    const winningCombos = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [0, 4, 8],
      [2, 4, 6],
    ];

    let winnerFound = false;

    winningCombos.forEach((array) => {
      let marshmallowWins = array.every(
        (cell) => cells[cell] === "marshmallow"
      );
      if (marshmallowWins) {
        setWinningMsg("Marshmallow Wins!");
        winnerFound = true;
        return;
      }
    });

    winningCombos.forEach((array) => {
      let strawberrywWins = array.every((cell) => cells[cell] === "strawberry");
      if (strawberrywWins) {
        setWinningMsg("Strawberry Wins!");
        winnerFound = true;
        return;
      }
    });

    if (!winnerFound && cells.every((cell) => cell !== "")) {
      setWinningMsg("It's a Draw!");
    }
  };

  const makeRain = () => {
    const rain = document.createElement("div");
    rain.classList.add("makeRain");

    rain.style.left = Math.random() * 100 + "vw";
    rain.style.animationDuration = Math.random() * 2 + 3 + "s";
    if (winningMsg === "Strawberry Wins!") {
      rain.innerText = '🍓'
    } else if (winningMsg === "Marshmallow Wins!") {
      rain.innerText = "🍡"
    } else {
      rain.innerText = "😶"; // Default or draw condition
    }

    document.body.appendChild(rain);

    setTimeout(() => {
      rain.remove();
    }, 5000);
  };

  const startRain = () => {
    const intervalId = setInterval(makeRain, 300);
    setRainIntervalId(intervalId);

    setTimeout(() => {
      clearInterval(intervalId);
    }, 10000);
  };

  return (
    <div className="app">
      <div className="gameboard">
        {cells.map((cell, index) => (
          <Cell
            key={index}
            id={index}
            cell={cell}
            go={go}
            setGo={setGo}
            cells={cells}
            setCells={setCells}
            winningMsg={winningMsg}
          />
        ))}
      </div>
      <p className="message">
        {winningMsg || message}
        <img
          src={restart}
          className="restartIcon"
          alt="restart"
          onClick={handleRestartGame}
        />
      </p>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Create a Cell Component

The Cell component represents an individual cell in the tic-tac-toe grid. It handles user interactions, such as clicks, to update the cell's state and manage player turns.

It updates the cells array with the current player's symbol and switches turns between "marshmallow" and "strawberry".

import React from "react";

const Cell = ({ cell, id, go, setGo, cells, setCells, winningMsg }) => {
  const handleClick = (e) => {
    if (!winningMsg) {
      const firstChild = e.target.firstChild;
      const taken =
        firstChild?.classList.contains("marshmallow") ||
        firstChild?.classList.contains("strawberry");
      if (!taken) {
        if (go === "marshmallow") {
          firstChild.classList.add("marshmallow");
          handleCellChange("marshmallow");
          setGo("strawberry");
        } else if (go === "strawberry") {
          firstChild.classList.add("strawberry");
          handleCellChange("strawberry");
          setGo("marshmallow");
        }
      }
    }
  };

  const handleCellChange = (className) => {
    const nextCells = cells.map((cell, index) => {
      if (index === id) {
        return className;
      } else {
        return cell;
      }
    });
    setCells(nextCells);
  };

  return (
    <div className="square" id={id} onClick={handleClick}>
      <div className={cell}></div>
    </div>
  );
};

export default Cell;
Enter fullscreen mode Exit fullscreen mode

Create Styles

I give the below styles to display the tic-tac-toe app.

.app {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.gameboard {
  width: 300px;
  height: 300px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  border: 1px solid black;
  background-color: rgb(92, 51, 22);
  border-radius: 3px;
}

.square {
  width: 100px;
  height: 100px;
  border: 3px solid rgb(37, 16, 1);
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  border-radius: 3px;
  box-shadow: 3px 3px 0px 0px wheat inset;
}

.message {
  font-family: 'Gill Sans';
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.marshmallow {
  height: 70px;
  width: 70px;
  background-color: inherit;
  border: 20px;
  background-image: url("./asset/marshmallow.png");
  background-size: cover; 
}

.strawberry {
  height: 70px;
  width: 70px;
  background-color: inherit;
  border: 20px;
  background-image: url("./asset/strawberry.png");
  background-size: cover; 
}

.restartIcon {
  height: 20px;
  width: 20px;
  cursor: pointer;
  margin: 10px;
}

.makeRain {
  position: fixed;
  top: -1vh;
  font-size: 2rem;
  transform: translateY(0);
  animation: fall 2s linear forwards;
}

@keyframes fall {
  to {
      transform: translateY(105vh);
  }
}
Enter fullscreen mode Exit fullscreen mode

Screenshots of Game

tic tac toe game in react

ic tac toe game in react

Use the links below to view the code and demo of the app.

Github
Demo


Thank you for reading! Feel free to connect with me on LinkedIn or GitHub.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .