Files
super-auto-pets-board-game/internal/game/battle_test.go
T
2026-07-22 23:07:29 -04:00

157 lines
4.4 KiB
Go

package game
import "testing"
// testGame builds a started 2-player game without going through the lobby.
func testGame(t *testing.T) (*Game, *Player, *Player) {
t.Helper()
g := New()
p1, err := g.AddPlayer("Alice")
if err != nil {
t.Fatal(err)
}
p2, err := g.AddPlayer("Bob")
if err != nil {
t.Fatal(err)
}
if g.Phase != PhaseShop {
t.Fatalf("expected shop phase after both players join, got %s", g.Phase)
}
return g, p1, p2
}
func (g *Game) pet(name string, power int) Card {
return Card{ID: g.newCardID(), Kind: KindPet, Name: name, Tier: 1, Power: power, Suit: SuitSun}
}
// forceBattle sets both decks, arranges them in current order, and resolves.
func forceBattle(t *testing.T, g *Game, d1, d2 []Card) *BattleResult {
t.Helper()
g.Players[0].Deck = d1
g.Players[1].Deck = d2
g.Phase = PhaseArrange
g.Players[0].Ready = false
g.Players[1].Ready = false
for _, p := range g.Players {
ids := make([]string, len(p.Deck))
for i, c := range p.Deck {
ids[i] = c.ID
}
if err := g.SubmitOrder(p.ID, ids); err != nil {
t.Fatal(err)
}
}
if g.Phase != PhaseBattle {
t.Fatalf("expected battle phase, got %s", g.Phase)
}
return g.Battle
}
// The worked example from the rules: a 3-power pet fights a 5-power pet. The
// 3 dies, the 5 survives with 3 damage markers (2 health left, 5 attack).
// Then a 2-power pet trades with it: both die.
func TestBattleDamageMarkers(t *testing.T) {
g, _, _ := testGame(t)
res := forceBattle(t, g,
[]Card{g.pet("Three", 3), g.pet("Two", 2)},
[]Card{g.pet("Five", 5)},
)
if len(res.Events) != 2 {
t.Fatalf("expected 2 clashes, got %d", len(res.Events))
}
first := res.Events[0]
if !first.Died[0] || first.Died[1] {
t.Fatalf("first clash: 3-power should die, 5-power should survive: %+v", first)
}
if first.Damage[1] != 3 {
t.Fatalf("5-power pet should carry 3 damage, has %d", first.Damage[1])
}
second := res.Events[1]
if !second.Died[0] || !second.Died[1] {
t.Fatalf("second clash: both should die (5 attack kills the 2; 3+2 damage kills the 5): %+v", second)
}
if res.WinnerSeat != -1 {
t.Fatalf("battle should be a draw, winner=%d", res.WinnerSeat)
}
if g.Players[0].Trophies != 0 || g.Players[1].Trophies != 0 {
t.Fatal("no trophies on a draw")
}
}
func TestBattleEqualPowerBothDie(t *testing.T) {
g, _, _ := testGame(t)
res := forceBattle(t, g,
[]Card{g.pet("A", 4)},
[]Card{g.pet("B", 4)},
)
ev := res.Events[0]
if !ev.Died[0] || !ev.Died[1] {
t.Fatalf("equal power pets should both die: %+v", ev)
}
if res.WinnerSeat != -1 {
t.Fatal("expected a draw")
}
}
func TestBattleWinnerGetsTrophy(t *testing.T) {
g, _, p2 := testGame(t)
res := forceBattle(t, g,
[]Card{g.pet("Small", 1)},
[]Card{g.pet("Big", 5)},
)
if res.WinnerSeat != 1 {
t.Fatalf("seat 1 should win, got %d", res.WinnerSeat)
}
if res.Trophies != 1 || p2.Trophies != 1 {
t.Fatalf("round 1 win should award 1 trophy, got %d/%d", res.Trophies, p2.Trophies)
}
}
func TestBattleFinalRoundWorthTwoTrophies(t *testing.T) {
g, _, p2 := testGame(t)
g.Round = MaxRounds
res := forceBattle(t, g,
[]Card{g.pet("Small", 1)},
[]Card{g.pet("Big", 5)},
)
if res.Trophies != 2 || p2.Trophies != 2 {
t.Fatalf("final round win should award 2 trophies, got %d/%d", res.Trophies, p2.Trophies)
}
}
// Foods stack onto the next pet beneath them; each apple adds 1 power.
// Trailing foods with no pet under them are wasted.
func TestBattleApplesBuffNextPet(t *testing.T) {
g, _, _ := testGame(t)
apple1, apple2, apple3 := g.newApple(), g.newApple(), g.newApple()
res := forceBattle(t, g,
[]Card{apple1, apple2, g.pet("Buffed", 3), apple3}, // 3+2=5 power; apple3 wasted
[]Card{g.pet("Enemy", 5)},
)
u := res.Lineups[0][0]
if u.Bonus != 2 || u.Power() != 5 {
t.Fatalf("expected 2 apples for 5 total power, got bonus=%d power=%d", u.Bonus, u.Power())
}
if len(res.WastedFood[0]) != 1 || res.WastedFood[0][0].ID != apple3.ID {
t.Fatalf("trailing apple should be wasted: %+v", res.WastedFood[0])
}
if res.WinnerSeat != -1 {
t.Fatal("5 vs 5 should draw")
}
}
// A player with an empty lineup loses immediately with zero clashes.
func TestBattleEmptyLineupLoses(t *testing.T) {
g, _, _ := testGame(t)
res := forceBattle(t, g,
[]Card{g.pet("Solo", 1)},
[]Card{g.newApple()}, // food only, no pets
)
if len(res.Events) != 0 {
t.Fatalf("expected no clashes, got %d", len(res.Events))
}
if res.WinnerSeat != 0 {
t.Fatalf("seat 0 should win by default, got %d", res.WinnerSeat)
}
}