文章目录
一、代码部分
App.js
import React, { Component } from 'react'
import './App.css';
const checkGame = function (game) {
var arr = [
[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 < arr.length; i++) {
let [index_1, index_2, index_3] = arr[i];
if (game[index_1] != null && game[index_1] === game[index_2] && game[index_2] === game[index_3]) {
return arr[i]
}
}
return false;
}
// console.log(checkGame(
// ["X","X","X",null,"O","O",null,null,null]
// ))
//历史面板
class History extends Component {
constructor(props){
super(props)
this.state={
order:false
}
}
reOrder(){
this.setState({
order:!this.state.order
})
}
render() {
// console.log(this.props)
const {winner, xIsNext ,jump,nowStep,game} = this.props;
let {history}=this.props
let tittle = '';
console.log('hhh',history)
let {order}=this.state;
if (winner&&history.length<=10) {
tittle = `Winner is ${game[winner[0]]}`
}
else if(!winner&&history.length==10){
tittle="Have no winner"
}
else {
if (xIsNext) {
tittle = `The next one is 'X'`
}
else {
tittle = `The next one is 'O'`
}
}
return (
<div className='history'>
<h2>{tittle}</h2>
<hr />
<button onClick={()=>this.reOrder()} style={{fontSize:'18px'}}>Reverse</button>
<ol>
{
history.map((value,key)=>{
let index=order==true?key:history.length-key-1
console.log(index)
return(
<li key={index}>
<button className='btn' onClick={()=>{jump(index)}} style={nowStep===index?{color:'green'}:{}}>
Jump to #{`${index==0?'start':index}`}
</button>
</li>
)
})
}
</ol>
</div>
)
}
}
//九个格子
class Square extends Component {
render() {
var {isHasI,value,handleClick}=this.props
return (
<div className='box'
onClick={handleClick}
style={isHasI==true?{color:'red'}:{}}
>{value}</div>
)
}
}
//游戏面板
class Board extends Component {
getSquare(i) {
const { game, handleClick ,winner } = this.props
let isHasI=false
if(winner){
let set=new Set(winner);
isHasI=set.has(i)
}
return (
<Square
value={game[i]}
isHasI={isHasI}
handleClick={() => handleClick(i)}
></Square>
)
}
render() {
return (
<div className='box-wrap'>
{this.getSquare(0)}
{this.getSquare(1)}
{this.getSquare(2)}
{this.getSquare(3)}
{this.getSquare(4)}
{this.getSquare(5)}
{this.getSquare(6)}
{this.getSquare(7)}
{this.getSquare(8)}
</div>
)
}
}
//整体容器
class Game extends Component {
constructor() {
super()
this.state = {
// game: Array(9).fill(null),
history: [Array(9).fill(null)],
xIsNext: true,
nowStep:0,
}
}
jump(i){
var xIsNext=i%2==0?true:false;
// nowStep=j;
this.setState({
xIsNext:xIsNext,
nowStep:i,
history:this.state.history.slice(0,i+1)
})
console.log(xIsNext,i)
}
handleClick(i) {
// console.log(i)
const { xIsNext, history ,nowStep } = this.state
console.log(history)
let game = history[nowStep].slice()
if (checkGame(game)) {
return
}
if (game[i] == null) {
if (xIsNext) {
game[i] = "X";
}
else {
game[i] = "O";
}
this.setState({
xIsNext: !this.state.xIsNext,
history:history.concat([game]),
nowStep:nowStep+1
});
// console.log(checkGame(game))
}
}
render() {
const { history, xIsNext ,nowStep } = this.state;
let game = history[nowStep]
return (
<div className='game'>
<Board game={game} handleClick={(e) => this.handleClick(e)} winner={checkGame(game)}></Board>
<History nowStep={nowStep}
jump={(e)=>{this.jump(e)}}
game={game}
history={history}
xIsNext={xIsNext}
winner={checkGame(game)}></History>
</div>
)
}
}
class App extends Component {
render() {
return (
<div>
<Game></Game>
</div>
)
}
}
export default App;
App.css
.box{
width: 100px;
height: 100px;
border: 2px dashed rgb(128, 123, 123);
float: left;
box-sizing: border-box;
font-size: 56px;
font-weight: 600;
text-align: center;
line-height: 100px;
}
.box-wrap{
height: 300px;
width: 300px;
border: 6px solid white;
/* box-sizing: border-box; */
float:left;
}
.history{
float:left;
padding-left: 20px;
box-sizing: border-box;
}
.game{
width: 602px;
}
.btn{
width: 200px;
height: 30px;
background-color: white;
font-size: 18px;
line-height: 10px;
margin-bottom: 10px;
}
二、简介(主要部分)
主要组件
游戏界面
(分左边游戏区域和右边历史区域)
格子布局
使用一维数组,在Board组件中使用9个Square组件,使用浮动+限宽控制显示三行三列(也可以使用二维数组)基本样式在css文件,不做过多介绍
判赢思路
穷举每种可能,一共8种。然后遍历判断,判断时记得注意已经点击过的格子,不能在点击,预处理判空(对应函数checkGame)。
点击效果+渲染
定义了一个handleClick函数,添加点击效果,维护一个状态xIsNext,用来判断下一位是不是X,维护一个game数组,即界面的九个格子,也是用来渲染格子。此处函数传参i即点击格子的位置,即数组下标,通过这里可以改变game的值,然后通过props传递给子组件Square,渲染格子内容,每个Square格子设置如下(点击事件+value显示):
其父组件Board传递props如下:
handleClick方法主要代码如下(使用唯一能改变state的setState方法处理;这里checkGame为判赢函数,这里如果已经找到赢家了,直接return即再点击失效):
状态提升
此时组件及其维护的状态如下:
所以选择将Board组件维护的状态提升至Game组件(具体看代码,主要修改参数的传递等)
History面板
1.下一步和结果显示
这里就是根据父组件传参xIsNext状态和checkGame()函数结果判断,这里需要注意如果是平局,也要进行判断(判断长度和结果即可)
这里的参数winner(调用checkGame的结果):
2.历史记录显示与跳转
这里选择数组形式存储每一步下完之后的game数组,
即维护一个这样的数组:
每次game求出后,使用concat方法复制到数组中,理想状况是:
但实际:
这里发现所有记录都一样,
原因:(详细链接参考)
数组的复制涉及到了深拷贝和浅拷贝(关于两者区别)简单来讲,就是concat是浅拷贝,浅拷贝改变其中一个数组,另外一个数组也会跟着改变。
解决:
加slice,slice 方法会返回一个新的 Array 对象,这个新的数组与原数组是不同的地址,所以即使game改变也影响不到history数组,所以取game时:
有了history历史记录数组,我们就可以对步数进行显示和跳转了:
步数显示
遍历history数组,使用es6字符串形式显示步数:
步数跳转
维护一个nowStep状态和一个jump(i)函数,history遍历时每次生成一个button按钮,每个按钮挂载onClick事件,箭头函数形式调用传来的jump方法,(map中的key相当于遍历的指针),jump函数需要改变三个状态:
1)xIsNext,即下一步是什么,根据奇偶数判断即可;
2)nowStep,当前步数,即根据当前步数的改变(因为新加了nowStep状态,后期history的改变也要改成nowStep+1)而跳转;
3)history,nowStep改变,使用slice截取前i+1项即可
最后整个项目大致完成
三、官方推荐练习
这里只做2、4、5
#2.高亮显示
只需Game组件传一个nowStep参数,然后Button挂载一个style即可
#4.正/逆序按钮
在History组件中维护一个order状态,在history数组遍历时判断,定义一个index用来表示遍历的下标,如果order==true则正序遍历,index=key即可,相反index=history.length-key-1,即反向遍历并渲染即为倒序。
渲染一个button按钮,维护一个reOrder函数对order取反,button挂载onClick方法,触发时使用箭头函数调用reOrder函数即可。
#5.高亮显示结果
获取结果的函数在checkGame()中,现在只需改变返回值即可,即将结果下标返回,如下:
结果的下标在game数组中就对应胜利的人即game[winner[0]]
再将winner传递给Board面板:
再在生成Square函数中将winner结果定义成set集合,调has方法判断参数i(即小方块下标)是否在结果集中,传参至Square中处理(要预先判断winner是否是存在的,有可能是false)
传递给Square组件后,进行判断添加样式即可
最后贴一张结果图: