157 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|