使用【码上掘金】基于React和Ant Design开发一个井字棋小游戏

989 阅读6分钟

我正在参加掘金社区游戏创意投稿大赛团队赛,详情请看:游戏创意投稿大赛
我正在参加 码上掘金体验活动,详情:show出你的创意代码块

先上一下效果

1650719207(1).png
在线预览地址:code.juejin.cn/pen/7087442…

前言

最近在使用【码上掘金】的时候发现能引入第三方js库,在开发的区域中也有切换React开发语言的选项。我就突发奇想的试了一下使用cdn引入React和AntDesign,没想到真的可以使用。这篇文章记录一下使用【React】+【Ant Design】在【码上掘金】上开发一个井字棋小游戏。

在【码上掘金】中引入React和Ant Design

我们通过cdn引入这两个库 先引入antd的css样式
步骤如下所示

  • 点击设置 1650719378(1).png
  • 切换到Style Tab 1650719428(1).png
  • 点击添加新资源,输入以下地址
https://unpkg.com/antd@4.19.5/dist/antd.min.css

然后引入antd和react的js库
我们切换到Script Tab,依次添加如下三个资源

https://unpkg.com/react@17/umd/react.production.min.js
https://unpkg.com/react-dom@17/umd/react-dom.production.min.js
https://unpkg.com/antd@4.19.5/dist/antd.min.js

前边两个是react和react dom的源,后边是 antd的源

先试一下是否正常引入

我们先实验一下是否正常引入了AntDesign,只要在我们的预览区中可以正常显示按钮样式,就代表这我们已经完成了所有我们需要的源引入
我们在JS开发区域中输入代码如下

class ChirldComponent extends React.Component {
    render() {
        return (<antd.Button type="primary" >这是一个按钮</antd.Button>
        )
    }
}
ReactDOM.render(<ChirldComponent />, document.getElementById("app"));

最终效果如下

1650720093(1).png

到这里我们已经成功引入了需要的开发依赖,只想要了解如何引入React和AntDesign到码上掘金的读者可以先撤了,下面我们开始介绍井字棋的实现过程

shou you the code

我先把代码贴出来,大家可以先试一下运行一下
Script

class ChirldComponent extends React.Component {
    constructor(props) {
      super(props);
      let nowKey = Date.now();
      this.state = {
        showWelcomeDiv: true,
        userFirst: true,
        currentStepIsUser: true,
        checkerboardArr: [
          [
            { id: 1, clickFlag: false, belone: "", key: `${nowKey}_1` },
            { id: 2, clickFlag: false, belone: "", key: `${nowKey}_2` },
            { id: 3, clickFlag: false, belone: "", key: `${nowKey}_3` },
          ],
          [
            { id: 4, clickFlag: false, belone: "", key: `${nowKey}_4` },
            { id: 5, clickFlag: false, belone: "", key: `${nowKey}_5` },
            { id: 6, clickFlag: false, belone: "", key: `${nowKey}_6` },
          ],
          [
            { id: 7, clickFlag: false, belone: "", key: `${nowKey}_7` },
            { id: 8, clickFlag: false, belone: "", key: `${nowKey}_8` },
            { id: 9, clickFlag: false, belone: "", key: `${nowKey}_9` },
          ],
        ],
        result: {
          finish: false,
          winOwener: "",
        },
      };
    }

