138 lines
4.2 KiB
Go
138 lines
4.2 KiB
Go
// 大富翁核心自测: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)
|
||
}
|
||
}
|