// 大富翁核心自测:AI 自对弈跑完整局,验证流程收敛与状态一致性 package monopoly import "testing" // TestFullGame 四个规则 AI 互相对局 50 盘,必须都能正常结束 func TestFullGame(t *testing.T) { for round := 0; round < 50; round++ { g := NewGame(4) for actions := 0; actions < 5000; actions++ { if g.Phase == PhaseOver { break } seat := g.Turn switch g.Phase { case PhaseRoll: if err := g.Roll(seat); err != nil { t.Fatalf("第 %d 盘掷骰失败: %v", round, err) } case PhaseBuy: if err := g.Buy(seat, AIShouldBuy(g, seat, "hard")); err != nil { t.Fatalf("第 %d 盘购买失败: %v", round, err) } case PhaseUpgrade: if err := g.Upgrade(seat, AIShouldUpgrade(g, seat, "hard")); err != nil { t.Fatalf("第 %d 盘升级失败: %v", round, err) } } g.DrainLog() } if g.Phase != PhaseOver { t.Fatalf("第 %d 盘 5000 步仍未结束(Round=%d)", round, g.Round) } if len(g.Winners) == 0 { t.Fatalf("第 %d 盘结束但没有获胜者", round) } // 校验:出局玩家不应持有地产 for _, tile := range g.Tiles { if tile.Owner >= 0 && g.Players[tile.Owner].Out { t.Fatalf("第 %d 盘出局玩家仍持有地产 %s", round, tile.Name) } } } } // TestEvents 动画事件流:每次掷骰必产生 roll+move 事件且序列合法 func TestEvents(t *testing.T) { g := NewGame(4) for actions := 0; actions < 200 && g.Phase != PhaseOver; actions++ { seat := g.Turn switch g.Phase { case PhaseRoll: g.Roll(seat) evs := g.DrainEvents() if len(evs) < 2 || evs[0].Kind != "roll" || evs[1].Kind != "move" { t.Fatalf("掷骰后事件序列非法: %+v", evs) } if evs[0].D1 < 1 || evs[0].D1 > 6 || evs[0].D2 < 1 || evs[0].D2 > 6 { t.Fatalf("roll 事件点数非法: %+v", evs[0]) } // move 事件的 From/To 必须在棋盘范围内 if evs[1].From < 0 || evs[1].From >= len(g.Tiles) || evs[1].To < 0 || evs[1].To >= len(g.Tiles) { t.Fatalf("move 事件坐标越界: %+v", evs[1]) } case PhaseBuy: g.Buy(seat, true) evs := g.DrainEvents() if len(evs) < 2 || evs[0].Kind != "buy" || evs[1].Kind != "cash" || evs[1].Amount >= 0 { t.Fatalf("购买后事件序列非法: %+v", evs) } case PhaseUpgrade: g.Upgrade(seat, true) evs := g.DrainEvents() if len(evs) < 2 || evs[0].Kind != "upgrade" || evs[1].Kind != "cash" { t.Fatalf("升级后事件序列非法: %+v", evs) } } g.DrainLog() } // DrainEvents 之后必须清空 if len(g.Events) != 0 { t.Fatalf("事件未被取走清空: %d 条", len(g.Events)) } } // TestUpgrade 建筑升级:扣费、等级提升、租金按等级翻倍 func TestUpgrade(t *testing.T) { g := NewGame(2) tl := g.Tiles[1] // 北京 350 tl.Owner = 0 g.Phase = PhaseUpgrade g.Pending = 1 g.Turn = 0 moneyBefore := g.Players[0].Money if err := g.Upgrade(0, true); err != nil { t.Fatalf("升级失败: %v", err) } if tl.Level != 1 { t.Fatalf("等级应为 1,实际 %d", tl.Level) } if g.Players[0].Money != moneyBefore-UpgradeCost(tl) { t.Fatalf("升级扣费错误:%d → %d", moneyBefore, g.Players[0].Money) } // 租金 = 地价30% × 2^等级:350*30% = 105 → 1级 210 if rent := g.rentOf(tl); rent != 210 { t.Fatalf("1 级租金应为 210,实际 %d", rent) } // 升满 3 级后不能再进入升级阶段 tl.Level = MaxLevel if rent := g.rentOf(tl); rent != 105<