Commit Graph

21 Commits

Author SHA1 Message Date
180be25ffa fix: code review — 修复4个问题
1. CardTracker.UnknownCount: 48→52 (硬编码修正)
2. IsmctsBot.GenerateDeck → 静态FullDeck池复用 (减少对象分配)
3. CardTracker: 删除3个死字段(_passStreak/_isNewRound/_bombCounts)
4. CardTracker.Update: 清理pass约束记录逻辑

验证: 100局0错误, 0.9s/局
2026-07-13 00:21:15 +08:00
7fdc45057c feat: 领牌保留策略 — 有绝对最高牌(2)时出最小的
问题: 玩家1有2+3+9, 领牌时出了9而非3
→ ISMCTS看不见'保留9未来组对子'的两步深度

修复:
- 有2时领牌单张: rank≤6→+15%, 7-10→+5%, J-K→-5%, A→-10%
- 核心逻辑: 有2能随时夺回控制权→出最小的→保留中牌组未来组合

效果: 单张更聪明(51%但质量高), ShunZi 11.6%, SanDai2 9.1%
2026-07-13 00:11:33 +08:00
1a97ea3d21 fix: afterMin权重强化 — 结构优化信号对抗模拟噪声
问题: 如玩家1手牌可拆2个顺子(6步完成), 但ISMCTS选择了8张顺子(7步)
→ 根因: 打散惩罚仅-3%, 被ISMCTS±3.5%噪声淹没

修复:
- 结构改善bonus: 12%→25% (对抗模拟噪声)
- 多牌打散penalty: 固定-3%→比例-30% (严重惩罚)
- 单张打散penalty: 新增-10%(之前无惩罚)
- 打散比例 = (afterMin-beforeMin)/beforeMin

效果: SanDai2 6.5%→8.1%(+25%), DanZhang-1.4%, 多牌54%
2026-07-13 00:05:22 +08:00
df048f6266 feat: EndgameSolver — 终局精确求解替代ISMCTS采样
EndgameSolver.cs (新文件):
- 触发条件: 手牌≤5张 + 未知牌≤15张
- 枚举100种对手手牌分布 → 每分布贪心模拟到底 (深度限制30)
- 比 ISMCTS 更精确: 确定性模拟 vs 随机rollout
- 结果: 8/10局触发, 每局5-15次, 速度快(<50ms)

修复: candidates[1]越界
2026-07-12 23:06:32 +08:00
76e4133f58 feat: afterMin牌效率加权 — 出牌后手牌结构改善→加分
- beforeMin = 当前手牌最小步数
- afterMin < beforeMin → +12% × improvement率
- afterMin > beforeMin 且多牌 → -3% (打散手牌惩罚)
- 效果: 单张-1.2%, 三带二+0.9%, 飞机×3, 锁手+2次
2026-07-12 22:56:27 +08:00
4263146487 fix: code review — 清理CardTracker死代码 + IsmctsBot未使用变量
CardTracker.BombsOutside: 删除13行注释+空循环(seenByRank从未填充)
IsmctsBot: 删除未使用的bombRanks变量
2026-07-12 22:51:48 +08:00
cb33dcac6c feat: LockDetector — 残局锁手必胜序列检测
LockDetector.cs (新文件, 170行):
- BFS搜索: 手牌≤8张时寻找无人能压制的出牌序列
- 硬锁: 每步绝对无人可挡→跳过ISMCTS,直接走必胜路径
- 支持5种牌型: 炸弹/对子/三张/三带二/顺子/单张
- 利用CardTracker.CanAnyoneBeat()判断压制可能性
- 优先大牌型(炸弹>顺子>三带二>对子>单张)

IsmctsBot集成:
- DecidePlay开头: hand≤8→调用LockDetector
- 发现锁手→跳过ISMCTS模拟,直接出牌(Console打LOG)
- Type=CardType1.None交给引擎GetOutCard重新算

生效条件: 残局+CardTracker累积足够已知牌信息+手牌有压制力
2026-07-12 22:40:49 +08:00
e67b06caa3 feat: CardTracker — 出牌记录+对手手牌推断+炸弹检测
CardTracker.cs (新文件, 400行):
- 48位bitset追踪已出牌(按ID)
- 对手pass约束记录: pass对子9→该对手无对子≥9
- BombsOutside(): 外面还剩几个炸弹
- BombRanks(): 可能藏炸弹的rank列表
- CanAnyoneBeat(play): 判断是否可能被压制
- SampleOpponentHands(): 约束采样替代随机分配
- 支持6种牌型的压制判断: 单张/对子/炸弹/顺子/连对/飞机

