Mo Baqeri
Mo Baqeri

Reputation: 333

React Tic Tac Toe Game

I'm trying to make a Tic Tac Toe game using React and I have done up to building elements but I cannot make the functionality of it how to make the click work and how to find the winner.

Could someone please help me with this how I can complete it?

This is the code I wrote:

import React, { useState } from "react";
import ReactDOM from "react-dom";

const rowStyle = {
  display: "flex"
};

const squareStyle = {
  width: "60px",
  height: "60px",
  backgroundColor: "#ddd",
  margin: "4px",
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
  fontSize: "20px",
  color: "white"
};

const boardStyle = {
  backgroundColor: "#eee",
  width: "208px",
  alignItems: "center",
  justifyContent: "center",
  display: "flex",
  flexDirection: "column",
  border: "3px #eee solid"
};

const containerStyle = {
  display: "flex",
  alignItems: "center",
  flexDirection: "column"
};

const instructionsStyle = {
  marginTop: "5px",
  marginBottom: "5px",
  fontWeight: "bold",
  fontSize: "16px"
};

const buttonStyle = {
  marginTop: "15px",
  marginBottom: "16px",
  width: "80px",
  height: "40px",
  backgroundColor: "#8acaca",
  color: "white",
  fontSize: "16px"
};

class Square extends React.Component {
  handleClick(index) {}

  render() {
    return (
      <div
        className="square"
        style={squareStyle}
        value={this.props.number}
        onClick={() => this.handleClick(this.props.number)}
      ></div>
    );
  }
}

class Board extends React.Component {
  render() {
    return (
      <div style={containerStyle} className="gameBoard">
        <div className="status" style={instructionsStyle}>
          Next player: X
        </div>
        <div className="winner" style={instructionsStyle}>
          Winner: None
        </div>
        <button style={buttonStyle}>Reset</button>
        <div style={boardStyle}>
          <div className="board-row" style={rowStyle}>
            <Square number={1} />
            <Square number={2} />
            <Square number={3} />
          </div>
          <div className="board-row" style={rowStyle}>
            <Square number={4} />
            <Square number={5} />
            <Square number={6} />
          </div>
          <div className="board-row" style={rowStyle}>
            <Square number={7} />
            <Square number={8} />
            <Square number={9} />
          </div>
        </div>
      </div>
    );
  }
}

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
      </div>
    );
  }
}

ReactDOM.render(<Game />, document.getElementById("root"));

My Code on Codesandbox: https://codesandbox.io/s/serene-ganguly-t39mu

Upvotes: 0

Views: 2125

Answers (2)

Yilmaz
Yilmaz

Reputation: 49190

You just wrote the UI components but asking for implementation :) That is a long discussion and you need to have good knowledge of data structures and algorithms and also minimax theory. Since I am still unemployed I will write the basics to give a road map of what to do for future reference.

When you start the app, you have to write a logic whose turn or you can always let the computer or "bot" start first. You store the turn variable:

const [turn, setTurn] = useState<"HUMAN" | "BOT">(Math.random() < 0.5 ? "HUMAN" : "BOT");

Let's say it is bot's turn. Now "bot" will be maximizer (min-max algorithm is a huge topic that you need to learn), it will try to make the possible move. In tic-tac-toe game, best move will be the center or corners. So you write code to make sure 'bot' insert its value in those indices: [0, 2, 6, 8, 4]; Here is the simple code to decide the first move for 'bot'.

 const centerAndCorners = [0, 2, 6, 8, 4];
                    const firstMove =
                        centerAndCorners[Math.floor(Math.random() * centerAndCorners.length)];

Then 'bot' inserts the first value and state gets updated. this happens inside useEffect() before component gets mounted.

const [state, setState] = useState<BoardState>([
        null,null,null,
        null,null,null,
        null,null,null
    ])

This is the function to insert the symbol:

const insertCell = (cell: number, symbol: "x" | "o"): void => {
        const stateCopy: BoardState = [...state];
        stateCopy[cell] = symbol;
        setState(stateCopy);
    };

Now mutating the state will re-render the component. So inside useEffect, you add some logic to see if the game is over. If the game is over, run some code. But how are you going to check if the game is over? That will include a lot of logic and it will be a long function. Let's say game is not over, then you need to update the state of board and turn. And also set the state that it is human's turn to maximizing. I think it will be easier to write useEffect:

useEffect(() => {
        if (gameResult) {
            alert("done");
        } else {
            if (turn === "BOT") {
                // If the board is empty
                if (isEmpty(state)) {
                    // this is the best move for the start
                    const centerAndCorners = [0, 2, 6, 8, 4];
                    const firstMove =
                        centerAndCorners[Math.floor(Math.random() * centerAndCorners.length)];
                    insertCell(firstMove, "x");
                    setIsHumanMaximizing(false);
                    setTurn("HUMAN");
                } else {
                    const best = getBestMove(state, !isHumanMaximizing, 0, -1);
                    insertCell(best, isHumanMaximizing ? "o" : "x");
                    setTurn("HUMAN");
                }
            }
        }

    }, [state, turn]);

inside useEffect there are two functions: isEmpty and getBestMove. isEmpty is easy:

export const isEmpty = (state: BoardState): boolean => {
    return state.every(cell => cell === null);
};

But getBestMove() function is a crazy function. It basically calculates the best move for the 'bot' if it is maximizer or minimizer. The computer will run through the board, see which cells are empty, and recursively calculate the best move for each move. It will recursively calculate the best move after next move the end.

enter image description here

It is like a tree data structure. So this is why data structures and algorithms are so important. Because these recursive calls require too much calculations and they will take time. So you need to figure out which data structure to use inorder to get the fastest response and how to manipulate this data structure.

Each step on the image shows the depth of the call. If you do not set the maximum depth, the computer will always win. This is where how the difficulty of the game changes. If you set the maxDepth=2, after two recursive call, 'bot' will make insertion based on results of those two recursive calls.

Upvotes: 1

Tarik
Tarik

Reputation: 11209

LOL. That's the game a miserably failed to program back in 1982 when I was learning to program on my own.

In order to do this, you need to learn about recursion and use the minimax algorithm.

See https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-3-tic-tac-toe-ai-finding-optimal-move/

I do not advise you to copy blindly. You have to take time understanding what's going on.

Upvotes: 0

Related Questions