1. 在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)。
game.state.history中不仅需要记录棋盘,还需要记录此步落子的坐标
class Game extends React.Component {
// 修改Game构造函数中的history
constructor(props) {
super(props)
this.state = {
history: [{
squares: Array(9).fill(null),
stepIndex: null, // 增加这一行
}],
xIsNext: true,
stepNumber: 0,
}
}
// 点击的时候记录此步落子的坐标
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares,
stepIndex: i, // 增加这一行
}]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext,
})
}
}
之后写一个函数式组件,用来展示棋子的坐标
function StepSpan(props){
const stepIndex = props.stepIndex
if(stepIndex !== null){
return (
<span>row:{Math.floor(stepIndex/3) + 1}|col:{stepIndex % 3 + 1}</span>
);
}
else{
return null
}
}
再修改Game的render方法中的moves
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
<StepSpan stepIndex={step.stepIndex} ></StepSpan> // 增加这行
</li>
);
});
2.在历史记录列表中加粗显示当前选择的项目。
这个只需要修改修改Game的render方法中的moves就行了
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
// 增加button的条件渲染
let button;
if (move === this.state.stepNumber) {
button = (<button onClick={() => this.jumpTo(move)}><b>{desc}</b></button>)
} else {
button = (<button onClick={() => this.jumpTo(move)}>{desc}</button>)
}
return (
<li key={move}>
{button}
<StepSpan stepIndex={step.stepIndex} ></StepSpan>
</li>
);
});
3.使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)。
修改class Board 的render方法,改采用循环的方式,这样就不需要renderSquare方法了
render() {
const squares = [];
for (let i = 0; i < 3; i++) {
let rows = [];
for (let j = 0; j < 3; j++) {
let key = i * 3 + j
rows.push(
<Square
key={key}
value={this.props.squares[key]}
onClick={() => { this.props.onClick(key) }}
/>
);
}
squares.push(
<div
key={i}
className="board-row"
>
{rows}
</div>
);
}
return (
<div>
{squares}
</div>
);
}
4.添加一个可以升序或降序显示历史记录的按钮。
这个在game的state里增加一个标记,来确认是升序排序还是降序排序
如果是降序那就把moves reverse
class Game extends React.Component {
constructor(props) {
super(props)
this.state = {
history: [{
squares: Array(9).fill(null),
stepIndex: null,
}],
xIsNext: true,
stepNumber: 0,
asc: true, // 默认升序排序
}
}
/* ... */
changeAsc(){
this.setState({
asc: !this.state.asc
})
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
let moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
let button;
if (move === this.state.stepNumber) {
button = (<button onClick={() => this.jumpTo(move)}><b>{desc}</b></button>)
} else {
button = (<button onClick={() => this.jumpTo(move)}>{desc}</button>)
}
return (
<li key={move}>
{button}
<StepSpan stepIndex={step.stepIndex} ></StepSpan>
</li>
);
});
if(!this.state.asc){
moves.reverse();
}
/* ... */
}
5.每当有人获胜时,高亮显示连成一线的 3 颗棋子。
需要改造calculateWinner方法,要能知道是哪些棋子连成一条线
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 {
winner: squares[a],
line: [a, b, c],
};
}
}
return null;
}
再同时改造Game Borad Square 三个组件
class Game extends React.Component {
render() {
/* ... */
let status;
let winLine; // 胜利线
if (winner) {
status = 'Winner: ' + winner.winner;
winLine = winner.line;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
winLine = null;
}
return (
<div className="game">
<div className="game-board">
<Board
winLine={winLine}
squares={current.squares}
onClick={(i) => { this.handleClick(i) }}
/>
</div>
<div className="game-info">
<button onClick={()=>{this.changeAsc()}}>reverse</button>
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
class Board extends React.Component {
render() {
const winLine = this.props.winLine;
const squares = [];
for (let i = 0; i < 3; i++) {
let rows = [];
for (let j = 0; j < 3; j++) {
let key = i * 3 + j
let win=false;
if(winLine && winLine.includes(key)){
win=true
}
rows.push(
<Square
win={win}
key={key}
value={this.props.squares[key]}
onClick={() => { this.props.onClick(key) }}
/>
);
}
squares.push(
<div
key={i}
className="board-row"
>
{rows}
</div>
);
}
return (
<div>
{squares}
</div>
);
}
}
function Square(props) {
return (
<button
style={{color: props.win? 'red':'black' }}
className={"square"}
onClick={props.onClick}
>
{props.value}
</button>
);
}
6.当无人获胜时,显示一个平局的消息。
当棋盘满了,而且没有胜者就是平局
写一个方法判断棋盘是不是满了
function isSquaresFull(squares){
for (let i = 0; i < 9; i++) {
if(squares[i]===null) return false
}
return true
}
再在修改Game组件
class Game extends React.Component {
/* ... */
let status;
let winLine;
if (winner) {
status = 'Winner: ' + winner.winner;
winLine = winner.line;
} else {
if(isSquaresFull(current.squares)){
status = 'No Winner!';
}else{
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
winLine = null;
}
/* ... */
}
TODO
- 在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)。
- 在历史记录列表中加粗显示当前选择的项目。
- 使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)。
- 添加一个可以升序或降序显示历史记录的按钮。
- 每当有人获胜时,高亮显示连成一线的 3 颗棋子。
- 当无人获胜时,显示一个平局的消息。