使用React实现井字棋小游戏
程序员文章站
2024-03-18 23:51:04
...
使用React实现井字棋小游戏
- 按照React官方教程来实现一个井字棋小游戏并完善其他功能,这里主要讲怎么完善这些功能
- 在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)。
- 在历史记录列表中加粗显示当前选择的项目。
- 使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)。
- 添加一个可以升序或降序显示历史记录的按钮。
- 每当有人获胜时,高亮显示连成一线的 3 颗棋子。
- 当无人获胜时,显示一个平局的消息。
- 点击此处获取React官方井字棋小游戏源码———官方代码.
- 点击此处获取已完善的井字棋的源码———已完善代码.
1.添加坐标功能
- 在state的history中添加coordinate
this.state = {
// square: Array(9).fill(null),
xIsNext: true,
history: [
{
square: Array(9).fill(null),
coordinate: 0
}
],
stepNumber: 0,
order: false
}
- 点击时根据index添加坐标信息
handleClick(i) {
let history = this.state.history.slice(0, this.state.stepNumber + 1);
let current = history[history.length - 1]
let square = current.square.slice();
//如果存在获胜者或此棋格已有值,则返回
if (calculateWinner(square) || square[i]) {
return;
}
//根据xIsNext判断值为X或0
square[i] = this.state.xIsNext ? "X" : "O";
//计算此步的x坐标
let x = Math.floor(i / 3) + 1
//计算此步的y坐标
let y = i % 3 + 1
//生成坐标信息
let coordinate = square[i] + '(' + x + ',' + y + ')'
this.setState({
history: history.concat([
{
square: square,
coordinate: coordinate
}
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext
});
}
- 渲染时渲染坐标
const moves = history.map((step, index) => {
const desc = index ?
'Go to move : ' + step.coordinate :
'Go to game start';
return (
<li key={index}>
<button onClick={() => this.jumpTo(index)} className='btn' {desc}</button>
</li>
);
});
效果图:
2.加粗显示当前选择的项目
- 根据stepNumber判断按钮是否对应当前项目,对应的话加入相应的样式
const moves = history.map((step, index) => {
const desc = index ?
'Go to move : ' + step.coordinate :
'Go to game start';
let light = {
color: 'rgb(158, 147, 147)'
};
if(index === this.state.stepNumber){
if(winner){
light={
color: 'red'
}
}else{
light={
color: '#000'
}
}
}
return (
<li key={index}>
<button onClick={() => this.jumpTo(index)} className='btn' style={light}>{desc}</button>
</li>
);
});
效果图:
3.使用两个循环来渲染出棋盘的格子
- 通过两个for循环来生成3X3表格
const SquareRow = []
for (let i = 0; i < 3; i++) {
let str = [];
//两重循环遍历出棋盘
for (let j = 0; j < 3; j++) {
let light = false;
if (this.props.winnnerStep.indexOf(i * 3 + j) != -1) {
light = true;
} else {
light = false;
}
str.push(
<Square
value={this.props.square[i * 3 + j]}
key={i * 3 + j}
onClick={() => this.props.handleClick(i * 3 + j)}
light={light}
/>
)
}
let square = <div className="board-row">{str}</div>
SquareRow.push(square)
}
let showSquare = SquareRow.map((item, index) => {
return <div key={index}>{item}</div>
})
return (
<div>
<div>
{showSquare}
</div>
<div>
<button onClick={() => this.props.reSetClick()} className='reset_btn'>重置</button>
</div>
</div>
);
4.添加一个可以升序或降序显示历史记录的按钮
- 添加一个state:order,(order为false时为正序,true时为倒序)
this.state = {
xIsNext: true,
history: [
{
square: Array(9).fill(null),
coordinate: 0
}
],
stepNumber: 0,
order: false
}
- 渲染按钮列表时判断order的值,并根据值渲染按钮列表和标题箭头
let order = this.state.order ? '▲' : '▼'
const moves = history.map((step, index) => {
const desc = index ?
'Go to move : ' + step.coordinate :
'Go to game start';
return (
<li key={index}>
<button onClick={() => this.jumpTo(index)} className='btn'>{desc}</button>
</li>
);
});
if (this.state.order) {
moves.reverse()
}
return (
<div className="game">
<div className="game-board">
<Board
square={current.square}
handleClick={this.handleClick.bind(this)}
winnnerStep={winnerStep}
reSetClick={this.reSetClick.bind(this)}
/>
</div>
<div className="game-info">
<div onClick={this.changeOrder.bind(this)}>
<div className='status'>
{status}
</div>
<div className='order'>
{order}
</div>
</div>
<ul>{moves}</ul>
</div>
</div>
);
效果图:
5.每当有人获胜时,高亮显示连成一线的 3 颗棋子
- 获胜时获取3连棋子的位置(step)
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]) {
//返回获胜的value和三连棋子的位置
return { user: squares[a], step: [a, b, c] };
}
}
return null;
}
- 将step传给子组件
let history = this.state.history;
let current = history[this.state.stepNumber];
let winner = calculateWinner(current.square);
//初始化获胜者获胜步的位置数组
let winnerStep = []
if (winner) {
status = '获胜者:' + winner.user;
//将连成一线的 3 颗棋子的位置放入数组
winnerStep = winner.step
}
<Board
square={current.square}
handleClick={this.handleClick.bind(this)}
winnnerStep={winnerStep}
reSetClick={this.reSetClick.bind(this)}
/>
- 在循环渲染棋盘时判断当前棋子是否在3连棋子数组内
if (this.props.winnnerStep.indexOf(i * 3 + j) != -1) {
light = true;
} else {
light = false;
}
str.push(
<Square
value={this.props.square[i * 3 + j]}
key={i * 3 + j}
onClick={() => this.props.handleClick(i * 3 + j)}
light={light}
/>
)
效果图:
6.当无人获胜时,显示一个平局的消息
- 渲染时判断棋盘是否已满,如果已满则将status改为平局
//设置标量标记棋盘是否已满
let flag = true;
//遍历判断棋盘是否已满
for (let i = 0; i < current.square.length; i++) {
if (current.square[i] === null) {
flag = false;
}
}
//初始化状态信息(显示对局情况)
let status;
if (winner) {
status = '获胜者:' + winner.user;
//将连成一线的 3 颗棋子的位置放入数组
winnerStep = winner.step
} else {
if(flag){
status = '平局'
}else{
status = "下一位选手: " + (this.state.xIsNext ? "X" : "O");
}
}
效果图: