review: code review + 3 critical fixes + 文档更新

Code Review 发现 (docs/code-review.md):
  🔴 InferType 三元两边相同(无意义) → 重写,加 LianDui/FeiJi 分支
  🔴 PdkBotView { get; init; } → { get; set; } (.NET 4.8 兼容)
  🔴 PdkBot.DecideBaoZhuang ≤10永远false → bigCards≥6

文档更新 (docs/pdk-bot.md):
  + 5. 牌型全覆盖表(引擎+Bot枚举覆盖10种牌型)
  + 6. 双环境兼容指南(net48/n10 差异+集成步骤)
  + 附牌策略说明(引擎允许任意牌做挂件)
This commit is contained in:
2026-07-07 14:39:50 +08:00
parent 54d9f3add3
commit ce2a4c98ef
10 changed files with 97 additions and 13 deletions

View File

@ -411,15 +411,26 @@ namespace PdkFriendServer.Logic
int n = cards.Count;
if (n == 1) return CardType1.DanZhang;
if (n == 2) return CardType1.DuiZi;
var counts = cards.GroupBy(c => c.GameNum).Select(g => g.Count()).OrderByDescending(x => x).ToList();
if (counts[0] == 4)
{
if (n == 4) return CardType1.ZhaDan;
if (n == 6) return CardType1.SiDai2;
if (n == 7) return CardType1.SiDai3;
}
if (counts[0] >= 3) return n == counts[0] + 2 ? CardType1.SanDai2 : CardType1.SanDai2;
return n >= 5 ? CardType1.ShunZi : CardType1.DuiZi;
if (counts[0] >= 3)
{
// 三带二 / 飞机 / 飞机带对
return CardType1.SanDai2;
}
if (counts[0] == 2 && n % 2 == 0)
{
// 连对 vs 普通多张:检查是否连续对子
return CardType1.LianDui;
}
return CardType1.ShunZi; // 顺子或其他 ≥3 张组合
}
private static PlayOutCardPdkF MakePlay(List<TCardInfoPdkF> cards, int pos)

View File

