【问题标题】:React Tic Tac Toe Game反应井字游戏
【发布时间】:2021-03-22 02:21:06
【问题描述】:

我正在尝试使用 React 制作井字游戏,并且我已经完成了构建元素,但我无法使其功能如何使点击工作以及如何找到获胜者。

有人可以帮我解决这个问题吗?

这是我写的代码:

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"));

我在 Codesandbox 上的代码: https://codesandbox.io/s/serene-ganguly-t39mu

【问题讨论】:

标签: reactjs tic-tac-toe


【解决方案1】:

您刚刚编写了 UI 组件,但要求实现 :) 这是一个漫长的讨论,您需要对数据结构和算法以及极小极大理论有很好的了解。由于我仍然处于失业状态,因此我将编写基础知识以提供路线图以供将来参考。

当你启动应用程序时,你必须编写一个逻辑,轮到你或者你总是可以让计算机或“机器人”先启动。你存储转变量:

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

假设轮到机器人了。现在“bot”将成为最大化器(min-max 算法是一个你需要学习的巨大主题),它将尝试做出可能的举动。在井字游戏中,最好的移动将是中心或角落。因此,您编写代码以确保 'bot' 将其值插入这些索引中: [0, 2, 6, 8, 4]; 这是决定“机器人”的第一步的简单代码。

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

然后'bot'插入第一个值并更新状态。这发生在组件安装之前的 useEffect() 中。

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

这是插入符号的函数:

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

现在改变状态将重新渲染组件。所以在 useEffect 里面,你添加一些逻辑来查看游戏是否结束。如果游戏结束,运行一些代码。但是你将如何检查游戏是否结束?这将包括很多逻辑,这将是一个很长的功能。假设游戏还没有结束,那么你需要更新棋盘和转弯的状态。并且还设置了轮到人类最大化的状态。我认为写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]);

useEffect里面有两个函数:isEmpty和getBestMove。 isEmpty 很简单:

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

但是 getBestMove() 函数是一个疯狂的函数。如果它是最大化器或最小化器,它基本上会计算“机器人”的最佳移动。计算机将在棋盘上运行,查看哪些单元格是空的,并递归计算每一步的最佳移动。下一次移动结束后递归计算最佳移动。

它就像一个树数据结构。这就是为什么数据结构和算法如此重要的原因。因为这些递归调用需要太多的计算,而且需要时间。因此,您需要弄清楚使用哪种数据结构以获得最快的响应以及如何操作该数据结构。

图像上的每个步骤都显示了调用的深度。如果不设置最大深度,电脑总是赢。这就是游戏难度变化的地方。如果设置 maxDepth=2,则在两次递归调用后,'bot' 将根据这两次递归调用的结果进行插入。

【讨论】:

    【解决方案2】:

    哈哈。这是 1982 年我自学编程时不幸编程失败的游戏。

    为此,您需要了解递归并使用极小极大算法。

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

    我不建议你盲目复制。您必须花时间了解正在发生的事情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-12
      • 2015-01-08
      • 1970-01-01
      • 1970-01-01
      • 2014-04-12
      相关资源
      最近更新 更多