Friday, November 15, 2024
Google search engine
HomeLanguagesHow to build a Tic-Tac-Toe Game using React Hooks ?

How to build a Tic-Tac-Toe Game using React Hooks ?

React is a frontend open-source JavaScript library, used to build interactive User Interfaces. React is focused on Single Page applications and is more popularly known as a SPA. In this tutorial, we’ll use React and its hooks to build a fun Tic-Tac-Toe application. Before jumping into code make sure the pre-requisites are checked for a better understanding.

Prerequisite:

Modules required:

  • npm
  • React

Creating React App and Setting Up: 

Step 1: You will start a new project using create-react-app so open your terminal and type.

npx create-react-app tic-tac-toe-react

Step 2: Switch to the tic-tac-toe-react folder using the following command.

cd tic-tac-toe-react

Step 3: Change to the src folder and remove the unnecessary stuff using the following command

cd src
rm *

Step 4: Create a css folder in src, which contains the app.css, board.css, index.css, and info.css files.

mkdir css
touch app.css board.css index.css info.css

Step 5: In the src folder, create App.js, Board.js, index.js, and Info.js files.

touch App.js Board.js index.js Info.js

Project Structure: The file structure in the project will look like this.

Example: This example will guide you with code to build a Tic-Tac-Toe game using React Hooks.

index.js: This file links the HTML file and the react code. Edit the index.js file in the following manner: 

Javascript




import React from 'react';
import ReactDOM from 'react-dom';
import './css/index.css';
import App from './App';
 
ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
    document.getElementById('root')
);


App.js: This file acts like a base file containing the Info and Board components. Edit the App.js file in the following manner: 

Javascript




// Importing the required components
import Board from './Board';
import Info from "./Info";
 
// Importing the CSS File
import "./css/app.css";
 
// Importing the useState hook
import { useState } from 'react';
 
function App() {
 
    // Creating a reset state, which indicates whether
    // the game should be reset or not
    const [reset, setReset] = useState(false);
 
    // Creating a winner state, which indicates
    // the current winner
    const [winner, setWinner] = useState('');
 
    // Sets the reset property to true
    // which starts the chain
    // reaction of resetting the board
    const resetBoard = () => {
        setReset(true);
    }
 
    return (
        <div className="App">
            {/* Shrinks the popup when there is no winner */}
            <div className={`winner ${winner !== '' ? '' : 'shrink'}`}>
                {/* Display the current winner */}
                <div className='winner-text'>{winner}</div>
                {/* Button used to reset the board */}
                <button onClick={() => resetBoard()}>
                    Reset Board
                </button>
            </div>
            {/* Custom made board component comprising of
            the tic-tac-toe board  */}
            <Board reset={reset} setReset={setReset} winner={winner}
                setWinner={setWinner} />
            <Info />
        </div>
    );
}
 
export default App;


Board.js: This file contains the tic-tac-toe board and the game logic. Edit the Board.js in the following manner: 

Javascript




// Importing the CSS for the board
import "./css/board.css";
 
// Importing the useState hook, useEffect hook and useRef hook
import { useState, useEffect, useRef } from "react";
 
const Board = ({ reset, setReset, winner, setWinner }) => {
 
    // Creating a turn state, which indicates the current turn
    const [turn, setTurn] = useState(0);
 
    // Creating a data state, which contains the
    // current picture of the board
    const [data, setData] = useState(['', '', '', '', '',
        '', '', '', ''])
 
    // Creating a reference for the board
    const boardRef = useRef(null);
 
    // Function to draw on the board
    const draw = (event, index) => {
        // Draws only if the position is not taken
        // and winner is not decided yet
        if (data[index - 1] === '' && winner === '') {
 
            // Draws X if it's player 1's turn else draws O
            const current = turn === 0 ? "X" : "O"
 
            // Updating the data state
            data[index - 1] = current;
 
            //Drawing on the board
            event.target.innerText = current;
 
            // Switching the turn
            setTurn(turn === 0 ? 1 : 0)
        }
    }
 
    // UseEffect hook used to reset the board whenever
    // a winner is decided
    useEffect(() => {
 
        // Clearing the data state
        setData(['', '', '', '', '', '', '', '', '']);
 
        // Getting all the children(cells) of the board
        const cells = boardRef.current.children
 
        // Clearing out the board
        for (let i = 0; i < 9; i++) {
            cells[i].innerText = '';
        }
 
        // Resetting the turn to player 0
        setTurn(0);
 
        // Resetting the winner
        setWinner('');
        setReset(false);
    }, [reset, setReset, setWinner])
 
 
    // useEffect hook used to check for a winner
    useEffect(() => {
 
        // Checks for the win condition in rows
        const checkRow = () => {
            let ans = false;
            for (let i = 0; i < 9; i += 3) {
                ans |= (data[i] === data[i + 1] &&
                    data[i] === data[i + 2] &&
                    data[i] !== '')
            }
            return ans;
        }
 
        // Checks for the win condition in cols
        const checkCol = () => {
            let ans = false;
            for (let i = 0; i < 3; i++) {
                ans |= (data[i] === data[i + 3] &&
                    data[i] === data[i + 6] &&
                    data[i] !== '')
            }
            return ans;
        }
 
        // Checks for the win condition in diagonals
        const checkDiagonal = () => {
            return ((data[0] === data[4] &&
                data[0] === data[8] && data[0] !== '') ||
                (data[2] === data[4] && data[2] === data[6] &&
                    data[2] !== ''));
        }
 
        // Checks if at all a win condition is present
        const checkWin = () => {
            return (checkRow() || checkCol() || checkDiagonal());
        }
 
        // Checks for a tie
        const checkTie = () => {
            let count = 0;
            data.forEach((cell) => {
                if (cell !== '') {
                    count++;
                }
            })
            return count === 9;
        }
 
        // Setting the winner in case of a win
        if (checkWin()) {
            setWinner(turn === 0 ? "Player 2 Wins!" :
                "Player 1 Wins!");
        } else if (checkTie()) {
 
            // Setting the winner to tie in case of a tie
            setWinner("It's a Tie!");
        }
 
    })
 
    return (
        <div ref={boardRef} className="board">
            <div className="input input-1"
                onClick={(e) => draw(e, 1)}></div>
            <div className="input input-2"
                onClick={(e) => draw(e, 2)}></div>
            <div className="input input-3"
                onClick={(e) => draw(e, 3)}></div>
            <div className="input input-4"
                onClick={(e) => draw(e, 4)}></div>
            <div className="input input-5"
                onClick={(e) => draw(e, 5)}></div>
            <div className="input input-6"
                onClick={(e) => draw(e, 6)}></div>
            <div className="input input-7"
                onClick={(e) => draw(e, 7)}></div>
            <div className="input input-8"
                onClick={(e) => draw(e, 8)}></div>
            <div className="input input-9"
                onClick={(e) => draw(e, 9)}></div>
        </div>
    )
}
 