    render() {
      function randomNum(minNum, maxNum) {
        switch (arguments.length) {
          case 1:
            return parseInt(Math.random() * minNum + 1, 10);
            break;
          case 2:
            return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
            break;
          default:
            return 0;
            break;
        }
      }
      const autoPlay = (tdItemId) => {
        let { checkerboardArr } = this.state;
        let autoAr = [];
        checkerboardArr.map((trItem) => {
          trItem.map((tdItem) => {
            if (tdItem.id === tdItemId) {
              tdItem.clickFlag = true;
              tdItem.belone = "user";
            }
            if (!tdItem.clickFlag) {
              autoAr.push(tdItem);
            }
          });
        });
        this.setState({
          checkerboardArr: checkerboardArr,
        });
        let finishFlag = calculate(checkerboardArr);
        if (finishFlag) {
          return;
        }
        const { currentStepIsUser } = this.state;
        this.setState({
          currentStepIsUser: !currentStepIsUser,
        });
        let autoDot = autoAr[randomNum(0, autoAr.length - 1)];
        if (typeof autoDot === "undefined") {
          return this.setState({
            result: {
              finish: true,
              winOwener: "",
            },
          });
        }
        setTimeout(() => {
          checkerboardArr.map((trItem) => {
            trItem.map((tdItem) => {
              if (tdItem.id === autoDot.id) {
                tdItem.clickFlag = true;
                tdItem.belone = "pc";
              }
            });
          });
          this.setState({
            checkerboardArr: checkerboardArr,
          });
          this.setState({
            currentStepIsUser: currentStepIsUser,
          });
          calculate(checkerboardArr);
        }, 1000);
      };
      const calculate = () => {
        let { checkerboardArr } = this.state;
        let userDot = [];
        let pcDot = [];
        checkerboardArr.map((trItem) => {
          trItem.map((tdItem) => {
            if (tdItem.belone === "user") {
              userDot.push(tdItem.id);
            }
            if (tdItem.belone === "pc") {
              pcDot.push(tdItem.id);
            }
          });
        });
        // 判断用户是否胜利
        if (isCurrerntWin(userDot)) {
          checkerboardArr.map((trItem) => {
            trItem.map((tdItem) => {
              tdItem.clickFlag = true;
            });
          });
          this.setState({
            result: {
              finish: true,
              winOwener: "user",
            },
            checkerboardArr,
          });
          return true;
        }
        // 判断电脑是否胜利
        if (isCurrerntWin(pcDot)) {
          checkerboardArr.map((trItem) => {
            trItem.map((tdItem) => {
              tdItem.clickFlag = true;
            });
          });
          this.setState({
            result: {
              finish: true,
              winOwener: "pc",
            },
            checkerboardArr,
          });
          return true;
        }
      };
      const isCurrerntWin = (arr) => {
        return (
          (arr.includes(1) && arr.includes(2) && arr.includes(3)) ||
          (arr.includes(4) && arr.includes(5) && arr.includes(6)) ||
          (arr.includes(7) && arr.includes(8) && arr.includes(9)) ||
          (arr.includes(1) && arr.includes(4) && arr.includes(7)) ||
          (arr.includes(2) && arr.includes(5) && arr.includes(8)) ||
          (arr.includes(3) && arr.includes(6) && arr.includes(9)) ||
          (arr.includes(1) && arr.includes(5) && arr.includes(9)) ||
          (arr.includes(3) && arr.includes(5) && arr.includes(7))
        );
      };
      return (
        <div>
          {this.state.showWelcomeDiv ? (
            <div id="welcomeDiv">
              <h1>欢迎体验【井字棋】小游戏</h1>
              <h3>本小游戏基于React和Antd开发,使用的开发工具为【码上掘金】</h3>
              <antd.Button
                type="primary"
                id="toGame"
                onClick={() => {
                  let random = Math.random();
                  let userFirst = random > 0.5;
                  this.setState({
                    showWelcomeDiv: false,
                    userFirst: userFirst,
                    currentStepIsUser: userFirst,
                  });
                  if (!userFirst) {
                    autoPlay(null);
                  }
                }}
              >
                点我开始游戏
              </antd.Button>
            </div>
          ) : null}
          {!this.state.showWelcomeDiv ? (
            <div>
              <h2> {this.state.userFirst ? "您为先手" : "您为后手"}</h2>
              <h2> {this.state.currentStepIsUser ? "请您落子" : "等待落子"}</h2>
              <table id="checkerboard">
                {this.state.checkerboardArr.map((trItem) => (
                  <tr>
                    {trItem.map((tdItem) => (
                      <td key={tdItem.key}>
                        <antd.Button
                          id="checkerboard-btn"
                          class={
                            tdItem.clickFlag
                              ? tdItem.belone === "pc"
                                ? "primary"
                                : "danger"
                              : ""
                          }
                          disabled={
                            !this.state.currentStepIsUser || tdItem.clickFlag
                          }
                          onClick={() => {
                            autoPlay(tdItem.id);
                          }}
                        >
                          <b>
                            {tdItem.clickFlag && tdItem.belone !== ""
                              ? tdItem.belone === "pc"
                                ? "X"
                                : "O"
                              : " "}
                          </b>
                        </antd.Button>
                      </td>
                    ))}
                  </tr>
                ))}
              </table>
              {this.state.result.finish ? (
                <div>
                  <h2>
                    {this.state.result.winOwener &&
                    this.state.result.winOwener !== ""
                      ? this.state.result.winOwener === "pc"
                        ? "失败乃成功之母,电脑获胜"
                        : " 恭喜您,您获胜了"
                      : "棋逢对手,和棋"}
                  </h2>
                  <antd.Button
                    type="primary"
                    onClick={() => {
                      let nowKey = Date.now();
                      const checkerboardArr = [...this.state.checkerboardArr];
                      checkerboardArr.map((trItem) => {
                        trItem.map((tdItem, index) => {
                          tdItem.clickFlag = false;
                          tdItem.belone = "";
                          tdItem.key = nowKey + "_" + index;
                        });
                      });
                      let random = Math.random();
                      let userFirst = random > 0.5;
                      this.setState({
                        checkerboardArr,
                        userFirst: userFirst,
                        currentStepIsUser: userFirst,
                        result: {
                          finish: false,
                          winOwener: "",
                        },
                        currentStepIsUser: true,
                      });
                      if (!userFirst) {
                        autoPlay(null);
                      }
                    }}
                  >
                    再来一局
                  </antd.Button>
                </div>
              ) : null}
            </div>
          ) : null}
        </div>
      );
    }
  }
  ReactDOM.render(<ChirldComponent />, document.getElementById("app"));

