11月6日
clineblog / 文章 / 阅读
技术笔记

灵词同契开发复盘

# 灵词同契开发复盘:用 AI 辅助完成一个双人猜词微信小游戏

作者:cline

整理日期:2026-11-09

我做了一个叫「灵词同契」的双人猜词微信小游戏。两个人各自想一个词,提交后一起揭晓,再根据彼此的答案继续联想,直到想到同一个词。

玩法可以用几句话讲清楚,但做成能让两个人在手机上连续玩下去的产品,需要处理房间状态、同步、重连、历史记录,以及不少只有实测才会发现的交互问题。这篇文章记录它从想法、开发到真机验收的过程,也说明 AI 在其中具体做了什么。

截至本文整理时,核心功能已完成双人真机验收,项目正在准备审核上线,尚未提供正式公开体验入口。微信分享功能曾出现平台限制提示,原因及解除状态仍未确认。

## 从一个简单玩法开始

游戏只需要两位玩家。第一位创建房间,将 6 位房间号告诉搭档;另一位输入房间号加入。两人各自输入一个词,双方提交完后同时看到结果。

例如,第一人想到「海浪」,第二人想到「沙滩」。看过答案后,两人继续寻找这两个词的联系,下一次也许都会想到「大海」。这是一个说明玩法的虚构例子,不是真实玩家记录。

实现中区分了“轮”和“尝试”:词语不一致时,双方确认后进入同一轮的下一次尝试;词语一致时,本轮成功,双方准备后才开始新一轮。答案只去除首尾空白,其余字符精确比较,没有接入 AI 判断近义词。

我把第一版控制在这个范围内,没有加入聊天、排行榜、商城或复杂养成。重点是让两个人能看懂规则、顺畅完成一局,并愿意继续。

## 第一版能运行,却没有完整表达玩法

早期版本完成了创建房间、加入和答案比较,但双人实测发现:双方提交以后,词语不一致就进入下一次尝试,玩家看不到对方刚才想了什么。

对这个游戏来说,上一组词语就是下一次联想的线索。只显示“不一致”会让玩家失去继续推理的依据。

因此,结果流程改成了三个明确的步骤:

1. 双方提交完后,公开各自的词语并停留在结果页。

2. 两人分别确认看过结果,等双方都准备好才继续。

3. 每次结果留在页面下方,后续仍可滑动查看。

这次修改同时影响了界面、服务端状态、事件记录和恢复逻辑。客户端不能仅靠跳转页面完成它,云端也要知道当前处于“已揭晓、等待继续”的阶段。

## 技术方案:Canvas 客户端与微信云开发

项目采用微信小游戏形式,界面使用原生 JavaScript 和 Canvas 2D 绘制,服务端使用微信云函数、wx-server-sdk 和云数据库。

浅蓝色背景、双心图案、白色卡片和大圆角按钮都由界面代码绘制。背景保持静止,仅在成功时播放一次双心靠近的动画。这样既符合我想要的柔和风格,也减少了第一版需要准备的外部素材。

Canvas 给了布局和绘制上的自由,同时也意味着按钮命中区域、滑动、输入框、键盘和安全区域适配都需要自己处理。例如,历史列表发生滚动后,对应按钮的可点击位置也必须跟着移动,不能只移动画面。

主要代码分工如下:

| 模块 | 负责的事情 |

| --- | --- |

| game.js | 初始化云开发,启动客户端和界面 |

| src/client.js | 调用云函数、管理房间快照、监听与恢复 |

| src/view.js | Canvas 绘制、触摸、键盘、资料页和历史列表 |

| cloudfunctions/gameApi/service.js | 创建和加入房间、提交词语、结算及继续 |

| cloudfunctions/gameApi/wx-store.js | 将业务所需的数据操作适配到云数据库 |

| tests/ | 使用隔离数据验证业务与客户端行为 |

整体数据流是:玩家点击按钮,客户端向云函数发起操作;云函数处理后更新房间和玩家视图;客户端收到响应或视图变更,再绘制界面。

这个游戏的业务后端运行在微信云开发中。我的博客使用 Docker Compose、Halo 社区版、PostgreSQL 和 nginx,它负责展示文章与作品,不承担小游戏的实时房间服务。

