井字棋游戏,也叫圈叉棋,英文:Tic-Tac-Toe
规则:
- 3×3 的九宫格棋盘。
- 一方画“○”,一方画“×”。
- 轮流在空格中画自己的符号。
- 先在横、竖、或对角线上连成一条直线的玩家获胜。
- 如果格子填满且无人连成一线,则为平局。
用一个 9 个元素的数组表示棋盘
const [board, setBoard] = useState(Array(9).fill(null));
用一个 boolean 表示轮到 X 还是 O
const [xIsNext, setXIsNext] = useState(true);
总共只有 8 种胜利方法,分别是三横,三竖,两个斜线。枚举 8 种胜利情况判断是否胜利
const winPatterns = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
];
const winner = winPatterns.some(([a, b, c]) =>
board[a] && board[a] === board[b] && board[a] === board[c]
);
如果没有胜利且棋盘满了,则为平局
const isDraw = !winner && board.every(cell => cell !== null);
实现下棋
const handleClick = (index) => {
if (board[index] || winner || isDraw) return;
const newBoard = board.map((cell, i) =>
i === index ? (xIsNext ? 'X' : 'O') : cell
);
setBoard(newBoard);
setXIsNext(!xIsNext);
};
完整 React Component
import React, { useState } from 'react';
export default function TicTacToe() {
const [board, setBoard] = useState(Array(9).fill(null));
const [xIsNext, setXIsNext] = useState(true);
const winPatterns = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
];
const winner = winPatterns.some(([a, b, c]) =>
board[a] && board[a] === board[b] && board[a] === board[c]
);
const isDraw = !winner && board.every(cell => cell !== null);
const handleClick = (index) => {
if (board[index] || winner || isDraw) return;
const newBoard = board.map((cell, i) =>
i === index ? (xIsNext ? 'X' : 'O') : cell
);
setBoard(newBoard);
setXIsNext(!xIsNext);
};
const resetGame = () => {
setBoard(Array(9).fill(null));
setXIsNext(true);
};
const getStatus = () => {
if (winner) return `Player ${xIsNext ? 'O' : 'X'} Wins!`;
if (isDraw) return "It's a Draw!";
return `Current: Player ${xIsNext ? 'X' : 'O'}`;
};
const getStatusStyle = () => {
if (winner) return { ...styles.status, color: '#16a34a' };
if (isDraw) return { ...styles.status, color: '#f59e0b' };
return styles.status;
};
const getCellBorders = (index) => {
const row = Math.floor(index / 3);
const col = index % 3;
return {
borderTop: row > 0 ? '2px solid #e2e8f0' : 'none',
borderBottom: row < 2 ? '2px solid #e2e8f0' : 'none',
borderLeft: col > 0 ? '2px solid #e2e8f0' : 'none',
borderRight: col < 2 ? '2px solid #e2e8f0' : 'none',
};
};
return (
<div style={styles.container}>
<h2 style={styles.title}>Tic-Tac-Toe</h2>
<div style={getStatusStyle()}>
{getStatus()}
</div>
<div style={styles.board}>
{board.map((cell, index) => (
<button
key={index}
style={{
...styles.cell,
...getCellBorders(index),
...(cell === 'X' ? styles.cellX : {}),
...(cell === 'O' ? styles.cellO : {}),
}}
onClick={() => handleClick(index)}
disabled={!!cell || winner || isDraw}
>
{cell}
</button>
))}
</div>
{(winner || isDraw) && (
<button style={styles.newGameButton} onClick={resetGame}>
New Game
</button>
)}
</div>
);
}
const styles = {
container: {
maxWidth: '400px',
margin: '20px auto',
padding: '20px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
title: {
fontSize: '28px',
fontWeight: '700',
color: '#1e40af',
margin: '0 0 20px 0',
},
status: {
fontSize: '20px',
fontWeight: '700',
color: '#1e293b',
marginBottom: '20px',
},
board: {
display: 'grid',
gridTemplateColumns: 'repeat(3, 80px)',
gridTemplateRows: 'repeat(3, 80px)',
gap: '0',
margin: '0 auto 20px',
},
cell: {
width: '80px',
height: '80px',
fontSize: '36px',
fontWeight: '700',
borderRadius: '0',
background: '#fff',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.2s ease',
padding: '0',
boxSizing: 'border-box',
},
cellX: {
color: '#3b82f6',
background: '#eff6ff',
},
cellO: {
color: '#ef4444',
background: '#fef2f2',
},
newGameButton: {
padding: '12px 32px',
fontSize: '16px',
fontWeight: '700',
color: '#fff',
background: 'linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%)',
border: 'none',
borderRadius: '10px',
cursor: 'pointer',
boxShadow: '0 4px 14px rgba(59, 130, 246, 0.4)',
},
};