Style(scss)

  #app {
    width: 100%;
    height: 100%;
    text-align: center;
  }
  #checkerboard {
    margin: auto;
    font-size: 30px;
  }
  #checkerboard .primary {
    color: blue;
  }
  #checkerboard .danger {
    color: red;
  }
  #checkerboard-btn {
    width: 50px;
    height: 50px;
  }

MarkUp

<div id="app"></div>

实现逻辑

我们的实现逻辑其实比较简单,就是定义9个td,当用户点击td的时候,自动设置当前td的点击人和自动计算是否赢得比赛,然后存储在state中,我对比代码给大家介绍一下实现逻辑

初始化数据

1650720612(1).png
我们在类的构造器中初始化开始的数据

  • showWelcomeDiv: 是否展示欢迎页。默认展示
  • userFirst:是否为用户先手,此处只是定义值,点击进入游戏和再来一局的时候会算这个值
  • currentStepIsUser:当前人是否是用户。这里为了防止在计算电脑下棋时用户点击棋盘,当前人不是用户的时候。所有的td都不可以点击
  • checkerboardArr:棋盘的td初始化数据
  • result.finish:是否结束当前棋局。平局和胜、负都会结束
  • result.winOwener:赢家。如果没有值则为平局

随机数算法

1650721545(1).png
这个算法是cv的网上的,我们用随机数来随机当前没有下过的格子给pc使用,作为它的落子点

自动下棋的方法

1650721676(1).png\

  1. 我们先获取当前用户点击的棋子,将它设置为用户已经点击的数据,然后更新状态。\
  2. 设置完之后计算实时的胜负情况,如果有人胜出则结束该方法
    3 .然后设置所有的当前步为pc执子:currentStepIsUser: !currentStepIsUser\
  3. 之后我们将获取到的所有未下的格子随机出一个格子来,如果随机不出来则表示平局(因为所有人都下完棋了而且在上一步计算的时候没有人胜出)\
  4. 如果不是平局,设置一个定时器。1S后落子计算输赢的情况

计算电脑或者用户输赢的方法

1650722097(1).png
这个方法只是用来收集用户和pc下的棋和设置胜利之后的状态值,下面是真正计算的方法 1650722192(1).png
算是否赢得比赛的方法很简单,就是穷举,这里的所有可能赢得情况是copilot生成的。不得不说,ai生代码真香

欢迎页和开始页

1650722376(1).png
这里我们在按钮点击的时候设置用户是否先手,如果用户后手需要pc先下一格棋

棋盘页

1650722503(1).png
棋盘页面负责的大部分就是渲染棋子的功能。在状态更新之后及时渲染

结果页

1650722619(1).png
结果页没有什么好说的,唯一需要注意的是checkerboardArr数组类的元素的key值一定要实时生成,否则状态会继续根据默认的index进行渲染,这个时候key===index没有发生变化。我们的棋盘不会重新渲染!

结语

今天使用【码上掘金】开发了一个井字棋小游戏,总体感觉就是十分强大。试想一下,一会如果出了Vue4.0,React20.x我们直接引入cdn就可以学习新特性了,真的是前端卷王必备。
如果您觉得对您有用的换,欢迎大家点赞交流!