Improve UI of food and set-aside cards.

This commit is contained in:
Greyson Parrelli
2026-07-23 11:12:47 -04:00
parent 7bb20e5d2d
commit 235b1d9cc8
6 changed files with 323 additions and 94 deletions
+86 -1
View File
@@ -1,6 +1,9 @@
package game
import "testing"
import (
"strings"
"testing"
)
// --- Tier 4 ---
@@ -165,6 +168,42 @@ func TestBlowfishDelayedRocksEnemyOnly(t *testing.T) {
}
}
// A fainting Blowfish is set aside as a card (with its own ID), then released
// once the next pet plays and its volley resolves.
func TestBlowfishSetAsideThenRelease(t *testing.T) {
g, _, _ := testGame(t)
g.RollDie = func() int { return 1 }
blowfish := g.realPet(t, "Blowfish")
res := forceBattle(t, g,
[]Card{blowfish, g.pet("Next", 2)},
[]Card{g.pet("Tank", 6)},
)
setasides := eventsOfType(res, "setaside")
if len(setasides) != 1 || setasides[0].Seat != 0 || setasides[0].Card == nil ||
setasides[0].Card.ID != blowfish.ID {
t.Fatalf("blowfish should be set aside once for seat 0: %+v", setasides)
}
releases := eventsOfType(res, "release")
if len(releases) != 1 || releases[0].Seat != 0 || releases[0].Card == nil ||
releases[0].Card.ID != blowfish.ID {
t.Fatalf("blowfish should be released once for seat 0: %+v", releases)
}
// setaside on faint, then release after the volley: setaside precedes the
// rock, which precedes the release.
idx := func(typ string) int {
for i, ev := range res.Events {
if ev.Type == typ {
return i
}
}
return -1
}
if !(idx("setaside") < idx("rock") && idx("rock") < idx("release")) {
t.Fatalf("expected setaside < rock < release, got %d < %d < %d",
idx("setaside"), idx("rock"), idx("release"))
}
}
// Skunk strips the enemy pet's foods; a wounded veteran can faint from the
// power loss.
func TestSkunkStripCanKill(t *testing.T) {
@@ -441,6 +480,52 @@ func TestMelonBlocksFirstHit(t *testing.T) {
}
}
// A used Melon names itself in the log and is released from the play area so
// the fan clears.
func TestMelonBlockNamesSourceAndClears(t *testing.T) {
g, _, _ := testGame(t)
melon := g.realFood(t, "Melon")
res := forceBattle(t, g,
[]Card{melon, g.pet("Holder", 3)},
[]Card{g.pet("Big", 5)},
)
shields := eventsOfType(res, "shield")
if len(shields) != 1 || !strings.Contains(shields[0].Text, "Melon") ||
strings.Contains(shields[0].Text, "shield") {
t.Fatalf("shield line should name the Melon, not say 'shield': %q", shields[0].Text)
}
releases := eventsOfType(res, "release")
if len(releases) != 1 || releases[0].Card == nil || releases[0].Card.ID != melon.ID {
t.Fatalf("the spent Melon should be released from the play area: %+v", releases)
}
}
// With both a set-aside Turtle and a Melon protecting the same pet, the
// borrowed Turtle shield is spent first so its card clears the board.
func TestTurtleShieldSpentBeforeMelon(t *testing.T) {
g, _, _ := testGame(t)
turtle := g.realPet(t, "Turtle") // power 2
res := forceBattle(t, g,
[]Card{turtle, g.realFood(t, "Melon"), g.pet("Holder", 3)},
[]Card{g.pet("Big", 5)},
)
// Turtle (2) dies to Big (5), setting aside a shield; Big keeps 2 damage.
// Holder plays with Melon (own shield). The next hit is absorbed by the
// Turtle set-aside first, releasing the Turtle; Holder then downs Big and
// its Melon shield is never spent.
releases := eventsOfType(res, "release")
if len(releases) != 1 || releases[0].Card == nil || releases[0].Card.ID != turtle.ID {
t.Fatalf("the Turtle shield should be spent (and released) before the Melon: %+v", releases)
}
shields := eventsOfType(res, "shield")
if len(shields) != 1 || !strings.Contains(shields[0].Text, "Turtle") {
t.Fatalf("the block should be credited to the Turtle: %+v", shields)
}
if res.WinnerSeat != 0 {
t.Fatalf("seat 0 should win with the Melon still in reserve, got %d", res.WinnerSeat)
}
}
// Leopard throws rocks equal to its power, apples included.
func TestLeopardRocksEqualPower(t *testing.T) {
g, _, _ := testGame(t)