dwgx@blog:~$dwgx
> cd ../posts

EmperorGame 一个 Java 进程跑联机卡牌对战

// created

EmperorGame 是个在浏览器里玩的联机卡牌对战, 玩法就是皇帝平民奴隶那套猜拳, 规则简单心理博弈足。整个后端塞在一个 Java 进程里, 拿它练实时通信。

一个小游戏不想拉一堆服务, 后端就做成单进程: Java-WebSocket 负责对战实时通信, JDK 自带的 HttpServer 顺手托管前端静态页和几个 JSON 接口。没 Spring 没 servlet 容器, mvn clean package 打个 fat jar, java -jar 直接起。前端 Vue 3 + Vite 的构建产物丢进内置 HTTP。上生产前面挂 Nginx 同域反代, / 走静态, /api/ 走 HTTP, /ws/ 走 WebSocket, HTTPS 下自然切 WSS。默认端口 WebSocket 13337, HTTP 13338

对战流程很朴素。客户端发 match_online 进队列, 凑够人配进一个 GameRoom, 每回合双方 play_online 锁牌, 都 ready 后服务端判定广播。判定就是那条克制环, 皇帝 > 平民 > 奴隶 > 皇帝; 叛徒赖皮只赢皇帝, 碰别人全输; 疯子更离谱, 强制平局还触发随机事件。皇帝输一手就结束, 奴隶掀翻皇帝直接秒胜, 牌堆出完收场。叛徒或疯子出场那回合强制明牌, 博弈立马紧张。牌堆有 standardrandom 两种模式, 控制台敲 /set randomcard on|off 或用 DECK_MODE 环境变量切。不想联机还有个 AiGame 单机跟电脑打。

联机得认得出人。注册登录走 POST /api/register/api/login, 返回 accountId 和一个签名 token, HMAC-SHA256 签的, 24 小时有效, 存 session_tokens 表。用户和日志落 MySQL utf8mb4, 启动自动建表。前端把 accountIdtoken 缓 localStorage, 连 WebSocket 时带上身份。另外留了 AUTH_SECRET 做签名密钥, ALLOWED_API_ORIGINS 控 CORS, 一组 keystore 变量开 WSS。

踩过几个坑。shade 打出来两个 jar, 得跑带 -shaded 后缀那个, 不然报 “no main manifest”。CORS 回显 Origin、WSS keystore 这些边角真上线才发现细节多。规则和 Nginx 示例都记在仓库的 gamelogic.md 里。