React井字棋游戏+练习

一、代码部分

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组件后,进行判断添加样式即可
在这里插入图片描述
最后贴一张结果图:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值