【新春征文】基于互动白板实现一个多人数独游戏

我正在参加「新春」征文活动: https://www.agora.io/cn/community/discussion/0/25358

基于Agora 互动白板的SDK与Window Manager 来制作一个场景化窗口插件,实现一个多人数独游戏。在游戏中,每个玩家进入白板房间,都能看到数独游戏插件,同时可以参与其中,与房间内的小伙伴一起完成数独解题。


前期准备

  1. 一个实名认证的声网账号
  2. 了解Vue开发的基础知识
  3. 了解声网互动白板产品
  4. 搭建开发环境


开启和配置互动白板服务

首先我们需要一个实名认证的声网账号,进入控制台,在 Agora 控制台开启互动白板服务。

这里需要注意的是互动白板它是作为服务而显示的,而控制台中只是显示项目列表,并没有直接显示服务。


这时候我们就先创建一个项目,然后点击“配置”进入到项目详情中,在里面的页面就可以看到服务内容了。

这时候找到互动白板服务,点击开启就行。因为我的就已经开启了,所以显示的是配置按钮。


开发环境

  • NodeJs版本 16.0.0
  • @vue/cli 4.5.1
  • VSCode代码开发工具


使用官网提供的代码模板

场景化窗口插件目前我们不需要从零开始建立的,声网提供了一个代码模板,基于此模板我们可以很轻松就能实现一个在互动白板上使用的插件。

模板地址: https://github.com/netless-io/community-app-template

当搭建好开发环境后,便可以下载模板代码了,通过Git或者下载Zip都行。

需要注意README.md中的开发环境配置:

  1. 在 .env 文件里配置白板房间 UUID 和 Token请将本目录下的 .env.example 文件复制一份,重命名为 .env 或 .env.local 后,在里面填写必须的白板配置信息。你可以在 Netless Workshop 申请专用的白板配置。
  2. 执行 npm install 安装依赖
  3. 执行 npm start 进行本地开发


项目结构

  • 笔者是基于vue版本的插件模板进行开发的,直接打开项目,修改src里的内容即可,基本上和Vue开发一致。如果是通过Git命令拉取的代码,需要切换分支为vue分支。

  • 项目结构如下:

  • playground目录是基本不用修改的,我们实现的插件是在src目录中完成的。
  • index.ts文件是我们插件的一些数据设置,比如插件名称。其他逻辑基本不用修改。
const Sudoku: NetlessApp = {
  kind: "Sudoku",
  setup(context) {
    const box = context.getBox();
    box.mountStyles(styles);

    const $content = document.createElement("div");
    $content.className = "app-counter";
    box.mountContent($content);

    const app = createApp(App).provide("context", context);

    app.mount($content);

    context.emitter.on("destroy", () => {
      app.unmount();
    });
  },
};

export default Sudoku;


数独游戏规则

数独网格由9x9个空格组成。玩家只能使用数字1到9,每个3×3 宫只能包含数字1到9,每一列只能包含数字1到9,每一行只能包含数字1到9,每个3×3 宫、每一列或每一行中的每个数字只能使用一次。当所有数独网格都填入正确的数字时,游戏结束。(摘自网上)


数据同步核心思路

1. 通过互动白板SDK提供的createStorage方法,初始化一个数独棋盘,并把数据存储起来,同时需要更新自己的chessBoard。

const chessBoard = ref();
const context = inject<AppContext>("context");
const storage = context.createStorage("chessBoard", { chessBoard: ChessBoard.init() });
chessBoard.value = deepCopy(storage.state.chessBoard)


2. 在vue界面的onMounted回调函数中添加存储值的更新监听,这样玩家在填格子的时候广播了最新的格子数据,其他玩家就能收到更新的通知,然后重新渲染界面。

onMounted(() => {
  initAppData()
  storage.addStateChangedListener((diff) => {
    chessBoard.value = deepCopy(diff.chessBoard?.newValue)
    if (finish(chessBoard.value.gridItems)) {
      statistics(chessBoard.value.gridItems)
      finishTag.value = true
    }
  })
});


3. 填格子的时候,通过SDK的setState方法去更新数独棋盘的数据。

  if (e.data && e.data > 0) {
    grid.number = parseInt(e.data)
    grid.userId = uid.value
    grid.color = rgb
  } else {
    grid.number = 0
    grid.userId = ''
    grid.color = new Rgb(233, 233, 233)
  }
  storage.setState({ chessBoard: chessBoard.value })


数独游戏界面

笔者是优先实现这个互动游戏的核心功能,所以设计出的游戏界面比较简陋。


数独的数据结构

export class ChessBoard {
    gridItems: GridItem[][]
}

因为需要统计游戏结束后,各个玩家填写的个数,所以在更新格子数值的时候也记录一下是哪一位玩家填写了。default字段表示该格子是自动生成的,不需要玩家填写了。

export class GridItem {
    number: number
    color: Rgb
    userId: string
    default: boolean
}


运行项目

  • 点击工具栏最下方的按钮,可以找到我们实现的插件icon,然后点击即可打开。

  • 打开插件后,会初始化创建一个数独题目,并且通过createStorage方法储存起来,所有进入房间的玩家都能拿到这个数据,后续数独的更新都会同步给玩家去修改。因为这个项目重点是学习一下插件开发与数据同步,所以游戏界面以及内容这些做的比较简单。

  • 接下来就要打开多个网页并且输入 localhost:3000 进入我们的互动白板房间了,因为模板的uid是随机生成的,也就表示有不同的玩家进入了。

  • 别的玩家填写后,其他玩家是不能再填这个格子的,最后游戏结束的时候,会统计各个玩家所填对的数字并展示。

  • 游戏结束


总结

通过官方提供的场景化插件模板,我们很容易实现一些好玩的互动场景的功能。在这个简单的项目中,由于时间仓促,还没来得及优化得更好,后续有时间的时候笔者再打磨一下。比如不同玩家填写的数字以不同的颜色区分,增加限时机制等等。感兴趣的朋友一起来开发一些好玩的实时互动功能吧~


推荐阅读
作者信息
相关专栏
SDK 教程
167 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。