返回

React井字棋游戏改进列表实现

前端

改进列表

1. 用枚举代替字符串表示方格状态

原先,我们使用字符串"X"和"O"来表示方格的状态。这很容易出错,因为我们可能会不小心拼错字符串,或者忘记在字符串前面加上引号。为了避免这种情况,我们可以使用枚举来表示方格的状态。

enum SquareState {
  EMPTY,
  X,
  O,
}

这样,我们就不会再犯上述错误了。

2. 创建自定义组件

原先,我们将游戏逻辑和UI代码都放在一个组件中。这使得代码很难维护和扩展。为了解决这个问题,我们可以创建自定义组件来封装游戏逻辑和UI代码。

const Square = ({ value, onClick }) => {
  return (
    <button className="square" onClick={onClick}>
      {value}
    </button>
  );
};

const Board = ({ squares, onClick }) => {
  return (
    <div className="board">
      {squares.map((row, i) => (
        <div key={i} className="row">
          {row.map((square, j) => (
            <Square key={j} value={square} onClick={() => onClick(i, j)} />
          ))}
        </div>
      ))}
    </div>
  );
};

这样,我们的代码就更加模块化和可重用了。

3. 使用React Context

原先,我们将游戏状态存储在组件的state中。这使得我们很难在不同的组件之间共享游戏状态。为了解决这个问题,我们可以使用React Context来共享游戏状态。

const GameContext = React.createContext();

const GameProvider = ({ children }) => {
  const [squares, setSquares] = useState(Array(9).fill(SquareState.EMPTY));
  const [currentPlayer, setCurrentPlayer] = useState(SquareState.X);

  return (
    <GameContext.Provider value={{ squares, setSquares, currentPlayer, setCurrentPlayer }}>
      {children}
    </GameContext.Provider>
  );
};

这样,我们就可以在任何组件中使用游戏状态了。

4. 添加AI对手

原先,游戏只能由两个人类玩家进行。为了让游戏更具挑战性,我们可以添加一个AI对手。

const AI = () => {
  const { squares, setSquares, currentPlayer } = useContext(GameContext);

  const bestMove = minimax(squares, currentPlayer);

  useEffect(() => {
    if (currentPlayer === SquareState.O) {
      setTimeout(() => {
        setSquares((prevSquares) => {
          const newSquares = [...prevSquares];
          newSquares[bestMove] = SquareState.O;
          return newSquares;
        });
      }, 1000);
    }
  }, [currentPlayer]);

  return null;
};

这样,我们的游戏就拥有了一个AI对手了。

5. 提供游戏历史记录和回放功能

原先,游戏没有提供游戏历史记录和回放功能。这使得我们很难回顾游戏的过程,并从中学习。为了解决这个问题,我们可以提供游戏历史记录和回放功能。

const GameHistory = () => {
  const { squares, setSquares } = useContext(GameContext);

  const [history, setHistory] = useState([]);
  const [currentIndex, setCurrentIndex] = useState(0);

  useEffect(() => {
    setHistory((prevHistory) => {
      const newHistory = [...prevHistory];
      newHistory.push(squares);
      return newHistory;
    });
  }, [squares]);

  const handleStepClick = (index) => {
    setCurrentIndex(index);
    setSquares(history[index]);
  };

  return (
    <div className="game-history">
      <ul>
        {history.map((square, index) => (
          <li key={index}>
            <button onClick={() => handleStepClick(index)}>
              {index + 1}
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

这样,我们的游戏就拥有了游戏历史记录和回放功能了。

6. 使用持久化存储

原先,游戏的状态只存储在内存中。这使得游戏无法在刷新页面后继续进行。为了解决这个问题,我们可以使用持久化存储来存储游戏的状态。

const useLocalStorage = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};

const Game = () => {
  const [squares, setSquares] = useLocalStorage("squares", Array(9).fill(SquareState.EMPTY));
  const [currentPlayer, setCurrentPlayer] = useLocalStorage("currentPlayer", SquareState.X);

  return (
    <GameProvider value={{ squares, setSquares, currentPlayer, setCurrentPlayer }}>
      <div className="game">
        <Board squares={squares} onClick={(i, j) => handleClick(i, j)} />
        <GameHistory />
      </div>
    </GameProvider>
  );
};

这样,我们的游戏就拥有了持久化存储功能了。

结语

通过这些改进,React井字棋游戏变得更加健壮、易于维护、功能更加完善。这些改进不仅可以帮助我们学习React,还可以帮助我们学习如何构建一个完整的Web应用程序。