React 教程:井字棋游戏

React 教程:井字棋游戏

使用 React 实现一个交互式的井字棋游戏,并配上好看的样式

// 导入必要的CSS样式和React库
import "./App.css";
import { useState } from "react";

// Square组件 - 表示棋盘上的一个格子
function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

// App组件 - 游戏的主组件
export default function App() {
  // 使用useState钩子管理游戏历史状态
  // 初始历史是一个包含9个null的数组(表示空棋盘)
  const [history, setHistory] = useState([Array(9).fill(null)]);

  // 当前步骤的索引(表示当前显示的是哪一步的棋盘状态)
  const [currentMove, setCurrentMove] = useState(0);

  // 判断当前玩家是否是"X"(❌)
  // 根据当前步骤的奇偶性决定玩家顺序
  const xIsNext = currentMove % 2 === 0;

  // 获取当前棋盘的方格状态
  const currentSquares = history[currentMove];

  // 处理玩家落子
  function handlePlay(nextSquares) {
    // 创建新的历史记录:
    // 1. 复制从第一步到当前步骤的历史
    // 2. 添加新的棋盘状态
    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];

    // 更新历史状态
    setHistory(nextHistory);

    // 将当前步骤设置为最新的一步
    setCurrentMove(nextHistory.length - 1);
  }

  // 跳转到指定步骤
  function jumpTo(nextMove) {
    setCurrentMove(nextMove);
  }

  // 生成历史步骤列表
  const moves = history.map((squares, move) => {
    let description;

    // 如果是第一步,显示"重新开始游戏",否则显示"转到第X步"
    description = move > 0 ? `转到第 ${move}` : "重新开始游戏";

    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );
  });

  // 渲染游戏界面
  return (
    <div className="game">
      {/* 游戏棋盘区域 */}
      <div className="game-board">
        {/* Board组件 - 渲染当前棋盘状态 */}
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>

      {/* 游戏信息区域(历史记录) */}
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

// Board组件 - 游戏棋盘
function Board({ xIsNext, squares, onPlay }) {
  // 处理格子点击事件
  function handleClick(i) {
    // 如果该格子已有内容或已有赢家,则不做任何处理
    if (squares[i] || calculateWinner(squares)) return;

    // 创建新的棋盘状态副本(React状态不可变原则)
    const nextSquares = squares.slice();

    // 根据当前玩家设置格子的内容
    nextSquares[i] = xIsNext ? "❌" : "⭕";

    // 调用父组件传递的onPlay函数更新游戏状态
    onPlay(nextSquares);
  }

  // 计算当前是否有赢家
  const winner = calculateWinner(squares);

  // 设置状态文本
  let status;
  if (winner) {
    status = "获胜者: " + winner;
  } else {
    status = "下一位玩家: " + (xIsNext ? "❌" : "⭕");
  }

  // 渲染棋盘
  return (
    <>
      {/* 显示游戏状态(赢家或下一步玩家) */}
      <div className="status">{status}</div>

      {/* 棋盘容器 */}
      <div
        style={{
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
          padding: "20px",
        }}
      >
        {/* 棋盘主体 */}
        <div className="calculator-board">
          {/* 第一行 */}
          <div className="board-row">
            <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
            <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
            <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
          </div>

          {/* 第二行 */}
          <div className="board-row">
            <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
            <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
            <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
          </div>

          {/* 第三行 */}
          <div className="board-row">
            <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
            <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
            <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
          </div>
        </div>
      </div>
    </>
  );
}

// 计算赢家函数
function calculateWinner(squares) {
  // 所有可能的获胜线(水平、垂直、对角线)
  const lines = [
    [0, 1, 2], // 第一行
    [3, 4, 5], // 第二行
    [6, 7, 8], // 第三行
    [0, 3, 6], // 第一列
    [1, 4, 7], // 第二列
    [2, 5, 8], // 第三列
    [0, 4, 8], // 主对角线
    [2, 4, 6], // 副对角线
  ];

  // 检查所有可能的获胜线
  for (let i = 0; i < lines.length; i++) {
    // 解构当前线的三个位置
    const [a, b, c] = lines[i];

    // 检查三个格子是否相同且不为空
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      // 返回赢家("❌"或"⭕")
      return squares[a];
    }
  }

  // 没有赢家则返回null
  return null;
}

// 游戏结束注释
#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

/* 九宫格容器 */
.calculator-board {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background-color: #2c3e50;
  padding: 20px;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}

/* 按钮行 */
.board-row {
  display: flex;
  gap: 8px;
}

/* 按钮样式 */
.square {
  width: 80px;
  height: 80px;
  background-color: #3498db;
  border: none;
  border-radius: 8px;
  font-size: 36px;
  color: white;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  transition: all 0.2s ease;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

/* 按钮悬停效果 */
.square:hover {
  background-color: #2980b9;
  transform: translateY(-3px);
  box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
}

/* 按钮点击效果 */
.square:active {
  transform: translateY(1px);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.game-info ol {
  list-style-type: none; /* 移除默认的序列编号 */
  padding-left: 0; /* 移除默认的左内边距 */
  margin: 0; /* 移除默认的外边距 */
}

.game-info button {
  background: #f0f0f0;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  width: 240px;
  text-align: left;
  transition: background 0.2s;
}

.game-info button:hover {
  background: #e0e0e0;
}

.game-info button.active {
  background: #3498db;
  color: white;
  font-weight: bold;
}

.game {
  display: flex;
  flex-direction: row;
  align-self: center;
  justify-self: center;
  gap: 20px; /* 设置两个div之间的间距 */
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值