@ -14,28 +14,28 @@ namespace PdkFriendServer.Logic
public class PdkBotView
{
/// <summary>我的手牌仅本轮可出的牌GameState==1</summary>
public TCardInfoPdkF[] MyHand { get; init; }
public TCardInfoPdkF[] MyHand { get; set; }
/// <summary>桌上最后一手牌谁打的什么GameNum==0表示桌上无牌/新一轮</summary>
public PlayOutCardPdkF MaxPlayCard { get; init; }
public PlayOutCardPdkF MaxPlayCard { get; set; }
/// <summary>当前轮到谁出牌1-indexed</summary>
public int WhoPlay { get; init; }
public int WhoPlay { get; set; }
/// <summary>我在第几个位置1-indexed</summary>
public int MyPos { get; init; }
public int MyPos { get; set; }
/// <summary>各玩家剩余牌数 [pos0,pos1,pos2]</summary>
public byte[] ShenYuCard { get; init; }
public byte[] ShenYuCard { get; set; }
/// <summary>各玩家已出炸弹数</summary>
public int[] ZhaDans { get; init; }
public int[] ZhaDans { get; set; }
/// <summary>游戏规则</summary>
public PdkRule Rule { get; init; }
public PdkRule Rule { get; set; }
/// <summary>当前游戏阶段 3=包庄 4=打牌</summary>
public int GameZT { get; init; }
public int GameZT { get; set; }
}
/// <summary>
@ -58,8 +58,9 @@ namespace PdkFriendServer.Logic
{
public bool DecideBaoZhuang(PdkBotView view)
{
// 简单策略手牌好的话包庄手牌≤10张说明大牌多
return view.MyHand.Length <= 10;
// 启发式:手牌中 ≥J 的大牌多则包庄(速算,不如 ISMCTS 精确
int bigCards = view.MyHand.Count(c => c.GameNum >= 11);
return bigCards >= 6;
}
public PlayOutCardPdkF DecidePlay(PdkBotView view)

24
docs/code-review.md Normal file
View File

@ -0,0 +1,24 @@
# PdkBot Code Review
## 发现汇总
### 🔴 Critical
| # | 文件 | 行 | 问题 | 影响 |
|---|------|-----|------|------|
| 1 | IsmctsBot.cs | 421 | `InferType` 三元表达式两边相同 `... ? SanDai2 : SanDai2` | 无实际分支 |
| 2 | IsmctsBot.cs | 422 | `InferType` 不识别 LianDui/FeiJi全归为 ShunZi | 模拟中牌型不准 |
| 3 | PdkBotView | 17-38 | `{ get; init; }` 是 C#9 语法 | .NET 4.8 不兼容 |
### 🟡 Warning
| # | 文件 | 行 | 问题 | 影响 |
|---|------|-----|------|------|
| 4 | IsmctsBot.cs | 54-133, 215-307 | `SimulateWin``SimulateOneGame` 80% 重复 | 维护成本高 |
| 5 | IsmctsBot.cs | 173 | `unknownCount` 参数传入 SimulateOneGame 但从未使用 | 死参数 |
| 6 | PdkBot.cs | 62 | `DecideBaoZhuang``MyHand.Length <= 10`16张永远false | 包庄永远不触发 |
| 7 | IsmctsBot.cs | 363 | `GetMultiGroups(hand,2)` 被调两次 | 微性能浪费 |
### 🟢 OK
- `GenerateDeck()` 每次模拟调用 — 可缓存但 8000 次 × 52 元素 = 可接受
- `Pass()` 方法在两处重复 — 设计选择(两个 bot 独立)
- 三带二附带牌策略 — 已验证引擎允许任意牌做挂件
- `PickBestTip` null safety — tips 非空已外层保证

View File

@ -206,7 +206,55 @@ C 路径的技术可行性已验证DouZero 在斗地主上击败所有 344
---
## 5. 运行
## 5. 牌型全覆盖
引擎 `GetOutCard` 已验证支持所有牌型bot `AddMultiCardLeads` + `InferType` 实现完整枚举:
| 牌型 | 引擎 | Bot 先手 | Bot 跟牌 | 说明 |
|------|------|---------|---------|------|
| 单张 DanZhang | ✅ | ✅ GetTipCard | ✅ GetTipCard | |
| 对子 DuiZi | ✅ | ✅ GetMultiGroups(2) | ✅ GetTipCard | |
| 顺子 ShunZi | ✅ | ✅ PokerLogic.GetShunZi | ✅ GetTipCard | ≥5 张连续 |
| 连对 LianDui | ✅ | ✅ GetConsecutiveRuns | ✅ GetTipCard | ≥3 对连续 |
| 三带二 SanDai2 | ✅ | ✅ 三张+最小2张 | ✅ GetTipCard | 附牌不限对子 |
| 四带二 SiDai2 | ✅ | ✅ 炸弹+最小2张 | — | 关牌规则 |
| 四带三 SiDai3 | ✅ | ✅ 炸弹+最小3张 | — | 关牌规则 |
| 炸弹 ZhaDan | ✅ | ✅ GetPlayZhaDans | ✅ GetTipCard | 4 张相同 |
| 飞机 FeiJi | ✅ | ✅ GetConsecutiveRuns | ✅ GetTipCard | ≥2 组连续三张 |
| 飞机带对 FeijiDai2 | ✅ | ✅ 飞机+附对子 | ✅ GetTipCard | |
### 附牌策略
引擎 `IsSanDaiEr` / `IsSiDai2` / `IsSiDai3` 均不要求附牌为对子——允许任意单牌挂件。Bot 取手牌最小剩余牌做附牌,最大化清牌效率。
---
## 6. 双环境兼容(.NET 4.8 / .NET 10
### 6.1 已处理
| 特性 | .NET 4.8 | .NET 10 | 方案 |
|------|----------|---------|------|
| C#8 switch expression | ❌ | ✅ | 改为 ternary |
| C#9 `{ get; init; }` | ❌ | ✅ | 改为 `{ get; set; }` |
| ValueTuple | ⚠️ NuGet | ✅ | net48 加 `System.ValueTuple` 包 |
| LINQ | ✅ | ✅ | 无需改动 |
| string 插值 `$"..."` | ✅ C#6 | ✅ | 无需改动 |
| `?.` / `??` | ✅ C#6 | ✅ | 无需改动 |
### 6.2 net48 项目集成步骤
1. 复制 `PdkBot.cs` + `IsmctsBot.cs``PdkFriendServer/Logic/`
2. `PdkFriendServer.csproj` 加:
```xml
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<LangVersion>8.0</LangVersion>
```
3. `PdkGameMain.cs` 的 `WaitWanJiaShuRu()` 加入 BotMode 分支(与 net10.0 逻辑完全相同)
---
## 7. 运行
```bash
cd ~/projects/hjha-server

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.