背景
三五好友,几台手机,没有实体牌。
西库是柬埔寨本地牌戏:52 张牌,三到四人,手里 8 张凑成四副对子;红牌算钱,钱在人和人之间走,不进程序。朋友本来就会打。问题不在规则,在牌桌:有人没带牌,或者牌要重新洗。任务不是发明另一种牌戏,而是让原来那张牌桌,在 Telegram 里、在三到四台手机上成立。

背景
西库是柬埔寨本地牌戏:52 张牌,三到四人,手里 8 张凑成四副对子;红牌算钱,钱在人和人之间走,不进程序。朋友本来就会打。问题不在规则,在牌桌:有人没带牌,或者牌要重新洗。任务不是发明另一种牌戏,而是让原来那张牌桌,在 Telegram 里、在三到四台手机上成立。
模型
能起争议的东西全部放在服务端:牌墙、四家手牌、轮次、倒计时、抢胡窗口。客户端只渲染发给自己的快照,再把出牌、摸牌、吃、胡、过发回去;它不判牌。每张弃牌都走同一条顺序:先看谁能胡,开 15 秒响应窗;没人胡,再看下家吃不吃;不吃就摸牌。吃进后必须打一张,新的弃牌重新走一遍。抢胡按座次,不按谁先点——先点先得奖励的是网速,不是牌技。判牌是纯函数:8 张拆 4 副对子,穷举验证全部 C(52,8) = 752,538,150 手。1,731 行规则写在代码之前。后面的工作才立得住。
构建
玩家端是 Telegram Mini App 里的 Flutter Web,原生壳留给以后。服务端是 Java 25 + Spring Boot 4.1.1:REST 管大厅,WebSocket 管牌桌;PostgreSQL 18.6 存会员、房间、已结算的局和转账;Valkey 9.1.2 存会话、进桌票据和还在打的牌。Vue 3 管理后台看会员、房间和账本。整套在 Docker + nginx 后面。好友桌和机器人桌共用同一个引擎,只换座位、结束规则和账本标题。客户端、服务端、后台合计 11.9k 行,另有 2.3k 行服务端测试。规则文档和测试决定了代码能做什么。
结果
v1 有好友房:3 或 4 人、邀请链接可重复使用、房主开局、中途要停须全员同意;机器人房一个人也能打;断线留 15 秒;账本把一局净额压成最少几笔转账。三语:中文、English、ខ្មែរ;高棉语字体随包走,避免组合符号错位。没有匹配、没有观战、没有钱包、没有广告。在 Telegram 里通过 @sikhu_bot 打开。
复盘
做对的是:先把规则和边界写死,再做界面;服务端权威;好友桌和机器人桌共用一套引擎;判牌穷举;账本和钱分开。会改的是:补 UI 自动化,不只单测;在更多真实 Telegram WebView 上看高棉语整形;把原生壳发出去;再给机器人一个难度档。1,731 行规则和测试,才是它没散架的原因。
2.3k 行服务端测试 · 7 个数据库迁移 · 13 个 Java 测试类,2 个 Flutter 测试文件。