TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
7.0 KiB
124 lines
7.0 KiB
# 海贼王 · 斗地主(Win32 / TypePHP 示例)
|
|
|
|
将 `HelgeSverre-libui-sdk/examples/onepiece-doudizhu.php`(libui 版)移植为
|
|
**TypePHP(tpc.exe)AOT 编译的原生 Win32 程序**,参考 `landlord-win32` 的
|
|
「纯 PHP 逻辑 + C++ 薄封装 Win32 绑定」架构。
|
|
|
|
## 特性
|
|
|
|
- 完整保留原作玩法:三大势力(海军 / 七武海 / 四皇)、9 名角色技能、
|
|
叫地主 → 出牌 → 结算全流程,内置 AI 对手与托管。
|
|
- 界面由 Win32 GDI 自绘(海洋渐变背景、势力配色卡牌、对手面板、
|
|
可拖拽手牌、底部操作按钮)。
|
|
- 游戏逻辑 100% 在 PHP 中实现(`php-src/doudizhu/`),与 libui 版共用同一套
|
|
领域模型;C++(`cpp-src/win32.cc`)仅封装窗口、消息循环与 GDI 绘制原语。
|
|
- 单文件 exe,无外部 PHP 依赖,不依赖 libui / mbstring / miniaudio。
|
|
|
|
## 目录结构
|
|
|
|
```
|
|
onepiece-doudizhu-win32/
|
|
├── main.php 入口:Win32 消息循环驱动 GameController
|
|
├── project.yml tpc.exe 构建配置(mode: bin)
|
|
├── build.bat 一键编译脚本(需 VS 2022 x64 工具链环境)
|
|
├── cpp-src/
|
|
│ └── win32.cc Win32 窗口 + GDI 绘制原语(C++ 薄封装)
|
|
└── php-src/
|
|
├── win32.stub.php win_* 原生函数声明(stub)
|
|
└── doudizhu/ 游戏域模型 + 渲染 shim
|
|
├── Card / Deck / Combo / MoveGenerator / Game / Ai / Skill /
|
|
│ Character / Faction / PlayerState 纯逻辑(与 libui 版同源)
|
|
├── GameController.php 对局编排 + Win32 输入/渲染适配
|
|
├── Render.php libui DrawContext 兼容 shim → GDI
|
|
└── Sound.php 音效管理器(TypePHP 版为空实现,保留 API)
|
|
```
|
|
|
|
## 编译
|
|
|
|
在 **x64 Native Tools Command Prompt for VS 2022**(或已加载
|
|
`vcvars64.bat` 的终端)中执行:
|
|
|
|
```bat
|
|
build.bat
|
|
```
|
|
|
|
脚本会:
|
|
1. 校验 `cl.exe` 可用(不在 VS 环境中会提示)。
|
|
2. 从 TypePHP 根目录调用 `tpc.exe project.yml`(tpc 打包后按 CWD 解析
|
|
`vendor/autoload.php`,必须从根目录运行)。
|
|
3. 将产物 `onepiece_doudizhu.exe` 复制回示例目录。
|
|
|
|
等价手动命令:
|
|
|
|
```bat
|
|
set PHP_HOME=D:\git\php\tpc_v1095_windows_x86_64
|
|
set PHPX_HOME=%PHP_HOME%\phpx
|
|
set PATH=%PHP_HOME%;%PATH%
|
|
cd /d D:\git\php\tpc_v1095_windows_x86_64
|
|
tpc.exe examples\onepiece-doudizhu-win32\project.yml --no-progress
|
|
```
|
|
|
|
## 运行
|
|
|
|
```bat
|
|
onepiece_doudizhu.exe
|
|
```
|
|
|
|
- 拖拽手牌选牌,底部按钮:出牌 / 不出 / 提示 / 技能 / 托管。
|
|
- 右上角按钮切换音效(当前为占位实现)。
|
|
- 关闭窗口或 `Esc` 退出。
|
|
|
|
## 移植要点
|
|
|
|
1. **渲染 shim(Render.php)**:原 `GameController` 的绘制代码直接使用
|
|
libui 的 `DrawContext / Brush / Color / FontDescriptor / DrawTextAlign /
|
|
TextWeight`,这些符号在 `php-src/doudizhu/Render.php` 中同命名空间重新声明,
|
|
`WinDrawContext` 把每个调用翻译为 `win_fill_rect / win_fill_ellipse /
|
|
win_fill_rounded_rect / win_stroke_rounded_rect / win_draw_text_ex`。
|
|
渐变近似为纯色填充;颜色统一 `0xRRGGBB → COLORREF(0xBBGGRR)` 转换。
|
|
2. **输入驱动**:libui 的 `AreaDelegate::mouse/key` 改为 `main.php` 主循环
|
|
轮询 `win_peek_message()`,把 `WM_LBUTTONDOWN/UP/MOUSEMOVE/KEYDOWN`
|
|
转换为 `GameController::onMouse()/onKey()` 调用。
|
|
3. **定时器**:`Loop::delay` 改为 `GameController::tick()`,由主循环每帧
|
|
调用,驱动 AI 走子 / 叫分 / 托管(基于 `win_get_tick_count()`)。
|
|
4. **TypePHP 兼容性修正**(相对 libui 版源码):
|
|
- 顶层游离代码(`define('FONT', …)`)包装进 `ensureDdzFont()`,由
|
|
`main()` 调用——TPC 要求所有执行代码位于函数内。
|
|
- `readonly` 属性 / `&$ref` 引用遍历等改为普通属性 / 下标赋值(TPC 语法限制)。
|
|
- `mb_strlen / mb_substr` 改为 `ddz_utf8_len / ddz_utf8_substr`
|
|
(PCRE 实现,TPC 运行时未链接 mbstring)。
|
|
- `Brush::linearGradient()` 签名改为与 libui 一致的
|
|
`(x0,y0,x1,y1, stops)` 形式,避免 TPC 变参类型推断失败。
|
|
5. **双缓冲位图管理(win32.cc)**:修复 `GetStockObject(BITMAP)` 用法错误
|
|
(`BITMAP` 是类型不是常量),改为保存/恢复 DC 原 bitmap 后再删除离屏位图。
|
|
6. **窗口尺寸(win32.cc)**:`win_create_window` 把传入宽高当作**客户区**尺寸
|
|
(`AdjustWindowRect` 自动补上标题栏/边框),保证底部按钮完整可见;窗口
|
|
样式为 `WS_OVERLAPPEDWINDOW`,支持拖拽缩放与最大化。新增
|
|
`win_get_client_size()` 返回当前客户区尺寸,`render()`/`drawButtons()`
|
|
每帧按实际尺寸自适应布局(绘制与点击命中均基于当前尺寸记录的矩形)。
|
|
7. **运行时兼容性**:`MoveGenerator::all()` 的 `$cnt` 闭包改为
|
|
`count($byRank[$r] ?? [])`——原实现 `count($byRank[$r])` 在顺子/连对
|
|
枚举缺失 rank 时对 null 调用 `count()`,TypePHP 运行时(PHP 8 严格类型)
|
|
抛 `TypeError: count(): Argument #1 must be of type Countable|array`。
|
|
8. **窗口标题乱码(win32.cc)**:本机腾讯电脑管家(`tsbx.dll`)会 inline-hook
|
|
进程内 user32 的 `DefWindowProcA` / `CreateWindowExW` / `SetWindowTextW`,
|
|
导致窗口标题被改写为乱码("wm<崑s " 之类)。修复:
|
|
- `DdzWndProc` 末尾显式调用 **`DefWindowProcW`**(而非 `DefWindowProc` 宏,
|
|
后者在非 UNICODE 编译下展开为被 hook 的 `DefWindowProcA`)。
|
|
- `php_win_create_window` 通过 `LoadLibraryW("C:\\Windows\\System32\\user32.dll")`
|
|
+ `GetProcAddress` 解析**原始** `CreateWindowExW` / `SetWindowTextW` 调用,
|
|
创建后再用原始 `SetWindowTextW` 重设一次标题(双保险)。
|
|
- 对照组验证:Explorer / Edge / SmartGit 等其它进程窗口标题读取正常,
|
|
仅本进程自定义 WndProc 窗口受影响,证明是进程内 hook 而非系统/代码问题。
|
|
9. **托管节奏(GameController.php)**:Win32 移植版里 `defer()` 与 `delay()`
|
|
都由主循环 `tick()` 驱动、机制相同。原代码在托管/AI 走子/叫分处同时挂
|
|
`defer`(立即执行)+ `delay`(兜底),导致动作在下一帧瞬间完成,玩家
|
|
来不及点「取消托管」。修复:
|
|
- 去掉托管分支的 `defer`,只保留 **1500ms** 延迟(充足取消窗口)。
|
|
- `scheduleAi()` 去掉 `defer`,AI 出牌节奏改为 **800ms**。
|
|
- `scheduleBidStep()` 去掉 `defer`,AI 叫分节奏改为 **700ms**。
|
|
- `toggleAutoPlay()` 关闭托管时调用 `cancelTimers()` 立即取消挂起的
|
|
托管定时器;`autoPlayStep()` 原有 `!$this->autoPlay` 保护兜底。
|
|
- `toggleAutoPlay()` 关闭托管且轮到玩家时,重新调用
|
|
`setActionsForHumanTurn()` 重算按钮状态——托管时「出牌/不出/提示/技能」
|
|
按 `!autoPlay` 被禁用,恢复后必须重算才能重新可点。
|
|
|