## 用状态机约束两个人的操作

双人游戏不能由两部手机各自判断“现在该进入下一步了”。否则,只要请求先后顺序不同,两个人就可能看到不同的阶段。

房间状态由服务端管理:

| 状态 | 含义 | 后续变化 |

| --- | --- | --- |

| waiting | 只有一位玩家,等待搭档 | 第二位加入后进入 playing |

| playing | 正在想词或等待另一位提交 | 双方提交后进入 revealed 或 matched |

| revealed | 词语不同,已经揭晓 | 双方确认后增加尝试号,回到 playing |

| matched | 词语一致,本轮成功 | 双方确认后增加轮次,回到 playing |

| closed / expired | 主动结束或到期 | 不再接受正常对局操作 |

客户端根据云端快照展示按钮。例如,自己已经提交时,输入和提交按钮会锁定;自己已经准备继续时,就显示等待搭档。点击按钮代表发出请求,收到确认才代表操作结果已经确定。

## 为什么把提交与结算放在事务中

两位玩家可能先后提交,也可能几乎同时提交。服务端需要把读取当前状态、接受提交、比较词语、写入结果和更新双方视图作为一组完整操作处理。

第一位玩家提交时,词语放在内部提交记录中,另一位看到的只是“已提交”。第二位提交时,事务读取第一位的词语,完成精确比较,将双方词语写入公开结果事件,删除此前的私密提交,并更新房间与双方视图。

这样,词语从“等待揭晓”到“双方可见”的切换有明确的边界,不会由客户端分别拼接出一半完成的结果。

客户端监听的是属于自己的 player_room_views。界面所需的状态放在这里,内部房间记录和未揭晓答案不由客户端直接读取。云函数根据微信调用上下文识别玩家,而不是让客户端自行声明身份。

## 超时并不等于没有提交成功

断网恢复中,一个容易忽略的情况是:云端已经接受词语,但手机没收到响应。如果客户端把超时当作失败,重新生成一次操作,就可能把同一个答案交到下一次尝试中。

本项目为一次操作生成一个 requestId。请求结果不确定时,先保留这次操作;恢复后沿用同一个编号确认结果。新操作才生成新编号。服务端保存回执,使重试能够返回已有结果,而不重复执行这次提交。

提交和继续请求还带有期望轮次与尝试号,用来判断操作属于哪个阶段。客户端收到房间快照时会比较版本,避免较旧的响应覆盖较新的状态。

前后台切换和监听重连也有自己的生命周期:后台暂停同步,返回前台重新恢复;替换监听时关闭旧连接,并忽略旧监听的迟到回调。心跳只用来反映活跃情况,不会因为短暂离线就把玩家踢出房间。

## 历史记录:快照只放最近一部分

每次结果都要留给玩家查看,但如果每次同步都带上房间内全部历史,数据会随对局不断增大。

实现采用两层记录:房间快照缓存最近 20 个结果,完整结果写入事件集合;用户查看更早记录时,再通过云函数按事件序号分页加载。客户端按事件 ID 去重、按序号排序。

还有一种情况:玩家长时间离线,回来时已经错过超过 20 条结果。直接把旧缓存和最新快照拼起来,中间可能出现一个看不见的缺口。因此恢复时会检查两个窗口是否衔接;必要时从最新连续窗口重新分页,避免把不完整列表误当成完整历史。

这里的“保留历史”指房间有效期内的结果恢复与查看,不是永久存档。当前房间设定为 24 小时有效。

## 真机测试带来的几个具体修改

### 头像昵称与首次进入

双人卡片最初只有通用身份标记。加入头像昵称后,对局更容易让人认出搭档。后续又按我的产品要求,把首次入口调整为先主动授权微信头像昵称,保存成功后进入大厅;拒绝授权时留在授权页。

头像选择曾被微信提示需要声明隐私用途。之后真机反馈确认上传已可用,双方也能看到彼此的头像昵称。这说明平台配置和实际设备测试属于功能交付的一部分。

当前授权标记用于恢复产品流程,不是服务端验证的微信授权凭证。这个入口要求也属于本项目的实现选择,不能据此推定平台审核一定接受;上线仍要按实际审核结果处理。

### 返回大厅与退出房间

早期有返回首页的操作,但退出入口放在较下方,不够明显。回到大厅后只剩“继续房间”,玩家容易觉得自己无法结束当前房间。

后来在大厅和房间固定顶部都加入退出入口。返回大厅保留房间,确认退出则结束整个双人房间,清理当前对局状态,再回到可以创建或加入房间的大厅。两种行为需要在界面上清楚区分。

### 成功提示被画成了警告

退出成功后,客户端曾显示“已退出房间,可以重新邀请搭档啦”。但当时所有消息共用一个粉红色提示区,还附带“重新连接”按钮,导致正常操作看起来像发生了故障。

最后移除了这条多余的退出成功消息。页面回到正常大厅本身就足以表达结果,不需要再让玩家处理一次提示。

### 本地修改不会自动更新手机上的包

更名为“灵词同契”后,本地代码和模拟器已经显示新名称,手机却仍然显示旧标题。原因是改完本地代码后,手机没有加载新的预览包。

这次问题提醒我,代码检查、模拟器运行、预览上传、真机扫码是几个不同环节。交付时要核对玩家实际打开的版本,不能把本地修改完成当成设备已经更新。

## 我怎样验证这个项目

最近一次本地验证共有 29 项测试通过,另有 12 个 JavaScript 文件语法检查和 JSON 配置解析检查通过。

测试使用 Node.js 内置测试工具与隔离的内存存储模型,覆盖了提交、结算、双方继续、请求重试、历史分页、监听替换和退出等行为。这些测试不连接真实云数据库,也不能替代微信平台上的测试。

真正的双人体验由两个不同微信身份在手机上验证。我确认了双方词语揭晓、逐次历史保留、头像昵称互相展示,以及后续交互修改的测试结果。

仍需单独对待的事项包括:

- 分享曾收到微信平台限制提示,不能仅因游戏其他部分通过就记为已恢复。

- 过期清理已有实现和本地测试,但现有记录尚未证明真实云端定时清理已完成验证。

- 上线前仍需核对资料与元数据保留策略、平台隐私配置和审核要求。

因此,“本地测试通过”“双人功能验收”和“审核上线完成”在这篇复盘里是三个不同结论。

## AI 在开发过程中做了什么

这不是一份独立手写全部代码的开发记录。项目使用 Codex 辅助需求梳理、页面和云端业务实现、测试编写、问题排查以及文档整理,这篇复盘草稿也由 AI 辅助整理。

我负责提出玩法、决定视觉方向、提出修改要求,并参与真实设备上的双人测试与验收。例如,保留双方每次词语、让退出入口更明显、删除容易误解的提示,都来自实际使用后的反馈。

AI 能较快把需求变成可运行代码,也能辅助补齐重复请求、状态恢复等逻辑。不过,它最初给出的方案并不总能完整表达我想要的体验。持续描述问题、实际操作、检查结果,再把反馈转成下一次修改,是这个项目真正推进的方式。

我更愿意把它理解为一次有人做决策、有人验收的 AI 辅助开发实践。生成代码只是其中一个环节,运行、部署、平台配置和真机体验都需要继续核对。

## 源码与后续计划

源码仓库:[chenxinxinxinxin/lingci-tongqi](https://github.com/chenxinxinxinxin/lingci-tongqi)。项目采用 MIT 许可证,版权署名为 cline;第三方依赖遵循各自许可证。

<!-- 发布前确认仓库已经设为 Public,否则访客无法访问上述源码链接。 -->

仓库包含客户端、云函数、测试及设计和部署文档。自行运行需要自己的微信小游戏 AppID 与云开发环境,不能直接将源码上传到普通网站运行。具体部署配置应以仓库说明和当前微信文档为准。

我的博客用于展示这个项目、记录实现思路和后续变化。暂时不再维护一套相同的网页游戏:那会增加微信登录、平台接口和实时服务适配的工作量,而当前展示需求用文章、截图和演示已经能够满足。

审核上线后,我会在这里补充正式小游戏码,并更新分享功能和后续版本的情况。

<!-- 上线后在此插入正式小游戏码;不要使用会过期的开发预览码。 -->

文章讨论

围绕这篇文章交流想法。请保持友善,尊重不同观点。