【作品】联机游戏大厅:五子棋、中国象棋、反转棋(附源码下载)
这是我刚交付给客户的一个小项目:网页版游戏大厅。打开链接,选一个游戏,开一个房间把房间号发给朋友,两个人就能对下;一个人也行,可以直接挑战电脑。
现在有三个游戏:五子棋、中国象棋、反转棋(黑白棋)。客户同意公开,所以我把代码整理了一份放在文末,可以下载自己跑,也可以拿去改。

图:大厅首页。
客户已经上线了:oyeyeyouxi.com。那个站设了进入密码,外人只能看到登录页,所以下面的截图都是我在本机跑源码截的。
它能做什么
- 联机对战:创建房间会得到一个 4 位房间号,发给对方输入就能加入。房间可以设成公开(出现在大厅的“等你来的房间”列表里)或者仅限邀请。
- 快速匹配:不想找人的时候点一下,会先加入正在等人的同类房间,没有就排队等下一个。
- 人机对战:四档难度(入门 / 普通 / 困难 / 大师),可以选执黑还是执红、先手还是后手。
- 悔棋、求和、认输:联机时每人每局最多悔棋 3 次、求和 3 次;人机对战每局可悔棋 5 次。
- 每步限时和断线处理:五子棋和反转棋每步 60 秒,中国象棋 120 秒,超时判负(这几个数都能在
.env里改);对局中掉线默认等 60 秒,不回来判负;回来之后能接着下。 - 快捷聊天:九句固定的话(“你好呀”“好棋!”“再来一局?”这类),另外可以打字,限 40 个字、每分钟 20 条。
- 三个游戏的规则:五子棋棋盘 13~20 路可选,自由规则(五连及以上获胜,黑先);中国象棋是标准走法,会提示将军,被将死或无子可动判输,连续 120 步没有吃子判和;反转棋 8×8,一方没地方下会自动跳过。

图:五子棋的房间页,下面是正在等人的公开房间。

图:五子棋,我执黑,白棋已经连成四个。

图:中国象棋,最后一步的起点和终点有标记。

