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>{/* 棋盘容器 */}<divstyle={{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];}}// 没有赢家则返回nullreturn 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之间的间距 */
}