React + Ts 实现三子棋小游戏

2023-11-13

在这里阅读效果更佳

还记得当年和同桌在草稿纸上下三子棋的时光吗

今天我们就用代码来重温一下年少(假设你有react基础,没有也行,只要你会三大框架的任意一种,上手react不难)

游戏规则

  • 双方各执一子,在九宫格内一方三子连成线则游戏结束
  • 九宫格下满未有三子连线则视为平局

你可以点击这里来体验最终效果,样式有点丑,请见谅

准备阶段

建议先全局安装typescript 和 create-react-app(安装过请忽略

npm install typescript create-react-app -g

使用typescript初始化项目

create-react-app demo --typescript

初始化成功后ts环境已经配好了,不需要你手动加ts配置
此时就是tsx语法,我们就可以愉快的写ts了
src文件夹就是开发目录,所有代码都写在src文件夹下
我们使用sass来写样式,先安装sass

npm install node-sass --save

运行项目

npm run start

删掉初始化界面的一些代码

开发阶段

组件化

开发一个项目其实就是开发组件
把一个项目拆分一个个小组件,方便后期维护以及复用

  1. 棋子组件
  2. 棋盘组件
  3. 游戏规则组件
  4. 游戏状态组件

react中组件分为类组件和函数组件
需要管理状态的最好使用类组件
所以我们先把App改成类组件

import React from 'react';
import './App.css';
class App extends React.Component{
    render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined {
        return (
            <div className="App">
            </div>
        );
    }
};

export default App;

开发棋子组件

在src下新建component文件夹,在component文件夹下新建ChessComp.tsx,ChessComp.css
以后我们的组件都放在component文件夹下
棋子组件我们使用函数组件,思考需要传入组件的属性的类型:

  1. type(棋子的类型)
  2. onClick(点击棋子触发的回调函数)

棋子类型有三种(红子 ,黑子, 空),
为了约束棋子类型,我们使用一个枚举类型,
在src下新建types文件夹,专门放类型约束,
在types下新建enums.ts约束棋子类型

export enum ChessType {
    none,
    red,
    black
}

并在棋子tsx中导入
传入tsx的所有属性用一个IProps接口约束

interface IProps {
    type: ChessType
    onClick?: () => void
}

全部tsx代码:

import React from 'react';
import {ChessType} from "../types/enums";
import './ChessComp.css';

interface IProps {
    type: ChessType
    onClick?: () => void
}
function ChessComp ({type, onClick}: IProps) {
    let chess = null;
    switch (type) {
        case ChessType.red:
            chess = <div className="red chess-item"></div>;
        break;
        case ChessType.black:
            chess = <div className="black chess-item"></div>;
        break;
        default:
            chess = null;
    }
    return (
        <div className="chess" onClick={() => {
            if (type === ChessType.none && onClick) {
                onClick();
            }
        }}>
            {chess}
        </div>
    )
};
export default ChessComp;

其中棋子只有为none类型时才能被点击
scss 代码:
棋子我们用背景颜色径向渐变来模拟

$borderColor: #dddddd;
$redChess: #ff4400;
$blackChess: #282c34;
.chess{
    display: flex;
    justify-content: center;
    align-items: center;
    width: 50px;
    height: 50px;
    border: 2px solid $borderColor;
    box-sizing: border-box;
    cursor: pointer;
    .chess-item{
        width: 30px;
        height: 30px;
        border-radius: 50%;
    }
    .red{
        background: radial-gradient(#fff, $redChess);
    }
    .black{
        background: radial-gradient(#fff, $blackChess);
    }
}

开发棋盘组件

同理在component文件夹下新建BoardComp.tsx,BoardComp.scss
棋盘组件我们需要传递三个参数:

  1. 棋子的数组
  2. 游戏是否结束
  3. 点击事件函数

循环数组渲染棋子, 并给游戏是否结束一个默认值
全部tsx代码:

import React from 'react';
import {ChessType} from "../types/enums";
import ChessComp from "./ChessComp";
import "./BoardComp.scss";
interface IProps {
    chesses: ChessType[];
    isGameOver?: boolean
    onClick?: (index: number) => void
}
const BoardComp: React.FC<IProps> = function(props) {
    // 类型断言
    const isGameOver = props.isGameOver as boolean;
    // 非空断言
    // const isGameOver = props.isGameOver!;
    const list = props.chesses.map((type, index) => {
        return (
            <ChessComp
                type={type}
                key={index}
            onClick={() => {
                if (props.onClick && !isGameOver) {
                    props.onClick(index)
                }
            }}/>
        )
    });
    return (
        <div className="board">
            {list}
        </div>
    )
};
BoardComp.defaultProps = {
  isGameOver: false
};
export default BoardComp;

scss 代码:
使用flex布局

.board{
    display: flex;
    flex-wrap: wrap;
    width: 150px;
    height: 150px;
}

开发游戏规则组件

在component文件夹下新建Game.tsx,Game.scss
游戏规则组件不需要传参,我们使用类组件来管理状态
在types文件夹下的enums.ts里新增游戏状态的枚举类型

export enum ChessType {
    none,
    red,
    black
}
export enum GameStatus {
    /**
     * 游戏中
     */
    gaming,
    /**
     * 红方胜利
     */
    redWin,
    /**
     * 黑方胜利
     */
    blackWin,
    /**
     * 平局
     */
    equal,
}

核心的代码就是如何判断游戏的状态,我的方法有点死,你们可以自己重构,

import React from 'react';
import {ChessType, GameStatus} from "../types/enums";
import BoardComp from "./BoardComp";
import GameStatusComp from "./GameStatusComp";
import './Game.scss';

/**
 * 棋子的数组
 * 游戏状态
 * 下一次下棋的类型
 */
interface Istate {
    chesses: ChessType[],
    gameStatus: GameStatus,
    nextChess: ChessType.red | ChessType.black
}
class Game extends React.Component<{}, Istate> {
    state: Istate = {
        chesses: [],
        gameStatus: GameStatus.gaming,
        nextChess: ChessType.black
    };

    /**
     * 组件挂载完初始化
     */
    componentDidMount(): void {
        this.init();
    }
    /**
     * 初始化9宫格
     */
    init() {
        const arr: ChessType[] = [];
        for (let i = 0; i < 9; i ++) {
            arr.push(ChessType.none)
        }
        this.setState({
            chesses: arr,
            gameStatus: GameStatus.gaming,
            nextChess: ChessType.black
        })
    }

    /**
     * 处理点击事件,改变棋子状态和游戏状态
     */
    handleChessClick(index: number) {
        const chesses: ChessType[] = [...this.state.chesses];
        chesses[index] = this.state.nextChess;
        this.setState(preState => ({
            chesses,
            nextChess: preState.nextChess === ChessType.black? ChessType.red : ChessType.black,
            gameStatus: this.getStatus(chesses, index)
        }))
    }

    /**
     * 获取游戏状态
     */
    getStatus(chesses: ChessType[], index: number): GameStatus {
        // 判断是否有一方胜利
        const horMin = Math.floor(index/3) * 3;
        const verMin = index % 3;
        // 横向, 纵向, 斜向胜利
        if ((chesses[horMin] === chesses[horMin + 1] && chesses[horMin + 1] === chesses[horMin + 2]) ||
            (chesses[verMin] === chesses[verMin + 3] && chesses[verMin + 3] === chesses[verMin + 6]) ||
            (chesses[0] === chesses[4] && chesses[4] === chesses[8] && chesses[0] !== ChessType.none) ||
            ((chesses[2] === chesses[4] && chesses[4] === chesses[6] && chesses[2] !== ChessType.none))) {
            return chesses[index] === ChessType.black ? GameStatus.blackWin : GameStatus.redWin;
        }
        // 平局
        if (!chesses.includes(ChessType.none)) {
            return GameStatus.equal;
        }
        // 游戏中
        return GameStatus.gaming;
    }
    render(): React.ReactNode {
        return <div className="game">
            <h1>三子棋游戏</h1>
            <GameStatusComp next={this.state.nextChess} status={this.state.gameStatus}/>
            <BoardComp
                chesses={this.state.chesses}
                isGameOver={this.state.gameStatus !== GameStatus.gaming}
                onClick={this.handleChessClick.bind(this)}/>
                <button onClick={() => {
                this.init()}
                }>重新开始</button>
        </div>;
    }
}

export default Game;

样式

.game{
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-around;
  top: 100px;
  width: 250px;
  height: 400px;
  left: 50%;
  transform: translateX(-50%);
}

开发显示游戏状态的组件

这个组件用来显示状态,在component文件夹下新建GameStatus.tsx,GameStatus.scss
没什么好说的,直接上代码

import React from 'react';
import {ChessType, GameStatus} from "../types/enums";
import './GameStatus.scss';
interface Iprops {
    status: GameStatus
    next: ChessType.red | ChessType.black
}
function GameStatusComp(props: Iprops) {
    let content: JSX.Element;
    if (props.status === GameStatus.gaming) {
        if (props.next === ChessType.red) {
            content = <div className="next red">红方落子</div>
        } else {
            content = <div className="next black">黑方落子</div>
        }
    } else {
        if (props.status === GameStatus.redWin) {
            content = <div className="win red">红方胜利</div>
        } else if (props.status === GameStatus.blackWin) {
            content = <div className="win black">黑方胜利</div>
        } else {
            content = <div className="win equal">平局</div>
        }
    }
    return (
        <div className="status">
            {content}
        </div>
    )
}

export default GameStatusComp;
.status {
  width: 150px;
  .next,.win{
    font-size: 18px;
  }
  .win{
    border: 2px solid;
    border-radius: 5px;
    width: 100%;
    padding: 10px 0;
  }
  .equal{
    background-color: antiquewhite;
  }
  .red{
    color: #ff4400;
  }
  .black{
    color: #282c34;
  }
}

收尾

最后在app.tsx里调用game组件

import React from 'react';
import './App.scss';
import Game from "./component/Game";

class App extends React.Component{
    render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined {
        return (
            <div className="App">
                <Game/>
            </div>
        );
    }
};

export default App;

转载于:https://www.cnblogs.com/LHLVS/p/11511673.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React + Ts 实现三子棋小游戏 的相关文章

随机推荐

  • layer关闭弹窗,刷新父页面

    获取index不要写在函数里 可能获取不到index var index parent layer getFrameIndex window name function closeFram window parent refreshPage
  • React 函数组件

    1 React 组件 函数式组件 1 React提供了两种创建组件方式 1 1 函数式组件 1 2 类组件 执行了ReactDOM render
  • ctrl c和 ctrl v 突然失灵不能使用的解决方法大全

    1 你是否正在运行 有道词典 那就把它关了 这也是困扰我许久的问题 2 打开你的输入法 看看快捷键是否有冲突 很大原因可能是这个 3 打开C盘 gt Windows gt System32 看看这个文件夹中是否有clip的文件夹 如果没有就
  • 由于找不到 MSVCR120.dll,无法继续执行代码解决方法

    问题描述 下载某C端软件并启动 提示 由于找不到 MSVCR120 dll 无法继续执行代码解决方法 在其它电脑尝试打开 可以正常打开 定位到确是环境问题 非下载的程序包问题 解决方法 方法一 在网上下载 msvcr120 dll 文件 并
  • 论文阅读之 Diffusion Models Beat GANs on Image Synthesis

    扩散首次打败gan 来源 OpenAI 无条件图像合成 条件图像合成 背景 目标函数和参数化方式的来源 Alex Nichol and Prafulla Dhariwal Improved denoising diffusion proba
  • 哪些字段适合建索引

    转自 https blog csdn net maliao1123 article details 52431144 经常需要进行更新操作的属性 1 表的主键 外键必须有索引 2 数据量超过300的表应该有索引 3 经常与其他表进行连接的表
  • QT+PMAC联合开发

    最近接触到PMAC的IMAC FX控制器 虽然他的主程序是写在下位机 但还是需要上位机和它进行通信和交互 官方提供的上位机DEMO都是基于VC C 和VB等 对于QT的用户 没有很直接的代码供参考 PMAC提供了COM组件 供上位机调用 在
  • Day 7

  • 【硬核】 ROS Navigation 局部路径规划常见算法

    简介 最近 作者参加了关于RMUS 高校 SimReal挑战赛 首次接触到了机器人导航领域 这里记录一下这段时间的收货 sim2real的全称是simulation to reality 是强化学习的一个分支 同时也属于transfer l
  • Windows上编译最新的CURL,含有zlib,openssl

    最近 从网上下载了一个curl库 使用时各种报错 都无法启动 于是干脆就直接自己编译了 1 准备工作 a 下载zlib zlib可以使得HTTP请求支持gzip压缩 其地址如下 官网 http zlib net 下载地址 http zlib
  • Unmet dependencies. Try ‘apt --fix-broken install‘ with no packages (or specify a solution).

    问题 Unmet dependencies Try apt fix broken install with no packages or specify a solution sudo apt get upgrade Reading pac
  • Java ffmpeg 工具类封住

    package com zzg ffmpeg import java io IOException import java io InputStream import java util ArrayList import java util
  • bp神经网络预测未来数据,bp神经网络如何预测

    bp神经网络算法后的结果看不懂 都是英文 预测值怎么弄出来 高手指导一下 p test out sim net p test 训练结束后用验证样本检验网络的预测效果 是验证数据的输入 模型会自动给出预测输出值 将预测值与真实值对比看看模型是
  • ZooKeeper(一):基础介绍

    文章目录 什么是 ZooKeeper ZooKeeper 发展历史 ZooKeeper 应用场景 ZooKeeper 服务的使用 ZooKeeper 数据模型 data tree 接口 znode 分类 总结 什么是 ZooKeeper Z
  • 机器学习-Sklearn-11(支持向量机SVM-SVC真实数据案例:预测明天是否会下雨)

    机器学习 Sklearn 11 支持向量机SVM SVC真实数据案例 预测明天是否会下雨 11 SVC真实数据案例 预测明天是否会下雨 这个案例的核心目的 是通过巧妙的预处理和特征工程来向大家展示 在现实数据集上我们往往如何做数据预处理 或
  • Java中ASCII码与字符之间的相互转换

    ASCII码转字符 char word char 97 强制转换 字符转ASCII码 int value a 加
  • STM32 进阶教程 17 - ADC注入通道

    前言 STM32 的ADC的一个强大功能是支持触发注入功能 在103中每个ADC模块支持4个注入通道 每个注入通道具有独立的结果突存器 注入通道具有较规划通道更高的优先级 在实际工程应用中 注入通道更多地会使用外部触发方式进行触发转换 关于
  • 时序预测相关技术分享

    时序预测相关技术分享 时序预测是指对时间序列数据进行预测 以预测未来的趋势或行为 在实际生产和应用中 时序预测广泛应用于金融 电力 交通等领域 时序预测可以帮助人们更好地理解和掌握未来的趋势和规律 从而做出更明智的决策 时序预测技术的方法和
  • 两个虚拟机互相拷贝文件或者文件夹

    互相拷贝文件 scp root k8s images tar bz2 root 192 168 33 16 root 将mysql配置文件上传到16服务器相应文件夹内 scp root 192 168 33 16 etc mysql my
  • React + Ts 实现三子棋小游戏

    在这里阅读效果更佳 还记得当年和同桌在草稿纸上下三子棋的时光吗 今天我们就用代码来重温一下年少 假设你有react基础 没有也行 只要你会三大框架的任意一种 上手react不难 游戏规则 双方各执一子 在九宫格内一方三子连成线则游戏结束 九