Files

138 lines
4.2 KiB
Go
Raw Permalink Normal View History

2026-08-14 13:17:03 +08:00
// 大富翁核心自测: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<<MaxLevel {
t.Fatalf("满级租金应为 %d,实际 %d", 105<<MaxLevel, rent)
}
// 破产释放地产时等级清零
g.Tiles[2].Owner = 0
g.Tiles[2].Level = 2
g.payTo(g.Players[0], nil, 999999, "测试破产")
if g.Tiles[2].Owner != -1 || g.Tiles[2].Level != 0 {
t.Fatalf("破产后地产应释放且等级清零:owner=%d level=%d", g.Tiles[2].Owner, g.Tiles[2].Level)
}
}
// TestEliminate 中途退出后行动权正常流转
func TestEliminate(t *testing.T) {
g := NewGame(4)
g.Eliminate(0)
if g.Turn == 0 {
t.Fatal("退出者仍持有行动权")
}
g.Eliminate(1)
g.Eliminate(2)
if g.Phase != PhaseOver {
t.Fatal("只剩一人时对局应立即结束")
}
if len(g.Winners) != 1 || g.Winners[0] != 3 {
t.Fatalf("获胜者应为 3 号,实际 %v", g.Winners)
}
}