🚀🚀 使用react+ts实现井字棋小游戏

使用React + TS 实现井字棋小游戏

在阅读react官网文档时,看到有个井字棋实例,感兴趣的小伙伴也可以看看官网的小例子: 官网井字棋小游戏

效果图

Aug-15-2023 15-39-55.gif

创建项目

使用命令新建一个react + ts模板项目

create-react-app react-ts-chess-game --template typescript

分析

通过展示组件 + 容器组件模式,把井字棋小游戏分为四个组件,棋子组件、棋盘组件、游戏状态展示组件、游戏容器

image.png

展示组件:通常是函数式组件

容器组件:通常是类组件

定义枚举类型

export enum ChessType{ 
  null,
  red,
  black
}

export enum GameStatus { 
  /**
   * 游戏进行中
   */
  gaming,

  /**
   * 红方胜利
   */
  redWin,

  /**
   * 黑方胜利
   */
  blackWin,

  /**
   * 平局
   */
  deuce
}

棋子组件的开发

/**
 * 旗子组件
 */
interface IProps { 
  type: ChessType
  onClick?: () => void 
}

export const ChessComp = (props: IProps) => { 
  let content: JSX.Element
  if (props.type === ChessType.red) {
    content = <div className="chess-item red" ></div>
  }
  else if (props.type === ChessType.black) {
    content = <div className="chess-item black" ></div>
  }
  else { 
    content = <div className="chess-item" ></div>
  }
  
  return (
    <div className="chess" onClick={() => { 
      if (props.type === ChessType.null && props.onClick) { 
        props.onClick()
      }
    }}
    >
      {content}
    </div>
  )
}

棋盘组件开发

/**
 * 棋盘组件
 */
interface IProps { 
  chesses: ChessType[],
  onClick?: (index: number) => void
  isGameOver?: boolean
}
export const BoardComp: React.FC<IProps> = (props) => {
  // const isGameOver = props.isGameOver as Boolean
  const isGameOver = props.isGameOver!
  const list = props.chesses.map((type, i) =>  
    <ChessComp
      type={type}
      key={i}
      onClick={() => { 
        if (props.onClick && !isGameOver) { 
          props.onClick(i)
        }
      }}
    />
  )

  return (
    <div className="board">
      {list}
    </div>
  )
}

BoardComp.defaultProps = {
  isGameOver: false
}

需注意,在函数组件中给参数设置默认值2种处理方法:

  1. 在定义接收参数接口类型的时候,参数修改为可选参数,比如这里游戏是否结束isGameOver?: boolean
  2. 使用React.FC对函数组件进行类型约束
  3. 设置默认值
    BoardComp.defaultProps = { isGameOver: false }
    

在做完前面步骤的时候,我们移动鼠标到 props.onClick && !isGameOver TS检查到的类型为boolean | undefined 先让不符合我们这里的处理逻辑,怎么处理呢?

这里使用非空断言:在数据之后加上一个!,告诉TS,不用考虑该数据为空的情况或者也可使用类型断言,如下:

const isGameOver = props.isGameOver as Boolean
const isGameOver = props.isGameOver!

游戏容器组件开发

这里游戏的核心就是判断当前落子之后游戏的状态变化:

  1. 其中一方胜利
  2. 平局
  3. 游戏正在进行中

在此,我们需要寻找当前落子和整个棋盘的下标之间的联系,我们可分为横向、纵向、斜线上面的三种情况来解决

image.png

找到当前index与横向、纵向连线的棋盘下标最小值的关系即可

interface IState {
  chesses: ChessType[];
  nextChess: ChessType.black | ChessType.red;
  gameStatus: GameStatus
}

export class GameComp extends React.Component<{}, IState> {
  state: IState = {
    chesses: [],
    nextChess: ChessType.black,
    gameStatus: GameStatus.gaming
  };

  componentDidMount(): void {
    this.init()
  }

