Initial pass at Golden Pack.

This commit is contained in:
Greyson Parrelli
2026-07-24 07:47:05 -04:00
parent e74f983470
commit dd395f4bbf
27 changed files with 2770 additions and 150 deletions
+173
View File
@@ -0,0 +1,173 @@
package game
import "testing"
// Highland Cow banks Trumpets equal to its power on entry.
func TestHighlandCowTrumpetsFromPower(t *testing.T) {
g, _, _ := testGame(t)
res := forceBattle(t, g,
[]Card{g.goldenPet(t, "Highland Cow")}, // power 3
[]Card{g.pet("Foe", 1)},
)
var gain *BattleEvent
for i, ev := range res.Events {
if ev.Type == "trumpet" && ev.Seat == 0 && ev.Count == 3 {
gain = &res.Events[i]
}
}
if gain == nil {
t.Fatalf("Highland Cow should bank 3 Trumpets (its power): %+v", eventsOfType(res, "trumpet"))
}
}
// Wildebeest spends 3 Trumpets on entry to shield its first hit.
func TestWildebeestSpendsForShield(t *testing.T) {
g, _, _ := testGame(t)
res := forceBattle(t, g,
[]Card{g.goldenPet(t, "Highland Cow"), g.goldenPet(t, "Wildebeest")}, // Cow banks 3
[]Card{g.pet("Chip", 1), g.pet("Hitter", 6)},
)
// Cow banks 3 Trumpets and trades with Chip, then dies to Hitter; Wildebeest
// enters, spends the 3 Trumpets for a shield, and blocks Hitter's blow.
spent := false
for _, ev := range eventsOfType(res, "trumpet") {
if ev.Seat == 0 && ev.Count == -3 {
spent = true
}
}
if !spent {
t.Fatalf("Wildebeest should spend 3 Trumpets: %+v", eventsOfType(res, "trumpet"))
}
if len(eventsOfType(res, "shield")) == 0 {
t.Fatal("Wildebeest should block a hit with its shield")
}
if res.WinnerSeat != 0 {
t.Fatalf("Wildebeest should survive the block and win, got %d", res.WinnerSeat)
}
}
// Grizzly Bear throws a rock per friendly fainted pet.
func TestGrizzlyRocksPerFainted(t *testing.T) {
g, _, _ := testGame(t)
g.RollDie = func() int { return 2 }
res := forceBattle(t, g,
[]Card{g.pet("F1", 1), g.pet("F2", 1), g.goldenPet(t, "Grizzly Bear")},
[]Card{g.pet("K1", 1), g.pet("K2", 1), g.pet("Tank", 2)},
)
rocks := eventsOfType(res, "rock")
if len(rocks) != 1 || len(rocks[0].Dice) != 2 || !rocks[0].TargetDied {
t.Fatalf("Grizzly should throw 2 rocks (one per friendly faint) and kill Tank: %+v", rocks)
}
}
// Catfish reactivates every pet's Buy ability at battle prep.
func TestCatfishReactivatesBuys(t *testing.T) {
g, p1, _ := goldenGame(t)
p1.Deck = []Card{g.goldenPet(t, "Catfish"), g.realPet(t, "Otter")} // Otter buy: +1 apple
g.beginArrange()
if countApplesIn(p1.Deck) != 1 {
t.Fatalf("Catfish should re-fire Otter's buy for 1 apple, got %d", countApplesIn(p1.Deck))
}
}
// Komodo shuffles 6 apples into the deck when it is the first pet.
func TestKomodoShufflesApplesFirstPet(t *testing.T) {
g, _, _ := testGame(t)
res := forceBattle(t, g,
[]Card{g.goldenPet(t, "Komodo")}, // power 6, first pet
[]Card{g.pet("Foe", 1)},
)
apples := 0
for _, ev := range eventsOfType(res, "summon") {
if ev.Card != nil && ev.Card.Name == "Apple" && ev.Seat == 0 {
apples++
}
}
if apples != 6 {
t.Fatalf("Komodo should shuffle 6 apples in as the first pet, got %d", apples)
}
}
// Komodo does nothing when it is not the first pet played.
func TestKomodoNoShuffleWhenNotFirst(t *testing.T) {
g, _, _ := testGame(t)
res := forceBattle(t, g,
[]Card{g.pet("Weak", 1), g.goldenPet(t, "Komodo")},
[]Card{g.pet("K", 1), g.pet("Foe", 1)},
)
for _, ev := range eventsOfType(res, "summon") {
if ev.Card != nil && ev.Card.Name == "Apple" {
t.Fatal("Komodo should not shuffle apples when it isn't the first pet")
}
}
}
// Bird of Paradise banks 2 apples-in-play and 2 Trumpets for the next battle.
func TestBirdOfParadiseBuy(t *testing.T) {
g, p1, _ := goldenGame(t)
g.ShopRow[0] = g.goldenPet(t, "Bird of Paradise")
g.Turn = 0
if err := g.Buy(p1.ID, 0); err != nil {
t.Fatal(err)
}
if p1.PendingApplesInPlay != 2 || p1.PendingTrumpets != 2 {
t.Fatalf("Bird of Paradise should bank 2 apples + 2 Trumpets, got %d/%d",
p1.PendingApplesInPlay, p1.PendingTrumpets)
}
}
// The banked Trumpets appear in the pool at battle start, and reset after.
func TestPendingTrumpetsSeedBattle(t *testing.T) {
g, p1, _ := testGame(t)
p1.PendingTrumpets = 2
res := forceBattle(t, g, []Card{g.pet("Hero", 3)}, []Card{g.pet("Foe", 1)})
var seeded *BattleEvent
for i, ev := range res.Events {
if ev.Type == "trumpet" && ev.Seat == 0 && ev.Count == 2 {
seeded = &res.Events[i]
}
}
if seeded == nil {
t.Fatalf("the battle should start with 2 Trumpets in the pool: %+v", eventsOfType(res, "trumpet"))
}
if p1.PendingTrumpets != 0 {
t.Fatalf("PendingTrumpets should reset after the battle, got %d", p1.PendingTrumpets)
}
}
// German Shepherd guards the Golden Retriever's first hit.
func TestGermanShepherdGuardsRetriever(t *testing.T) {
g, _, _ := testGame(t)
res := forceBattle(t, g,
[]Card{g.goldenPet(t, "German Shepherd"), g.goldenPet(t, "Nyala")}, // Nyala banks 2 Trumpets
[]Card{g.pet("BigA", 5), g.pet("BigB", 9)},
)
// German Shepherd faints (guard aside), Nyala faints (+2 Trumpets), then the
// Golden Retriever arrives and its first hit is shaved by 5.
var guarded *BattleEvent
for i, ev := range res.Events {
if ev.Type == "prevent" && ev.Seat == 0 && ev.Count == 5 {
guarded = &res.Events[i]
}
}
if guarded == nil {
t.Fatalf("the Golden Retriever's first hit should be guarded for 5: %+v", eventsOfType(res, "prevent"))
}
}
// Tomato is a perk that throws 4 rocks on play.
func TestTomatoThrowsRocks(t *testing.T) {
g, _, _ := testGame(t)
g.RollDie = func() int { return 1 }
res := forceBattle(t, g,
[]Card{g.goldenFood(t, "Tomato"), g.pet("Holder", 2)},
[]Card{g.pet("Tank", 3)},
)
rocks := eventsOfType(res, "rock")
if len(rocks) != 1 || rocks[0].Roll != 4 || !rocks[0].TargetDied {
t.Fatalf("Tomato should throw 4 rocks and kill the 3-power Tank: %+v", rocks)
}
if res.WinnerSeat != 0 {
t.Fatalf("seat 0 should win, got %d", res.WinnerSeat)
}
}