IsmctsBot.cs 整合:
- CardTracker实例,每次DecidePlay/DecideBaoZhuang调用Update()
- 炸弹感知加权: 外面没炸弹→我方炸弹+25%, 快赢时+15%
- 外面有炸弹且快赢→保守出牌(-5%多牌分)
- SimulateToEnd改用约束采样(回退随机补齐)
2026-07-12 22:36:22 +08:00
2f3b62e835 feat: 最小步数规划 + 自适应模拟 + 快速初筛
HandOptimizer.cs (新文件):
- DP 搜索手牌最优拆分, 计算最少出牌次数
- 支持全部牌型: 单张/对子/三张/三带二/顺子/连对/飞机/炸弹/四带二/四带三/飞机带对
- Analyze() 返回最优分组详情
- PlayEfficiency() 评估单次出牌的牌效率

IsmctsBot.cs 改进:
- 包庄决策加入 MinPlays 分析, 手牌散(≥7步)直接不包, 手牌好(≤3步)阈值降至50%
- 自适应模拟: 3张→2000, 4-6→1500, 7-10→1200, 11-13→1000, ≥14→800
- 快速初筛: 40次模拟定位好候选, 胜率>30%才追加模拟
- 多牌候选选择逻辑改为 top90% 阈值(之前无脑选多牌)
- 出牌时计算剩余手牌 MinPlays 辅助候选评估
- 简化 multiCount 追踪(改为 Any(c=>c.isMulti))

压测: 50局 0错误, avg 0.9s/局
2026-07-12 22:26:29 +08:00
aa30bfac29 tune: 包庄阈值 60%→70% + 多牌加权 +10%/张
包庄: 100局校准 60%阈值→30%成功率 → 提至70%(更保守)

多牌: 加 MultiBonusPerCard=0.10 加权,但实际效果有限——
两阶段选择已经无条件优先多牌。53%比例接近16张手牌
结构的天然上限(约一半牌可组组合,其余为散牌)

10局验证: DanZhang 88|DuiZi 61|ShunZi 19|SanDai2 10|LianDui 3|SiDai3 2
2026-07-07 14:51:44 +08:00
b39e28411b refactor: 修复3个🟡问题 — 合并模拟方法/移除死参数/缓存GetMultiGroups
1. SimulateWin+SimulateOneGame → SimulateToEnd(nullable firstPlay)
   减少 ~80行重复代码, 包庄/出牌共享同一模拟引擎

2. 移除 unknownCount 死参数 (SimulateOneGame 从未使用)

3. GetMultiGroups(hand,2) 只调一次 — 对子+连对共享 pairs 变量

文件: IsmctsBot.cs 583行 → 331行 (-43%)
2026-07-07 14:43:13 +08:00
ce2a4c98ef 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 差异+集成步骤)
  + 附牌策略说明(引擎允许任意牌做挂件)
2026-07-07 14:39:50 +08:00
54d9f3add3 compat: switch expression → ternary for .NET 4.8 双兼容
IsmctsBot.cs:539: C#8 switch expression → C#7.3 ternary
  num switch {1=>14,2=>16,_=>num} → num==1?14:num==2?16:num

bot代码(.NET 4.8)兼容性清单:
   LINQ (GroupBy/OrderBy/SelectMany) — 原生
   string 插值 $"..." — C#6
   ?./?? — C#6
   ValueTuple — 需 NuGet System.ValueTuple (net48)
   已移除 switch expression — 唯一 C#8 特性

net48 项目集成只需3步:
  1. 复制 PdkBot.cs + IsmctsBot.cs → PdkFriendServer/Logic/
  2. csproj: <PackageReference Include="System.ValueTuple" Version="4.5.0"/>
  3. WaitWanJiaShuRu 加入 BotMode 分支(同 net10.0 逻辑)
2026-07-07 14:36:13 +08:00
d520534b5b fix: InferType — ISMCTS模拟Type不再为None,所有牌型正确推演
根因: MakePlay/AddCandidate设Type=None,导致SimulateOneGame中
myPlay.Type=None, GetTipCard不匹配任何case→对手全员pass→评估失真

修复:
- 新增 InferType(): 根据牌数和面值分布推断牌型
  1张→DanZhang 2张→DuiZi 4同→ZhaDan 4+2→SiDai2
  4+3→SiDai3 3+2→SanDai2 ≥5→顺子/连对
- AddCandidate+MakePlay 均使用 InferType 替代 CardType1.None
- ISMCTS模拟中对手能正确识别我的出牌类型并跟牌

10局验证: FeiJi首次出现(2次), 全牌型:
  DanZhang 72 | DuiZi 47 | ShunZi 11 | SanDai2 10 | LianDui 5 | FeiJi 2 | ZhaDan 1
多牌比例: 51%
2026-07-07 14:32:13 +08:00
6299e4e580 feat: AddMultiCardLeads 补全 — 连对/飞机/飞机带对
新增枚举:
- 连对(LianDui): 提取对子 → 排序 → 找≥3连续段(如334455)
- 飞机(FeiJi): 提取三张 → 排序 → 找≥2连续段(如333444)
- 飞机带对(FeijiDai2): 连续三张 + 每组附带1对子

辅助方法: SortByGameNum/AddConsecutiveGroups/GetConsecutiveRuns

