Egerion/open-atomic-bomberman-97
GitHub: Egerion/open-atomic-bomberman-97
用现代 C++20 净室重写 1997 年经典游戏 Atomic Bomberman,忠实还原玩法核心并新增了基于回滚 netcode 的互联网联机功能。
Stars: 1 | Forks: 0
# 打开 Bomberman
对 **Atomic Bomberman**(Interplay, 1997)的一次净室、现代 **C++20** 重写——这是一个确定性的重新实现,使用*你自己的*游戏副本资源来渲染原版游戏,并为这款 1997 年的游戏添加了**互联网多人游戏**功能,这是原版从未以能在现代网络中存续的形式提供的。
/` 中——**不会触碰你的安装** | 否 |
| `make run` | 构建,然后从构建目录运行 | 否(自动检测) |
| `make deploy` | 构建,然后将 `OPEN-BM95.exe` 复制**到你的安装目录中**,以便它在原始资源旁边运行(加上 `SDL3.dll`,仅当你配置了动态 SDL 时) | 是 |
| `make survey` | 使用 `abtool` 验证你安装的资源 | 是 |
| `make test` | 构建**完整**游戏(与 `make build` 使用相同的预设——*不是*无头模式)并运行测试套件 | 否 |
`deploy` 和 `survey` 如果你传递了路径,则从 `GAME_DIR=` 获取路径,否则
从 `gamedir.txt` 获取。如果两者都没有提供,它们会停止并告诉你,而不是猜测目录并写入令人意外的地方。
上面的每个目标共享一个构建树——Windows 上是 `windows-fetch`,
其他地方是 `build/make`——因此所有这些目标(包括 `make test`)都会构建 SDL3
游戏和查看器。无头配置是你直接调用的单独预设
(`cmake --preset headless`);这就是 `bash scripts/test.sh` 和
pre-push 钩子所使用的配置。
## 运行
`OPEN-BM95`(游戏)——可玩本地 + 在线模式:
```
OPEN-BM95 # auto-detects the game install, boots to the menu
OPEN-BM95 # explicit install + scheme
```
玩家 0:方向键 + Right Ctrl/Space(炸弹),Right Shift(投掷/抓取/触发/拳击) · 玩家 1:WASD + Left Ctrl/E,Left Shift · Esc:退出。如果找不到你的安装目录,它会提示你——请参见上面的*首次设置*。
### 在线多人游戏
**从菜单:** **Start Network Game** 会打开包含六行的 *Network Game* 列表:
| 行 | 需要服务器? | 它的作用 |
|---|---|---|
| **Host Private Game** | 是 | 创建大厅并显示 6 个字符的**代码**。将其读给朋友听。 |
| **Host Public Game** | 是 | 同上,但也会列在 *Browse Public Games* 下供任何人查找。仍然可以通过代码加入。 |
| **Join by Code** | 是 | 输入房主的 6 个字符代码。 |
| **Browse Public Games** | 是 | 列出开放的**公开**大厅——名称、玩家数/座位数、代码。按 `Enter` 加入,`R` 刷新,`Esc` 返回。构建版本与你不同的行会变灰并标记为 `VERSION`;它们无法加入(服务器会拒绝)。 |
| **Host LAN Game** | 否 | 在 UDP 端口 8000 上托管并等待(原版的直接路径)。 |
| **Join by IP Address** | 否 | 输入房主的 `IP:port`(默认为 `127.0.0.1:8000`)。 |
这四个**在线**行都会进入同一个**等候室**:大厅代码显示在窗口标题中,名册列出每个座位(名称、房主标记、准备状态),按 `Space` 可切换你的准备标志,房主按 `Enter` 开始,按 `Esc` 离开。房主开始后,对端会打通直接的 UDP 路径(STUN/打洞)——如果它们的 NAT 拒绝,则回退到服务器中继——比赛以**服务器**的种子和座位分配开始。在比赛开始前,直接路径必须在**双向**都证明自己:如果一方能听到你但你听不到对方,则双方将被发送到中继,而不是开始一场只有单方面能玩的比赛。在顺畅打洞的情况下,该检查只需几分之一秒;在恶劣的 NAT 环境下,这就是为什么你现在会多看一会儿连接对话框,而不是看着比赛开始后却纹丝不动。**Join Network Game**(下方的菜单行)仍然是直接的 `IP:port` 加入,保持不变。
#### 匹配器配置
在线行与信令服务器(`services/matchmaker`,一个小型的 Go 二进制文件)通信,该服务器从不模拟游戏——它只负责在对端之间进行介绍,并在它们的 NAT 拒绝直接路径时中继它们的数据包。**已部署公共实例**,因此在线行可以开箱即用。该 URL 的解析顺序为 CLI 标志 → 环境变量 → 编译时默认值:
```
OPEN-BM95 --matchmaker ws://127.0.0.1:8080/ws # 1. CLI flag (e.g. your own server)
set BOMBER_MATCHMAKER_URL=ws://127.0.0.1:8080/ws # 2. environment
# 3. wss://…fly.dev/ws — the deployed
# default (game_app.cpp)
```
UDP **STUN** 回显的解析方式相同(`--matchmaker-stun `、`BOMBER_MATCHMAKER_STUN_HOST` / `BOMBER_MATCHMAKER_STUN_PORT`),并默认指向匹配器 URL 本身所在主机的 **808 端口**。要在本地运行服务器:
```
go build -o mm.exe ./services/matchmaker/cmd/matchmaker && ./mm.exe
```
当设置了 `BOMBER_MATCHMAKER_URL` 时,`ctest -R net_lobby_live` 会对正在运行的服务器测试整个大厅堆栈(否则跳过)。
**从 CLI**(两个对端,相同的种子 → 完全相同的竞技场):
```
OPEN-BM95 --host 8000 127.0.0.1 8001 --seed 0x1234
OPEN-BM95 --join 8001 127.0.0.1 8000 --seed 0x1234
```
对于跨机器游戏,请将 `127.0.0.1` 替换为另一台机器的局域网 IP(端口/种子相同)。对端会在每个 tick 交叉检查 `state_hash`,如果它们的构建版本或配置不同,会大声报告失步。
`abtool`——无头资源检查器/提取器;`bomber_viewer`——SDL3 动画查看器。有关用法,请参见它们的 `--help` / 源码头文件。
## 测试
`ctest` 运行 doctest 套件:确定性(1 万次 tick 锁步)、游戏规则、移动(忠实的 `sub_41EC84` 移植)、疾病、netcode(环回锁步、回滚、真实 UDP 往返、种子握手),以及锁定模拟行为的黄金哈希。针对原始安装的完整验证:`abtool survey` 和 `bomber_viewer --selftest`。
有一个锁定值得一提,因为很容易做出不同的假设:**`visual_golden` 对渲染帧进行哈希处理,它不是 pre-push 门禁的一部分。** 它需要 `headless` 预设不构建的 SDL `bomber_game` 目标,并且由于缺少原始安装而在 CI 上被跳过——因此它实际上只有在人类针对自己的副本运行时才会真正运行。在此之前,渲染器的更改是未锁定的。
## Git 钩子
`lefthook.yml` 串联了一个 pre-push 门禁:完整的 `headless` 构建 + `ctest`、覆盖整个 repo 的 `clang-tidy` 检查(配置在 `.clang-tidy` 中)、针对 push 所更改行的 `clang-format` 检查(配置在 `.clang-format` 中),以及函数复杂度检查。每个 clone/worktree 必须启用一次:
```
winget install evilmartians.lefthook # if not already installed
lefthook install
```
可以通过 `bash scripts/test.sh` / `bash scripts/lint.sh` / `bash scripts/format.sh` / `bash scripts/complexity.sh` 手动运行任何检查,或通过 `lefthook run pre-push --force` 运行整个门禁。
其中两个检查特意限定在 push 实际涉及的内容上,原因相同:树中的大部分内容早于这些检查,如果进行全仓库扫描,会将每个真正的更改淹没在机械性的噪音中。
**格式**检查是按行限定的,而不是按文件限定的——只有你编写的行才必须匹配。`scripts/format.sh [base]` 默认指向与 `origin/main` 的合并基点。
**复杂度**检查是一种棘轮机制。它测量 clang-tidy 的*认知*复杂度(它会对嵌套收费,但对于扁平的 `switch` 不收费——对于充满移植调度表的代码库来说,这是正确的倾向),阈值为 25,并与 `scripts/complexity-baseline.txt` 进行比较,后者记录了在添加该检查时已经超标的内容。那些可以保留;没有任何内容可以变得更糟;任何新内容都必须满足该阈值。该列表只会缩小:它最初有 70 个函数(最严重的为 251),现在已减少到 6 个(最严重的为 58)。在重构真正改善了情况后,使用 `scripts/complexity.sh --update` 重新记录——而不是为了清除未通过的门禁。
## 路线图
自 netcode 核心(ADR-0010、ADR-0011)发布以来已交付的内容:可共享的**大厅代码**
和**公开比赛列表**、**跨 NAT 的互联网游戏**(STUN + UDP
打洞,对于打洞无法到达的对称 NAT 情况提供服务器中继)、通过游戏自身屏幕进行的房主权威
**比赛设置**、在房主中继星型拓扑下**单个大厅最多可容纳十台机器**、轮换回合、确定性的
**对端掉线 → AI 接管**,以及大厅聊天。促成这一切的匹配
服务位于 `services/matchmaker` 中并且已部署;游戏
无需配置即可连接到它。它的控制平面**仅限 `wss://`**
(已于 2026-07-26 关闭)——请参见
[`services/matchmaker/SECURITY.md`](services/matchmaker/SECURITY.md) 中的安全审查,该文档
的编写初衷是作为记录而非检查清单。
仍然有待解决:
- **中继仅支持双人游戏。** 当打洞失败时,2 座位的比赛
会回退到服务器转发器;更大的比赛则不能,因为
`RelayedTransport` 仅寻址单个目标座位,而星型拓扑需要
扇出以及客机↔客机反射。如果超过 2 个座位的大厅打洞失败,它会说明情况
并返回菜单,而不是半连接状态。
- **房主迁移**——如果房主掉线,比赛将结束,而不是重新选举
新的中心。设计详见 ADR-0011(§ Risks),并且服务器端已构建完成。
- **无法检测比赛中途断开的路径。** 上述相互检查运行在
*tick 0 之前*;过了那个时间点就没有任何内容会重新验证,因此交接窗口的“两军问题”尾部
仍然是悬而未决的。修复方案的形式(客机 keepalive
加上可切换的传输方式)写在
`docs/online-multiplayer-design.md` § 4.1 中,而不是隐含不说。
- **netcode 在测试中从未遇到过真实的 NAT。** `tests/net` 中的所有内容都
通过环回运行;不对称性是*建模出来的*(黑洞地址),而不是由
硬件产生的。这证明了状态机在给定特定
不对称性的情况下能够收敛——但并不能证明真实 NAT 产生的每一种不对称性看起来都是
那样的。实时游戏目前是这一半证据的唯一来源。
**跨平台**是结构性的,而不是奢望:模拟是
纯整数运算,网络传输是小端序的,并且编译时的 `build_hash` 会在
大厅门口进行检查,并在 tick 0 之前再次检查,因此不匹配的构建会
被大声拒绝,而不是导致失步。对该保证的客观限制:该
摘要是固定参考场景的折叠 `state_hash`,因此它只能准确捕获
这些场景执行的行为。如果在未扩展这些场景的情况下添加新机制,就会敞开大门——这
曾经发生过,也是为什么
`build_hash.cpp` 包含测量过程和一个命名的未覆盖类别的原因。所有三个平台都会在 CI 中构建并运行测试
套件——该矩阵包含 Linux (GCC)、macOS (Clang) 和 Windows (MSVC),每个
预设一个作业。*未*声明的是同等的游戏测试:Windows 是唯一一个
每天针对真实安装进行手动演练的平台,因此请将 Linux 和 macOS
视为“能够构建并通过测试”,而不是“已经打磨完善”。请参见 `docs/adr/`、
`docs/re/network-screens.md` 和 `services/matchmaker/PROTOCOL.md`。
## 法律
Open Bomberman 是一个独立的**净室重新实现**。它**不**附属于、不受认可或与 Interplay Entertainment 或 Konami 有任何关联。
- **不包含任何原始代码或游戏数据。** 本仓库仅包含由贡献者编写的原创源码,这些源码是通过观察数据格式和行为编写的。它不包含任何 Interplay/Konami 代码、音频、关卡数据或任何类型的游戏数据,并且逆向工程工作材料(二进制文件、反汇编、反编译器输出)也从未被提交过(参见 `.gitignore`)。有一点值得一提而不是一带而过:本页面顶部的屏幕截图和动画是*本*引擎运行时的抓图,因此它们确实在屏幕上显示了原作的美术作品。它们是移植版外观的文档——本仓库中的任何内容都不能让你在没有自己游戏副本的情况下进行游戏。
- **你必须拥有原版游戏。** 引擎会在运行时从*你自己的*合法获取的副本中加载 Atomic Bomberman 的数据;它不提供任何此类数据。没有原始安装包,就无法进行游戏。
- **商标。** “Atomic Bomberman”和“Bomberman”以及所有相关名称、徽标、角色和艺术作品均为其各自所有者(Interplay / Konami)的财产。此处仅出于指代目的使用它们,用于描述本软件兼容的内容。
- **许可证。** 本仓库中的原始源码和文档基于 MIT 许可证发布——参见 [`LICENSE`](LICENSE)。该许可证**仅**涵盖贡献者的代码;它不授予对任何第三方名称、商标或资源的任何权利。
如果你是版权持有者并有任何疑虑,请提交 issue。
标签:AI生成代码, Bash脚本, C++20, SDL3, 云资产清单, 回滚网络代码, 日志审计, 游戏开发, 网络多人游戏, 逆向工程