使用React实现井字棋小游戏
按照React官方教程来实现一个井字棋小游戏并完善其他功能,这里主要讲怎么完善这些功能
在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)。在历史记录列表中加粗显示当前选择的项目。使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)。添加一个可以升序或降序显示历史记录的按钮。每当有人获胜时,高亮显示连成一线的 3 颗棋子。当无人获胜时,显示一个平局的消息。 点击此处获取React官方井字棋小游戏源码———官方代码.点击此处获取已完善的井字棋的源码———已完善代码.
1.添加坐标功能
在state的history中添加coordinate
this.state
= {
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;
}
square
[i
] = this.state
.xIsNext
? "X" : "O";
let x
= Math
.floor(i
/ 3) + 1
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
]) {
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
;
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
;
winnerStep
= winner
.step
} else {
if(flag
){
status
= '平局'
}else{
status
= "下一位选手: " + (this.state
.xIsNext
? "X" : "O");
}
}
效果图: