网页联机红心大战开发日志

玩windows红心大战的时候突然想到可以写一个 打开微软大战代码开干

确定框架

首先确定需求和技术栈,和Gemini沟通几轮之后决定采用前后端分离的形式,前端负责UI、发牌动画、点击出牌、房间号输入框等;后端是游戏服务器(维护房间列表、洗牌发牌、校验出牌是否合法、计算分数,并通过 WebSocket 实时把画面变化广播给所有玩家

因为我基本没有网站开发经验(也不需要有),技术栈全让AI决定了:

  • 包管理:pnpm workspace
  • 语言:TypeScript
  • 前端:React 19、Vite 6、socket.io-client
  • 后端:Node.js、Express、Socket.IO
  • 构建:
    • shared:tsc
    • client:tsc -b && vite build
    • server:tsup(ESM 打包)

开发过程

首轮开发

第一轮对话之后,codex捣鼓捣鼓发了个网站过来,没什么报错,是个好兆头,不过ui相当简陋


此外bug也不少,包括但不限于:ui非常丑,我希望的ui和微软游戏的界面类似,并且有选牌的动画,出牌特效等;此外我希望在打牌的进程中就可以看到四个人得了多少分,但实际上是一局结束后才显示;最后一局打完之后无法结束,也无法开下一局,应该定一个规则,就是分数最高的玩家达到多少分之后游戏结束,默认可以是100分;传牌阶段只能点快速传牌,不能自己选;机器人只能点击添加,添加之后好像不能修改难度…等等等等

优化UI

首当其冲的是改UI,这ui看着我就不想玩。上网找了找,搞了一套扑克牌的svg图给ai,但体积稍微有点大:不到8MB,这个是要加载到前端的素材,最好优化一下,不过我懒得搞了。

接着创建一个全新的 PlayingCard.tsx 组件,统一渲染所有卡牌;把背景改成绿的…反正大改ui,改完之后长这样:

偷懒用现在版本的图,不过刚改完也差不多了
是不是一下子就有感觉了?对比微软的版本:

这样看着就舒服多了!

但当时ui还有点小问题,比如中间的出牌区有时会和头像框重合之类的,反馈一下也好了

增加出牌动画(暂缓)

现在这个阶段已经是可玩的了,但所有的cpu出牌都是瞬间出,所以我出完牌之后看不到cpu出的牌这回合就结束了,有点难受,于是让agent增加一个慢速模式,不过改了两版之后,反而连开头的换牌都继续不了了,只能先回退
后面这个功能还得加。

跨域访问

这个项目是前后端分离的,用pnpm管理,pnpm --filter @hearts/client dev --host可以启动客户端 ,--host参数可以在局域网内都能访问,否则只有localhost可以用;pnpm --filter @hearts/server dev可以开后端,但这时加参数没用了,还是连不上后端,问ai是CORS的问题
修改了绑定的host为0.0.0.0 CORS 改为环境变量驱动process.env.CORS_ORIGIN || "*"差不多这样就行了

多设备适配

这下别的设备也可以访问了,但手机端没做适配,ui有问题,交给ai解决

后端打包成docker并部署

现在游戏基本可用了,下一步就是把后端打包成docker镜像,这样随便找台服务器放上去就行了
让agent写Dockerfile .dockerignore等配置文件,写好之后传到对应机器构建
不过遇到点问题:构建完之后无法运行,还没找到解决办法,暂时只能在本地跑后端了

前端部署到Vercel

前端比较好办,把项目传到github上之后从vercel拉下来就行,但由于这是一个monorepo,即客户端和服务端放在一个仓库里,还有shared文件夹存放了共享的代码,所以部署有些麻烦
首先根目录不能是packages/client,而是packages 因为client和shared都和前端有关
然后Build Command改为

1
pnpm install && pnpm -F @hearts/shared build && pnpm -F @hearts/client build

Output Directory改为client/dist

然后直接Deploy,前端就没啥毛病了

总结

AI编程还是很有趣的,整个项目我几乎完全没写过代码,让我写我也不会,但排查bug不断反馈还是相当累人的,而前端后端部署虽然ai也可以代劳,但研究之路还很长,就先这样吧

TODO

  • 隐蔽的调试后门 (Hidden Debug Menu)

    • 建议实现方式:在登录页或大厅的某个不起眼的角落(比如左下角的版本号文本),实现“连续点击 5 次”触发的隐藏彩蛋。

    • 功能:弹出一个弹窗,允许手动输入后端的 WebSocket URL(例如 ws://192.168.x.x:3001),并将其持久化保存到浏览器的 localStorage 中。这样你在手机上测试时,随时可以切换后端地址,再也不用重新部署 Vercel。

  • 前端管理员大盘 (Admin Dashboard)

    • 入口:单独的路由(例如 /admin)。

    • 鉴权:最简单的实现方案是在前端输入一个密码,通过 WebSocket 发给后端校验(密码可以配置在后端的 .env 中)。

    • 功能:获取并展示当前所有活跃房间的列表、房间内的玩家状态(真人/CPU)、当前进行到第几局、服务器内存占用等基本信息。

  • 彻底攻克 Docker 打包 (Docker Build)

    • 目标:解决 Node.js ESM 模块的路径解析报错(ERR_MODULE_NOT_FOUND)。

    • 方案:在 packages/server 引入 tsup,将后端代码打包成单文件(Single Executable),彻底规避 Monorepo 目录结构和文件后缀带来的各种奇怪路径问题。

  • 重构前端动作队列与出牌动画 (Action Queue)

    • 目标:解决“AI 瞬间出牌导致玩家看不清”的问题。

    • 方案:重新审视之前回退的代码。将 WebSocket 收到的事件先推入前端队列,按照固定的时间间隔(如 800ms)逐个播放,并配上 SVG 卡牌的平移飞行动画。

  • 加入基础音效系统 (Audio Effects)

    • 建议:寻找几个无版权的清脆音效:发牌声、出牌声、一墩结算收牌声、得分警告声(比如吃到黑桃 Q 时的特殊音效)。音效对棋牌类游戏的“打击感”提升是巨大的。
  • 幽灵房间清理机制 (Garbage Collection)

    • 目标:防止服务器长时间运行后内存泄漏。

    • 方案:在后端的 RoomManager 里加一个定时器。如果一个房间超过 2 小时没有任何操作,或者所有真人都断开连接超过 15 分钟,自动销毁该房间实例。

  • 房主踢人与权限转移

    • 功能:房主可以手动将一直不准备的玩家踢出房间;如果房主掉线,系统自动将房主权限移交给房间内的下一个真人玩家。

项目地址:https://github.com/Sanstale/hearts-online