React中井字棋游戏的实现示例

目录
  • 需求分析
  • 实现分析
    • 涉及的组件
    • 涉及的状态
  • 编码实现
    • 项目初始化
    • 定义各个组件的props/state
      • Square组件props
      • Board组件props
      • Game组件state
    • 各组件代码
      • Square
      • Board
      • Game

最近开始接触React,我认为读官方文档是最快上手一门技术的途径了,恰好React的官方文档中有这样一个井字棋游戏demo,学习完后能够快速上手React,这是我学习该demo的总结

需求分析

首先看看这个游戏都有哪些需求吧

  • 游戏玩家:XO,每次落棋后需要切换到下一个玩家
  • 赢家判断:什么情况下会诞生赢家,如何进行判断?
  • 禁止落棋的时机:游戏已有赢家 or 棋盘上已有棋子时
  • 时间旅行:能够展示游戏下棋历史,点击可跳转回相应的棋局

实现分析

首先声明一下,我不会像官方文档那样一步步从底层实现,然后逐步状态提升至父组件的方式讲解,而是直接从全局分析,分析涉及哪些状态,应当由哪个组件管理以及这样做的原因是什么

涉及的组件

先来思考一下整个游戏会涉及什么组件:

  • 首先最基本的,打开游戏最能吸引目光的,就是棋盘了,所以肯定得有一个棋盘组件Board
  • 棋盘有多个格子,因此还能将棋盘分割成多个格子组件Square
  • 还需要有一个游戏界面去控制游戏的UI以及游戏的逻辑,所以要有一个Game组件

涉及的状态

  • 棋盘中的每个格子的棋子是什么,比如是X还是O
  • 下一步是哪个玩家
  • 棋盘的历史记录,每下一步棋都要保存整个棋盘的状态
  • 棋盘历史记录指针,控制当前的棋盘是历史记录中的哪个时候的棋盘

我们可以自顶向下分析,最顶层的状态肯定是历史记录,因为它里面保存着每一步的棋盘,而棋盘本应该作为Board组件的状态的,但又由于有多个变动的棋盘(用户点击历史记录切换棋盘时),所以不适合作为state放到Board组件中,而应当作为props,由父组件Game去控制当前展示的棋盘

而棋盘中的格子又是在棋盘中的,所以也导致本应该由棋盘格子Square组件管理的格子内容状态提升至Game组件管理,存放在历史记录的每个棋盘对象中,所以Square的棋盘内容也应当以props的形式存在

下一步轮到哪个玩家是视棋盘的情况而定的,所以我认为应当放到历史记录的棋盘对象里和棋盘一起进行管理,官方那种放到Gamestate中而不是放到历史记录的每个棋盘中的做法我觉得不太合适

有了以上的分析,我们就可以开始写我们的井字棋游戏了!

编码实现

项目初始化

首先使用vite创建一个react项目

pnpm create vite react-tic-tac-toe --template react-ts
cd react-tic-tac-toe
pnpm i
code .