10局验证: LianDui 5次出现
完整牌型: DanZhang 76 | DuiZi 37 | ShunZi 17 | SanDai2 7 | LianDui 5 | SiDai3 1
(飞机需≥2连续三张,16张牌罕见但逻辑已就位)
2026-07-07 14:28:49 +08:00
2dd0bc3f78 feat: AddMultiCardLeads 补全 — 三带二/四带二/四带三
引擎支持(GetOutCard已验证): DanZhang/DuiZi/SanDai2/ShunZi/LianDui/
  FeiJi/ZhaDan/SiDai2/SiDai3/FeijiDai2

AddMultiCardLeads 新增:
- 三带二: 三张相同 + 附带手牌最小的2张
- 四带二: 四张相同 + 附带2张 (优先四带三)

10局验证: DuiZi 54 | DanZhang 53 | ShunZi 16 | SanDai2 13 | SiDai 2
单张比例: 100% → 38%
2026-07-07 14:22:48 +08:00
5547f75b5a fix: 多牌先手枚举 — 根除只出单张问题
根因: GetTipCard 在 GameNum≤0(新轮次)时只返回单张候选,
不包含对子/三张/顺子/炸弹等多牌先手方案。

修复:
- 新增 AddMultiCardLeads(): 新轮次枚举全部多牌先手
  - 对子 (GetMultiGroups:2) 三张 (3) 顺子(PokerLogic.GetShunZi) 炸弹(PokerLogic.GetPlayZhaDans)
- GetMultiGroups(): 统计同面值牌数,生成多牌候选
- 两阶段选择: 多牌候选优先,按牌数多少×胜率排序
- GetOutCard集成: 确保引擎收到正确牌型(DuiZi/ShunZi而非假DanZhang)

10局验证: DanZhang 59 | DuiZi 33 | ShunZi 16 | ZhaDan 1
多牌比例: 46% (之前0%)
2026-07-07 14:19:38 +08:00
3ef9d7f29e fix: GetOutCard 集成 — bot 出牌类型不再硬编码 DanZhang
根因: MakePlay 永远设 Type=DanZhang,bot 路径跳过 GetOutCard 校验,
导致多牌组合(对子/顺子等)被错误标为单张。

修复:
- MakePlay: Type=DanZhang → Type=None(占位,等引擎赋值)
- WaitWanJiaShuRu case4: bot 有Ids时通过 GetOutCard 获取正确牌型
- payCard.Pos 补设(GetOutCard 输出 Pos 默认为0)
- ISMCTS 日志: 按 Ids 为空判断 pass,而非 Type

现在游戏日志正确显示: type:DanZhang/type:None/type:DuiZi 等
2026-07-07 14:07:35 +08:00
4c21e5d8b5 fix: ISMCTS v2 — 包庄阈值校准 + 多牌组合Rollout + 防死循环
P0修复: 包庄严重高估
- 阈值 45%→60%(1000局数据校准)
- Rollout 对手从「最小单张」改为 PickBestTip(最大+多牌优先)
- 模拟对手更强 → P(win) 估计更准确

P2修复: 只出单张不组合
- 新增 PickBestTip(): 优先多牌组合(对子/顺子 > 单张)
- 同数量选最大的牌
- Rollout 模拟中所有玩家均受益

P1确认(虚警): 75 moves 异常局实为碾压局(高牌玩家清16张)
- 加 pass streak 检测:连续 2 pass 则强制最大出牌
2026-07-07 13:59:49 +08:00
d23e0b7616 fix: ISMCTS包庄决策——600次模拟估算P(win), 阈值45%
替换占位逻辑(大牌≥6张)为真正的蒙特卡洛评估:
- EstimateWinProbability(): 模拟N局完整对局, 统计我第一个出完的概率
- 阈值45%: 包庄收益翻倍, 略微倾斜于有利局势
- 日志: [ISMCTS posX] 包庄评估: P(win)=XX% → 包庄/不包

原理: 包庄不改变牌局流程(不像斗地主叫地主), 只翻倍输赢。
因此决策简化为 P(win|我的手牌) ≥ 阈值 → 包庄。
2026-07-07 13:29:36 +08:00
a75733e4ae feat: ISMCTS bot + full documentation
新增:
- PdkFriendServer/Logic/IsmctsBot.cs: ISMCTS蒙特卡洛搜索机器人
  - 800次模拟/决策,对手手牌从未知牌池随机采样
  - 包庄决策:大牌(≥J/Q/K/A/2)数量 ≥6 则包庄
  - winRate日志输出,决策透明
- docs/bot-architecture.md: 系统架构文档
- docs/bot-algorithms.md: A/B/C三条算法路径完整分析
- docs/ismcts-design.md: ISMCTS实现设计详案

切换:
- PdkGameMain.InitBots: PdkBot → IsmctsBot(默认ISMCTS)

验证:
- dotnet build 0 errors
- 多人对局完整运行:包庄→出牌→结算→test over
2026-07-07 13:27:00 +08:00