export default Board;


Info.js: This file contains info about the tic-tac-toe game. Edit Info.js in the following manner: 

Javascript




// Importing the css for the info
import "./css/info.css";
 
const Info = () => {
    return (
        <div className="info">
            <div className="player">Player 1: X</div>
            <div className="player">Player 2: O</div>
        </div>
    )
}
 
export default Info;


index.css

CSS




*{
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;   
    box-sizing: border-box; 
  }
   
  body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
      'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
      'Droid Sans', 'Helvetica Neue',
      sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
   
  code {
    font-family: source-code-pro, Menlo, Monaco,
      Consolas, 'Courier New',
      monospace;
  }


App.css

CSS




 
.App {
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    gap: 5vh;
    backdrop-filter: 5px;
    background-color: #101010;
}
 
.winner {
    transition: all ease-in .3s;
    display: flex;
    opacity: 1;
    font-size: 1.5rem;
    font-weight: 600;
    gap: 1vh;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 20vw;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -70%);
    background-color: rgba(195, 141, 158, 0.863);
    backdrop-filter: 5px;
    padding: .5rem;
    padding-bottom: 1rem;
    border-radius: 10%;
}
 
.winner-text {
    padding: .3em 1em .25em;
    font-weight: 600;
    font-size: 2.5rem;
    color: white;
    font-family: 'Bellefair', serif;
    position: relative;
    text-align: center;
    line-height: 1.3;
}
 
.shrink {
    transform: scale(.1);
    opacity: 0;
}
 
button {
    background-color: #111827;
    border: 1px solid transparent;
    border-radius: .75rem;
    box-sizing: border-box;
    color: #FFFFFF;
    cursor: pointer;
    flex: 0 0 auto;
    font-family: "Inter var";
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.5rem;
    padding: .75rem 1.2rem;
    text-align: center;
    text-decoration: none #6B7280 solid;
    text-decoration-thickness: auto;
    transition-duration: .2s;
    transition-property: background-color, border-color,
        color, fill, stroke;
    transition-timing-function: cubic-bezier(.4, 0, 0.2, 1);
    user-select: none;
    -webkit-user-select: none;
    touch-action: manipulation;
    width: auto;
}
 
button:hover {
    background-color: #374151;
}
 
button:focus {
    box-shadow: none;
    outline: 2px solid transparent;
    outline-offset: 2px;
}
 
@media (min-width: 768px) {
    button {
        padding: .75rem 1.5rem;
    }
}
 
;


board.css

CSS




:root {
    --board-background: none;
    --border-color: #f6546a;
    --border-thickness: 5px;
}
 
.board {
    width: 30vw;
    height: 50%;
    background-color: var(--board-background);
    display: flex;
    align-items: flex-start;
    flex-direction: row;
    flex-wrap: wrap;
}
 
.input {
    height: 33.33%;
    width: 33.33%;
    display: flex;
    justify-content: center;
    align-items: center;
    color: whitesmoke;
    font-family: 'Bellefair', serif;
    font-style: italic;
    font-weight: 700;
    font-size: 6rem;
}
 
.input-1 {
    border-right: var(--border-thickness) dashed var(--border-color);
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-2 {
    border-right: var(--border-thickness) dashed var(--border-color);
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-3 {
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-4 {
    border-right: var(--border-thickness) dashed var(--border-color);
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-5 {
    border-right: var(--border-thickness) dashed var(--border-color);
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-6 {
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-7 {
    border-right: var(--border-thickness) dashed var(--border-color);
}
 
.input-8 {
    border-right: var(--border-thickness) dashed var(--border-color);
}


info.css

CSS




.info {
    width: 30vw;
    display: flex;
    justify-content: space-evenly;
    align-items: center;
    color: whitesmoke;
}
 
.player {
    border: 2px solid #f6546a;
    border-radius: 5%;
    padding: .5rem 0;
    display: flex;
    font-size: 1.5rem;
    justify-content: center;
    align-items: center;
    width: 10vw;
}


Save all files and start the application by running the following command:

npm start

Output:

Whether you’re preparing for your first job interview or aiming to upskill in this ever-evolving tech landscape, neveropen Courses are your key to success. We provide top-quality content at affordable prices, all geared towards accelerating your growth in a time-bound manner. Join the millions we’ve already empowered, and we’re here to do the same for you. Don’t miss out – check it out now!

RELATED ARTICLES

Most Popular

Recent Comments