¿Cómo construir un juego de Tic-Tac-Toe usando React Hooks?

React es una biblioteca JavaScript de código abierto de frontend, que se utiliza para crear interfaces de usuario interactivas. React se centra en aplicaciones de una sola página y se conoce más popularmente como SPA. En este tutorial, usaremos React y sus ganchos para crear una divertida aplicación Tic-Tac-Toe. Antes de saltar al código, asegúrese de verificar los requisitos previos para una mejor comprensión.

Requisito previo:

Módulos requeridos:

  • npm
  • Reaccionar

Creación de la aplicación React y configuración: 

Paso 1: Comenzará un nuevo proyecto usando create-react-app, así que abra su terminal y escriba.

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

Paso 2: cambia a la carpeta tres en raya usando el siguiente comando.

cd tic-tac-toe-react

Paso 3: cambie a la carpeta src y elimine las cosas innecesarias con el siguiente comando

cd src
rm *

Paso 4: Cree una carpeta css en src, que contenga los archivos app.css, board.css, index.css e info.css.

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

Paso 5: en la carpeta src , cree archivos App.js, Board.js, index.js e Info.js.

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

Estructura del proyecto: La estructura de archivos en el proyecto se verá así.

Ejemplo: este ejemplo lo guiará con el código para crear un juego de Tic-Tac-Toe usando React Hooks.

index.js: Este fHTML

index.js

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: este archivo actúa como un archivo base que contiene los componentes Info y Board. Edite el archivo App.js de la siguiente manera:

App.js

// 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: este archivo contiene el tablero de tres en raya y la lógica del juego. Edite Board.js de la siguiente manera:

Board.js

// 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: este archivo contiene información sobre el juego de tres en raya. Edite Info.js de la siguiente manera:

Info.js

// 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

*{
    -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

@import url(
'https://fonts.googleapis.com/css2?family=Bellefair&display=swap');
  
.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

: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

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

Guarde todos los archivos e inicie la aplicación ejecutando el siguiente comando:

npm start

Producción:

Publicación traducida automáticamente

Artículo escrito por tejsidda34 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *