Files
nl-game-api/internal/gamecore/monopoly/monopoly_test.go
2026-08-14 13:17:03 +08:00

138 lines
4.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 大富翁核心自测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)
}
}