图:反转棋,小圆点提示可以下的位置。
下载和运行
源码包:game-lobby.zip,约 80KB,解压后是这样:
game-lobby/
├── server.py # 服务端:网页 + WebSocket + 规则
├── index.html # 游戏页面(含 AI,单文件)
├── login.html # 密码登录页
├── gomoku.sh # 后台运行脚本(带菜单)
├── nginx-gomoku.conf # 反向代理示例
├── .env.example # 配置模板
├── README.md
└── LICENSE # MIT不用 pip install 任何东西,需要 Python 3.7 以上(用到了 asyncio.run):
cd game-lobby
python3 server.py然后浏览器打开 http://localhost:8765。这样默认没有密码,只监听本机,自己试玩够用。
要后台长期跑、加进入密码:
cp .env.example .env
chmod 600 .env # 然后编辑 .env,填 GOMOKU_PASSWORD
./gomoku.sh # 不带参数会出现菜单我自己在本机用解压出来的这份包启动过,页面能正常打开、/healthz 返回 ok。包里不含我的 .env(里面有真实密码),nginx 配置里的域名和证书路径也换成了占位符。
结构:为什么只有两个主要文件
整个项目的思路和官网的表单推送一样:能不装的东西都不装。
- 没有框架、没有数据库。 服务端只用标准库的
asyncio,房间、对局、封禁记录全放内存里。 - 网页和 WebSocket 同一个进程、同一个端口。
GET /发页面,/ws走对局,nginx 一层反代就行。 - 前端是一个 HTML 文件。 样式、脚本、三个游戏的 AI 都在里面,没有构建步骤,改完刷新就能看。
代价也很明显,放到后面“局限”里说。
几个值得讲的设计
1. 所有规则在服务端判,页面只说“我想下在这”
页面发给服务器的只有一句话:“我想下在 (x, y)”。落子合不合法、轮到谁、有没有赢、有没有超时,全部由 server.py 决定,再把结果广播给两个玩家。
这样做的好处是:就算有人改了页面脚本,也没法作弊——最多只是发一个服务端不认的走法,服务端直接拒绝。AI 例外:人机对战在浏览器里跑,因为对手是电脑,没有“防作弊”的必要,还能省服务器。
2. 手写 WebSocket,没有想象中复杂
用标准库没有现成的 WebSocket,所以握手和帧都是手写的。握手只需要算一个值:
key = hd.get('sec-websocket-key', '')
accept = base64.b64encode(hashlib.sha1((key + GUID).encode()).digest()).decode()帧的解析要处理三件事:客户端发来的帧必须带掩码、消息可能被分片、长度有三种编码(7 位 / 16 位 / 64 位)。我加了两条保护:单条消息最大 4096 字节,每条连接用令牌桶限速(突发 20 条,每秒回 10 条)。服务端每 20 秒发一次心跳,60 秒没收到任何帧就当掉线。
写这个不难,但有一个坑要记住:nginx 反代 WebSocket 必须带 Upgrade 和 Connection 头,不然网页能开、对局连不上。示例配置里已经写好了。
3. 进入密码,而且“没登录就不下发游戏代码”
客户要求只让知道密码的人进。我做成:
- 没登录时,访问
/只返回login.html,里面没有任何游戏代码; POST /login输对密码后,发一个HttpOnly、SameSite=Strict的登录 cookie(走 HTTPS 时加Secure),之后才下发游戏页;- WebSocket 连接也靠这个 cookie 放行,没登录会收到“需要登录”然后被断开;
- 密码比较用
hmac.compare_digest,每次输错还会让接口多等 1 秒。
连续输错会封 IP:默认输错 5 次封 1 小时,再次被封时长翻倍,最长一天(GOMOKU_MAX_FAIL、GOMOKU_BAN_SEC 可调)。gomoku.sh 菜单里能看到被封的 IP、手动解封,不用重启,也不影响正在进行的对局。这个管理接口只接受“本机直连”的请求,经过 nginx 的一律 404,公网访问不到。
这是一个共享密码,不是账号系统:知道密码的人都能进,适合“发给熟人朋友”的场景。
4. 其他防刷
这是公网服务,所以多叠了几层,都写在代码或配置里:
| 位置 | 限制 |
|---|---|
| 服务端 | 每个 IP 最多 20 条连接、每分钟最多新建 30 条 WebSocket、每分钟最多 120 次网页请求 |
| 服务端 | 每个 IP 最多开 10 个房间,全站最多 1000 个房间 |
| 服务端 | WebSocket 握手时核对 Origin,拒绝别的网站的页面来连 |
| 服务端 | 打字聊天去掉控制字符和看不见的字符(包括会改变文字方向的),昵称去掉 < > |
| nginx | limit_req 按 IP 限速:登录每分钟 10 次、页面 60 次、WebSocket 30 次;limit_conn 限并发 |
有一点要提醒:服务端按 X-Real-IP 统计真实 IP,只信任来自本机(nginx)的这个头。如果你直接把服务端口开到公网,别人可以伪造它。所以默认只监听 127.0.0.1,要对外一定前面放 nginx。
5. 中国象棋的规则写了两份,用走法计数互相校验
象棋规则最容易出错:蹩马腿、塞象眼、炮隔子打、将帅不能对面、送将不合法……
服务端要判定,页面又要显示可走的位置、给 AI 用,所以我把规则写了两份(Python 和 JavaScript),并且用 perft(走法计数) 互相校验:从开局出发,数一数走 N 步一共有多少种合法走法,两边必须一致。
我在本机把两份都跑了一遍:开局走 1、2、3 步,Python 和 JavaScript 的结果都是 44、1920、79666,两份一致,也和公开的象棋 perft 参考数值一致。
6. AI 放在浏览器的 Web Worker 里
AI 在 Web Worker 里计算,不卡页面,也不占服务器。三个游戏各有一套:
- 五子棋:按五格窗口给局面打分,用负极大值搜索,再加“连续冲四”的必杀搜索(VCF)。“入门”和“普通”是在候选点里按权重随机挑,所以会漏招;“困难”最深 6 层、限时 1.5 秒,“大师”最深 24 层、限时 4.5 秒。
- 中国象棋:迭代加深的 alpha-beta 搜索,加置换表、静态搜索、杀手走法和历史启发。“入门”只看一步再加随机扰动,“普通”看两步,“困难”最深 7 层、限时 1.5 秒,“大师”最深 20 层、限时 4.5 秒。
- 反转棋:负极大值搜索,处理“一方没地方下要跳过”的情况。
各档的搜索深度、时间都集中写在代码里的 LEVELS 表里,想调强弱改那里就行。
部署到线上
反代的核心几段(完整的在 nginx-gomoku.conf):
# 对局用的 WebSocket:必须带 Upgrade / Connection 头
location /ws {
limit_req zone=gomoku_ws burst=20 nodelay;
limit_conn gomoku_conn 20;
proxy_pass http://127.0.0.1:8765;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host; # 服务端用它核对 Origin
proxy_set_header X-Real-IP $remote_addr; # 没有这一行,所有玩家会被当成同一个 IP
proxy_read_timeout 3600s; # 对局可能很久
proxy_buffering off;
}步骤:
- 把整个文件夹传到服务器,比如
/opt/game-lobby; cp .env.example .env,设密码,BIND=127.0.0.1、PORT=8765;- 把
nginx-gomoku.conf里的game.example.com换成你的域名,证书路径换成你的,放进/etc/nginx/conf.d/; nginx -t && nginx -s reload,DNS 把域名指到服务器;./gomoku.sh start。
如果前面还有 Cloudflare 这类代理,要确认它开着 WebSocket(大多数默认就开了),并且服务端拿到的“真实 IP”是访客而不是代理的地址,否则限流和封 IP 会误伤一片。这一点我没在你的环境里测,配完请自己试。
局限,用之前先知道
- 状态都在内存里。 重启服务,进行中的对局和封禁记录会清空;登录 cookie 也会失效,玩家要重新输一次密码。
- 只有一个进程。 适合朋友圈、小社群这种规模的小场子,不适合做成大平台。
- 没有账号、没有战绩、没有排行榜。 昵称是自己取的,不保证唯一。
- 共享密码不是账号。 想按人管理,要自己加账号体系。
- 页面里的品牌名“哦耶耶游戏”和底部“快建工作室”字样是原项目的,拿去用请自己换掉(
index.html、login.html)。 - 我没有在多台设备、不同网络、长时间挂机的条件下做过完整的压力测试;上线前自己多开几个浏览器对下一局最稳妥。
想要一个自己的游戏大厅?
这类小而完整的网页项目正是我平时做的事:你说想要哪几个游戏、要不要密码、挂在哪个域名下,我来做、部署、交付源码。想聊聊的话,来官网留言。
出处
- 源码包:game-lobby.zip(MIT 许可)
- WebSocket 协议:RFC 6455
- 走法计数(perft):Chess Programming Wiki:Perft
有想法或问题?去留言板留言 →