这里我使用vscode进行开发,当然,你也可以使用别的ide(如NeovimWebStorm

定义各个组件的props/state

由于使用的是ts进行开发,所以我们可以在真正写代码前先明确一下每个组件的propsstate,一方面能够让自己理清一下各个组件的关系,另一方面也可以为之后编写代码提供一个良好的类型提示

Square组件props

每个棋盘格中需要放棋子,这里我使用字符XO充当棋子,当然,棋盘上也可以不放棋子,所以设置一个squareContent属性

点击每个格子就是落棋操作,也就是要填充一个字符到格子中,根据前面的分析我们知道,填充的逻辑应当交由棋盘Board组件处理,所以再添加一个onFillSquareprop,它起到一个类似事件通知的作用,当调用这个函数的时候,会调用父组件传入的函数,起到一个通知的作用

所以Square组件的props接口定义如下:

interface Props {
  squareContent: string | null;
  fillSquare: () => void;
}

Board组件props

棋盘中要管理多个格子,所以肯定要有一个squares状态,用于控制各个格子

棋盘填充棋子的逻辑也应当交给Game组件去完成,因为要维护历史记录,而棋盘的状态都是保存在历史记录中的,所以填充棋子也要作为Board组件的一个prop

还要在棋盘上显示下一个玩家以及在对局结束时显示赢家信息,所以要有一个statusMsgprop显示对局信息,以及nextPlayer记录下一个玩家

最终Board组件的props接口定义如下:

interface Props {
  squares: Squares;
  statusMsg: string;
  nextPlayer: Player;
  fillSquare: (squareIdx: number) => void;
}

Game组件state

要记录历史信息,以及通过历史记录下标获取到对应历史记录的棋盘,所以它的State如下

interface State {
  history: BoardPropsNeeded[];
  historyIdx: number;
}

各组件代码

Square

export interface Props {
  squareContent: string | null;
  fillSquare: () => void;
}

export type Squares = Omit<Props, "fillSquare">[];

export default function Square(props: Props) {
  return (
    <div className="square" onClick={() => props.fillSquare()}>
      {props.squareContent}
    </div>
  );
}

Board

import React from "react";
import Square from "./Square";
import type { Squares } from "./Square";

export type Player = "X" | "O";

export interface Props {
  squares: Squares;
  statusMsg: string;
  nextPlayer: Player;
  fillSquare: (squareIdx: number) => void;
}

export default class Board extends React.Component<Props> {
  renderSquare(squareIdx: number) {
    const { squareContent } = this.props.squares[squareIdx];

    return (
      <Square
        squareContent={squareContent}
        fillSquare={() => this.props.fillSquare(squareIdx)}
      />
    );
  }

  render(): React.ReactNode {
    return (
      <div>
        <h1 className="board-status-msg">{this.props.statusMsg}</h1>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

Game

import React from "react";
import Board from "./Board";
import type { Props as BoardProps, Player } from "./Board";
import type { Squares } from "./Square";

type BoardPropsNeeded = Omit<BoardProps, "fillSquare">;

interface State {
  history: BoardPropsNeeded[];
  historyIdx: number;
}

export default class Game extends React.Component<any, State> {
  constructor(props: any) {
    super(props);

    this.state = {
      history: [
        {
          squares: new Array(9).fill({ squareContent: null }),
          nextPlayer: "X",
          statusMsg: "Next player: X",
        },
      ],
      historyIdx: 0,
    };
  }

  togglePlayer(): Player {
    const currentBoard = this.state.history[this.state.historyIdx];
    return currentBoard.nextPlayer === "X" ? "O" : "X";
  }

  fillSquare(squareIdx: number) {
    const history = this.state.history.slice(0, this.state.historyIdx + 1);
    const currentBoard = history[this.state.historyIdx];
    // 先判断一下对局是否结束 结束的话就不能继续落棋
    // 当前格子有棋子的话也不能落棋
    if (
      calcWinner(currentBoard.squares) ||
      currentBoard.squares[squareIdx].squareContent !== null
    )
      return;

    const squares = currentBoard.squares.slice();
    squares[squareIdx].squareContent = currentBoard.nextPlayer;
    this.setState({
      history: history.concat([
        {
          squares,
          statusMsg: currentBoard.statusMsg,
          nextPlayer: this.togglePlayer(),
        },
      ]),
      historyIdx: history.length,
    });
  }

  jumpTo(historyIdx: number) {
    this.setState({
      historyIdx,
    });
  }

  render(): React.ReactNode {
    const history = this.state.history;
    const currentBoard = history[this.state.historyIdx];
    const { nextPlayer } = currentBoard;
    const winner = calcWinner(currentBoard.squares);
    let boardStatusMsg: string;

    if (winner !== null) {
      boardStatusMsg = `Winner is ${winner}!`;
    } else {
      boardStatusMsg = `Next player: ${nextPlayer}`;
    }

    const historyItems = history.map((_, idx) => {
      const desc = idx ? `Go to #${idx}` : `Go to game start`;
      return (
        <li key={idx}>
          <button className="history-item" onClick={() => this.jumpTo(idx)}>
            {desc}
          </button>
        </li>
      );
    });

    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={currentBoard.squares}
            statusMsg={boardStatusMsg}
            nextPlayer={nextPlayer}
            fillSquare={(squareIdx: number) => this.fillSquare(squareIdx)}
          />
        </div>
        <div className="divider"></div>
        <div className="game-info">
          <h1>History</h1>
          <ol>{historyItems}</ol>
        </div>
      </div>
    );
  }
}

const calcWinner = (squares: Squares): Player | null => {
  // 赢的时候的棋局情况
  const winnerCase = [
    [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 < winnerCase.length; i++) {
    const [a, b, c] = winnerCase[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a].squareContent as Player;
    }
  }

  return null;
};

到此这篇关于React中井字棋游戏的实现示例的文章就介绍到这了,更多相关React 井字棋游戏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 用React Native制作一个简单的游戏引擎

    简介 今天我们将学习如何使用React Native制作一个游戏.因为我们使用的是React Native,这个游戏将是跨平台的,这意味着你可以在Android.iOS和网络上玩同一个游戏.然而,今天我们将只关注移动设备.所以我们开始吧. 开始吧 要制作任何游戏,我们需要一个循环,在我们玩的时候更新我们的游戏.这个循环被优化以顺利运行游戏,为此我们将使用 React Native游戏引擎 . 首先让我们用以下命令创建一个新的React Native应用. npx react-native ini

  • Three.js+React实现3D开放世界小游戏

    目录 背景 效果 设计 实现 加载资源 页面结构 数据初始化 场景初始化 创建世界 创建星空 创建地形 加载进度管理 创建基地模型 创建阿狸模型 控制阿狸运动 动画更新 页面缩放适配 添加游戏逻辑 毛玻璃效果 总结 背景 2545光年之外的开普勒1028星系,有一颗色彩斑斓的宜居星球 ,星际移民必须穿戴基地发放的防辐射服才能生存.阿狸驾驶星际飞行器降临此地,快帮它在限定时间内使用轮盘移动找到基地获取防辐射服吧! 本文使用 Three.js + React + CANNON 技术栈,实现通过滑动屏

  • React中井字棋游戏的实现示例

    目录 需求分析 实现分析 涉及的组件 涉及的状态 编码实现 项目初始化 定义各个组件的props/state Square组件props Board组件props Game组件state 各组件代码 Square Board Game 最近开始接触React,我认为读官方文档是最快上手一门技术的途径了,恰好React的官方文档中有这样一个井字棋游戏的demo,学习完后能够快速上手React,这是我学习该demo的总结 需求分析 首先看看这个游戏都有哪些需求吧 游戏玩家:X和O,每次落棋后需要切换

  • C++实现井字棋游戏

    本文实例为大家分享了C++实现井字棋游戏的具体代码,供大家参考,具体内容如下 初步实现双玩家输入,操作游戏. 下一步将实现人机博弈. #include<iostream> using namespace std; void Player1(void); //玩家1输入(操作)函数 void Player2(void); //玩家2输入(操作)函数 void game_judge(void); //输赢判断 void game_start(void); //游戏开始 int rows = 3,c

  • vue实现井字棋游戏

    本文实例为大家分享了vue实现井字棋游戏的具体代码,供大家参考,具体内容如下 之前看react的教程时看到的小游戏,试着用vue做一个.右边的winner提示胜者,还没有胜者时提示下一个棋子的种类.restart按钮点击可重新开始.go to step可跳转到第n步. html: <div id="app"> <ul id="board" class="white normal"> <li class="s

  • python实现简单的井字棋游戏(gui界面)

    项目输出 项目先决条件 要使用python构建井字游戏,我们需要tkinter模块和python的基本概念 Tkinter模块是用于渲染图形的标准图形用户界面. Tkinter.messagebox用于显示消息框 要安装tkinter模块,我们在命令提示符下使用了pip install命令: pip install tkinter 项目文件结构 这些是使用python构建井字游戏的步骤: 导入模块 初始化窗口 检查结果的功能 检查获胜者的功能 定义标签和按钮 1.导入模块 from tkinte

  • C语言实现井字棋游戏

    本文实例为大家分享了C语言实现井字棋游戏的具体代码,供大家参考,具体内容如下 首先,我们需要一个大体的思路,先进行宏观规划,再对细节进行实现. 比如: 1.首先需要一个菜单面板作以修饰,在这个面板上,玩家可以选择进入游戏或者退出游戏. 2.需要一个游戏程序,这个是核心. 差不多就是这两个了,我们可以先把这个写下来,这样也可以方便后面使用,像这样: void Game(); int Menu();//这里Menu之所以用int,是为了用返回值来确定是否退出游戏,并非唯一,也非最佳,读者自己尝试 为

  • C语言实现简易井字棋游戏

    井子棋承载了每个人孩童时的美好时光,小到书本.纸张,大到课桌.墙壁,总能找到井字棋盘的痕迹.今天我们就来实际操作一番,用C语言完成一个简单的井字棋游戏,让我们一起重温美好. 棋盘如下: **功能描述:**棋盘共分为九个格子,一方执"O"为棋,一方执"X"为棋,双方依次选择格子.己方棋子率先连成三子的获胜,若棋盘占满仍未分胜负,则打成平局. 具体功能实现: 1.在页面选择玩家vs玩家,或玩家vs电脑 2.玩家下棋时,输入对应格子的坐标 3.电脑下棋时,使用随机值选择坐

  • 基于C语言实现井字棋游戏

    井字棋游戏要求在3乘3棋盘上,每行都相同或者每列都相同再或者对角线相同,则胜出.因此我们可以使用一个二维数组来表示棋盘,判断胜负只需要判断数组元素是否相同即可.具体我们可以分为以下几步来做: 1.创建维数组并进行初始化,如果仅仅是一个二维数组来表示棋盘,看起来不是很清楚,因此我们可以对棋盘边框用符号打印出来进行优化一下: //初始化棋盘 void init(char board[max_row][max_col]) { for (int row = 0; row < max_row; row++

  • JavaScript通过极大极小值算法实现AI井字棋游戏

    话不多说直接上运行截图: 黑棋是玩家的位置,红色方是电脑.电脑会根据当前棋盘的情况选择一个对自己有利却对玩家不利的情况. 算法可以实现电脑胜利,或者电脑和玩家平局. 代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>井字棋AI</title> <style> .title { text-align: center; } .

  • C语言二维数组应用之井字棋游戏

    本文实例为大家分享了C语言实现井字棋游戏的具体代码,供大家参考,具体内容如下 数组是C语言中一种重要的数据类型,接下来我和大家分享用二维数组完成一个井字棋游戏. 井字棋,是一种在3*3格子上进行的连珠游戏,和五子棋类似.游戏需要的工具仅为纸和笔,然后由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X),任意三个标记形成一条直线,则为获胜. 井字棋的规则想必大家都已非常清楚,下面来简单梳理一下完成这个游戏的主要思路 一.变量的定义 1.首先要定义棋盘变量为一个3*3的二维数组 

  • python实现井字棋游戏

    本文实例介绍了python实现井字棋游戏的方法,分享给大家,具体内容如下 windows7下python3.4.0编译运行通过.由于采用了cmd调用,所以与Linux不兼容,无法在Linux下运行. 游戏就是井字棋,小键盘上的数字位置对应棋盘位置. #本游戏python3.4.0下编写调试,只能在windows下运行. import random import subprocess import time #定义函数 def draw_board(the_board): subprocess.c

随机推荐