  /**
   * @description: 初始化棋盘数据
   * @return {*}
   */
  init() { 
    const chesses: ChessType[] = []
    for (let i = 0; i < 9; i++) { 
      chesses.push(ChessType.null)
    }

    this.setState({
      chesses,
      nextChess: ChessType.black,
      gameStatus: GameStatus.gaming
    })
  }

/**
 * @description: 点击单个旗子触发的事件
 * @return {*}
 */
  handelChessClick(index: number) { 
    const chesses = [...this.state.chesses]
    chesses[index] = this.state.nextChess

    this.setState(prevState => ({ 
      chesses,
      nextChess: prevState.nextChess === ChessType.red ? ChessType.black : ChessType.red,
      gameStatus: this.getGameStatua(index, chesses)
    }))
  }

  /**
   * @description: 游戏状态判断核心逻辑
   * @param {number} index
   * @param {ChessType} chesses
   * @return {*}
   */  
  getGameStatua(index: number, chesses: ChessType[]): GameStatus {
    // 1. 其中一方胜利
    const herMin = Math.floor(index / 3) * 3
    const verMin = index % 3
    if ((chesses[herMin] === chesses[herMin + 1] && chesses[herMin] === chesses[herMin + 2])
      || (chesses[verMin] === chesses[verMin + 3] && chesses[verMin] === chesses[verMin + 6])
      || (chesses[0] === chesses[4] && chesses[0] === chesses[8] && chesses[0] !== ChessType.null)
      || (chesses[2] === chesses[4] && chesses[2] === chesses[6] && chesses[2] !== ChessType.null)
    ) { 
      if (chesses[index] === ChessType.red) {
        return GameStatus.redWin
      }
      else { 
        return GameStatus.blackWin
      }
    }

    // 2. 平局
    if (!chesses.includes(ChessType.null)) return GameStatus.deuce

    // 3. 游戏正在进行中
    return GameStatus.gaming

  }

  render(): React.ReactNode {
    return (
      <div className="game">
        <ShowGameStatus
          nextChess={this.state.nextChess}
          gameStatus={this.state.gameStatus}
        />
        <BoardComp
          chesses={this.state.chesses}
          onClick={this.handelChessClick.bind(this)}
          isGameOver={this.state.gameStatus !==  GameStatus.gaming}
        />
        <button onClick={() => { 
          this.init()
        }}>重新开始</button>
      </div>
    );
  }
}

游戏展示函数组件开发

interface IProps { 
  nextChess: ChessType.black | ChessType.red
  gameStatus: GameStatus
}
export const ShowGameStatus = (props: IProps) => {
  let content: JSX.Element;
  if (props.gameStatus === GameStatus.gaming) {
    if (props.nextChess === ChessType.black) {
      content = <div className="black">黑子落棋</div>
    }
    else {
      content = <div className="red">红子落棋</div>
    }
  }
  else if (props.gameStatus === GameStatus.redWin) {
    content = <div className="win red">红方胜利</div>
  }
  else if (props.gameStatus === GameStatus.blackWin) {
    content = <div className="win black">黑方胜利</div>
  }
  else { 
    content = <div className="win deuce">平局</div>
  }

  return (
    <div className="status">
      {content}
    </div>
  )
 }

结语

到此,使用 react+ts 就完成了井字棋游戏开发

如果有小伙伴感兴趣可以看具体demo代码,喜欢的话一键三连🌹🌹🌹

全部评论

相关推荐

ALEX_BLX:虽然说聊天记录不可信,不过这个趋势确实如此但我觉得也要想到一点就是卷后端的人里真正有“料”的人又有多少,我说的这个料都不是说一定要到大佬那种级别,而是就一个正常的水平。即使是现在也有很多人是跟风转码的,2-3个月速成后端技术栈的人数不胜数,但今时不同往日没可能靠速成进大厂了。这种情况就跟考研一样,你能上考场就已经打败一半的人了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务