Add support for up to 6 players.
This commit is contained in:
+70
-21
@@ -32,24 +32,36 @@ func forcePlayable(id string) func() {
|
||||
|
||||
func playBotGamePack(t *testing.T, pack string, levelA, levelB float64) *game.Game {
|
||||
t.Helper()
|
||||
defer forcePlayable(pack)()
|
||||
return playBotTable(t, []string{pack}, levelA, levelB)
|
||||
}
|
||||
|
||||
// playBotTable drives a full game with a bot in every seat — one per level
|
||||
// given, so it covers tables of two, four, or six — the same way the server
|
||||
// would: observe on every state change, then act when input is owed. It fails
|
||||
// the test if a bot ever produces an illegal action or the game stops making
|
||||
// progress.
|
||||
func playBotTable(t *testing.T, packs []string, levels ...float64) *game.Game {
|
||||
t.Helper()
|
||||
for _, pack := range packs {
|
||||
defer forcePlayable(pack)()
|
||||
}
|
||||
g := game.New()
|
||||
pa, err := g.AddBot("Bot A", levelA)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBot A: %v", err)
|
||||
bots := map[string]*Bot{}
|
||||
mems := map[string]*Memory{}
|
||||
for i, level := range levels {
|
||||
p, err := g.AddBot(fmt.Sprintf("Bot %c", 'A'+i), level)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBot %d: %v", i, err)
|
||||
}
|
||||
bots[p.ID] = New(level)
|
||||
mems[p.ID] = &Memory{}
|
||||
}
|
||||
pb, err := g.AddBot("Bot B", levelB)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBot B: %v", err)
|
||||
}
|
||||
if err := g.SetPack(pack); err != nil {
|
||||
t.Fatalf("SetPack %s: %v", pack, err)
|
||||
if err := g.SetPacks(packs); err != nil {
|
||||
t.Fatalf("SetPacks %v: %v", packs, err)
|
||||
}
|
||||
if err := g.StartGame(); err != nil {
|
||||
t.Fatalf("StartGame: %v", err)
|
||||
}
|
||||
bots := map[string]*Bot{pa.ID: New(levelA), pb.ID: New(levelB)}
|
||||
mems := map[string]*Memory{pa.ID: {}, pb.ID: {}}
|
||||
|
||||
observe := func() {
|
||||
for _, p := range g.Players {
|
||||
@@ -128,6 +140,43 @@ func TestBotsFinishGames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBotsFinishMultiplayerGames plays complete four- and six-bot games on
|
||||
// combined packs — the multiplayer setup the rulebook calls for. Beyond the
|
||||
// usual "no illegal action" safety net, it exercises the bot against a rotating
|
||||
// opponent (a different rival every round), several battles resolving in one
|
||||
// round, and a card pool spanning more than one pack.
|
||||
func TestBotsFinishMultiplayerGames(t *testing.T) {
|
||||
tables := []struct {
|
||||
name string
|
||||
packs []string
|
||||
levels []float64
|
||||
}{
|
||||
{"4p", []string{"turtle", "golden"}, []float64{1, 0.6, 0.25, 0.6}},
|
||||
{"6p", []string{"turtle", "golden", "unicorn"}, []float64{1, 0.6, 0.25, 0, 1, 0.6}},
|
||||
}
|
||||
for _, tc := range tables {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
g := playBotTable(t, tc.packs, tc.levels...)
|
||||
if g.Round != game.MaxRounds {
|
||||
t.Errorf("game ended on round %d, want %d", g.Round, game.MaxRounds)
|
||||
}
|
||||
// Every seat should have fought all six rounds, so the trophies in
|
||||
// play must add up to the six battles per seat-pair.
|
||||
total := 0
|
||||
for _, p := range g.Players {
|
||||
total += p.Trophies
|
||||
}
|
||||
maxPossible := len(tc.levels) / 2 * (game.MaxRounds + 1) // round 6 pays double
|
||||
if total > maxPossible {
|
||||
t.Errorf("%d trophies awarded, only %d were available", total, maxPossible)
|
||||
}
|
||||
if len(g.WinnerSeats) == 0 {
|
||||
t.Error("a finished game should name at least one winner")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBotsFinishGoldenGame plays complete games on the Golden pack (tiers 1-3
|
||||
// printed; 4-6 empty). It exercises the Trumpet/Golden Retriever/Cone Snail
|
||||
// battle mechanics via SimulateBattle rollouts and the new shop effects, and
|
||||
@@ -233,11 +282,11 @@ func assertModelMatches(t *testing.T, mem *Memory, opp *game.Player) {
|
||||
want[c.Name]++
|
||||
}
|
||||
got := map[string]int{}
|
||||
for _, c := range mem.Opp.Known {
|
||||
for _, c := range mem.Opp(1).Known {
|
||||
got[c.Name]++
|
||||
}
|
||||
if len(mem.Opp.Hidden) != 0 {
|
||||
t.Errorf("model has %d hidden cards, want 0 (everything was public)", len(mem.Opp.Hidden))
|
||||
if len(mem.Opp(1).Hidden) != 0 {
|
||||
t.Errorf("model has %d hidden cards, want 0 (everything was public)", len(mem.Opp(1).Hidden))
|
||||
}
|
||||
for name, n := range want {
|
||||
if got[name] != n {
|
||||
@@ -306,8 +355,9 @@ func TestDecideShopNeverSellsLastPet(t *testing.T) {
|
||||
Round: game.MaxRounds, // alpha == 1: score is win-now only
|
||||
MaxRounds: game.MaxRounds,
|
||||
MaxPets: game.MaxPets,
|
||||
Pack: game.DefaultPack,
|
||||
Packs: []string{game.DefaultPack},
|
||||
YouSeat: 0,
|
||||
YourOpponent: 1,
|
||||
Turn: 0,
|
||||
PrioritySeat: 0,
|
||||
DeckCounts: make([]int, game.MaxRounds+1),
|
||||
@@ -319,9 +369,8 @@ func TestDecideShopNeverSellsLastPet(t *testing.T) {
|
||||
}
|
||||
// Model a crushing opponent so every simulated battle is a loss.
|
||||
mem := &Memory{}
|
||||
mem.Opp.Seat = 1
|
||||
for i := range 5 {
|
||||
mem.Opp.Known = append(mem.Opp.Known,
|
||||
mem.opp(1).Known = append(mem.opp(1).Known,
|
||||
game.Card{ID: fmt.Sprintf("o%d", i), Kind: game.KindPet, Name: "Wall", Tier: 1, Power: 50})
|
||||
}
|
||||
|
||||
@@ -360,8 +409,9 @@ func TestDecideShopKeepsHealthyBoard(t *testing.T) {
|
||||
Round: 3, // mid-game: future value still carries real weight
|
||||
MaxRounds: game.MaxRounds,
|
||||
MaxPets: game.MaxPets,
|
||||
Pack: game.DefaultPack,
|
||||
Packs: []string{game.DefaultPack},
|
||||
YouSeat: 0,
|
||||
YourOpponent: 1,
|
||||
Turn: 0,
|
||||
PrioritySeat: 0,
|
||||
DeckCounts: make([]int, game.MaxRounds+1),
|
||||
@@ -379,9 +429,8 @@ func TestDecideShopKeepsHealthyBoard(t *testing.T) {
|
||||
// A comparable opponent, so battles are genuinely competitive — selling is
|
||||
// a real temptation, not a hopeless-position tie-break.
|
||||
mem := &Memory{}
|
||||
mem.Opp.Seat = 1
|
||||
for i, n := range []string{"Dog", "Sheep", "Ant", "Cricket"} {
|
||||
mem.Opp.Known = append(mem.Opp.Known, pet(fmt.Sprintf("o%d", i), n, 3, 3, game.SuitRed))
|
||||
mem.opp(1).Known = append(mem.opp(1).Known, pet(fmt.Sprintf("o%d", i), n, 3, 3, game.SuitRed))
|
||||
}
|
||||
|
||||
bot := New(1.0) // a capable bot should almost never make this trade
|
||||
|
||||
Reference in New Issue
Block a user