Files
super-auto-pets-board-game/internal/game/cards.go
T

373 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package game
import "fmt"
// Suit is the colored trade-in symbol printed on pet cards. Three pets of
// the same suit can be traded (the "Triple" action) for a pick from the next
// tier's deck.
type Suit string
const (
SuitRed Suit = "red"
SuitBlue Suit = "blue"
SuitYellow Suit = "yellow"
)
// CardKind distinguishes pets from foods.
type CardKind string
const (
KindPet CardKind = "pet"
KindFood CardKind = "food"
)
// Food identifiers.
const (
FoodApple = "apple"
FoodHoney = "honey"
FoodGarlic = "garlic"
)
// EffectTrigger is when an effect fires.
type EffectTrigger string
const (
TriggerFaint EffectTrigger = "faint" // defeated in battle
TriggerSell EffectTrigger = "sell" // sold in the shop (the discard→apple action)
TriggerBuy EffectTrigger = "buy" // purchased (also fires on pets gained via Triple)
TriggerPlay EffectTrigger = "play" // revealed on the stack during battle
TriggerTriple EffectTrigger = "triple" // used as one of the three traded-in cards
TriggerHurt EffectTrigger = "hurt" // attacked and received damage
// TriggerBattlePrep fires right after the shop phase ends, as players
// begin ordering their decks.
TriggerBattlePrep EffectTrigger = "battlePrep"
// TriggerPassive marks always-on effects (e.g. Garlic's damage
// prevention); they're consulted contextually rather than fired.
TriggerPassive EffectTrigger = "passive"
)
// EffectAction is what the effect does.
type EffectAction string
const (
// ActionSummonTop puts Count cards (Effect.Card: "apple" or "bee") on
// top of a deck/stack — the owner's, or the enemy's when Target is
// "enemy". Battle-time.
ActionSummonTop EffectAction = "summonTop"
// ActionGainApple adds Count apples to the player's hand. Shop-time.
ActionGainApple EffectAction = "gainApple"
// ActionThrowRock rolls Count rock dice (faces: 0,0,1,1,2,2) and deals
// the total to the opposing pet in play. Battle-time.
ActionThrowRock EffectAction = "throwRock"
// ActionEatApple gives the pet itself +Count power (as if it ate
// apples), if it hasn't fainted. Battle-time. With Per set, Count is
// multiplied by a battle statistic (see Effect.Per).
ActionEatApple EffectAction = "eatApple"
// ActionRefreshGold returns Count spent coins to the player (never
// above the round's allowance). Shop-time.
ActionRefreshGold EffectAction = "refreshGold"
// ActionPreventDamage (passive) reduces every attack that hits this pet
// by Count damage.
ActionPreventDamage EffectAction = "preventDamage"
// ActionRecycleApples (faint) puts up to Count of this pet's attached
// apples back on top of its owner's deck.
ActionRecycleApples EffectAction = "recycleApples"
// ActionDelayedRocks (faint) sets the pet aside: when its owner plays
// their next pet, it throws Count rocks at EACH active pet — the
// enemy's and the owner's own.
ActionDelayedRocks EffectAction = "delayedRocks"
)
// Per multipliers for dynamic effect counts.
const PerFaintedBees = "faintedBees" // × friendly bees fainted this battle
// Effect is one trigger→action pair printed on a card.
type Effect struct {
Trigger EffectTrigger `json:"trigger"`
Action EffectAction `json:"action"`
Card string `json:"card,omitempty"` // summonTop: "apple" | "bee"
Count int `json:"count,omitempty"` // 0 means 1
Target string `json:"target,omitempty"` // summonTop: "" (self) | "enemy"
// Per multiplies Count by a battle statistic (e.g. PerFaintedBees).
Per string `json:"per,omitempty"`
// MinRound gates the effect to round >= MinRound (0 = always).
MinRound int `json:"minRound,omitempty"`
}
// count normalizes the zero value to 1.
func (e Effect) count() int {
if e.Count <= 0 {
return 1
}
return e.Count
}
// Card is a single physical card instance. IDs are unique per game.
type Card struct {
ID string `json:"id"`
Kind CardKind `json:"kind"`
Name string `json:"name"`
Tier int `json:"tier,omitempty"`
Power int `json:"power,omitempty"`
Suit Suit `json:"suit,omitempty"`
// Effects drive the engine; EffectText is the human-readable rule shown
// on the card face.
Effects []Effect `json:"effects,omitempty"`
EffectText string `json:"effectText,omitempty"`
Food string `json:"food,omitempty"`
// Perk foods (e.g. Honey) attach like any food, but a pet only benefits
// from the last-applied perk.
Perk bool `json:"perk,omitempty"`
// Temporary cards (apples, bees) are removed from the deck after the
// next battle.
Temporary bool `json:"temporary,omitempty"`
}
func (c Card) IsPet() bool { return c.Kind == KindPet }
func (c Card) IsFood() bool { return c.Kind == KindFood }
// petTemplate is the printed definition of a pet. Suits lists the suit of
// each physical copy in the tier deck (one card per entry).
type petTemplate struct {
Name string
Power int
Suits []Suit
Effects []Effect
EffectText string
}
// foodTemplate is the printed definition of a food card that lives in a
// shop deck (e.g. Honey).
type foodTemplate struct {
Name string
Food string
Copies int
Perk bool
Effects []Effect
EffectText string
}
// petTiers defines the shop decks' pets. Index 0 is tier 1 (round 1)
// through index 5 for tier 6 (round 6).
//
// Tiers 1-2 are real card data. Tiers 3-6 are placeholders with the same
// structure until their real definitions are provided.
var petTiers = [MaxRounds][]petTemplate{
{ // Tier 1
{
Name: "Ant", Power: 1, Suits: []Suit{SuitBlue, SuitYellow},
Effects: []Effect{{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "apple"}},
EffectText: "Faint: add an Apple on top of your deck",
},
{
Name: "Cricket", Power: 1, Suits: []Suit{SuitRed, SuitBlue},
Effects: []Effect{{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "bee"}},
EffectText: "Faint: add a Bee on top of your deck",
},
{
Name: "Duck", Power: 2, Suits: []Suit{SuitYellow, SuitBlue},
Effects: []Effect{{Trigger: TriggerSell, Action: ActionGainApple}},
EffectText: "Sell: add 1 extra Apple to your hand",
},
{
Name: "Otter", Power: 1, Suits: []Suit{SuitYellow, SuitRed},
Effects: []Effect{{Trigger: TriggerBuy, Action: ActionGainApple}},
EffectText: "Buy: add an Apple to your hand",
},
{
Name: "Mosquito", Power: 2, Suits: []Suit{SuitRed, SuitBlue},
Effects: []Effect{{Trigger: TriggerPlay, Action: ActionThrowRock}},
EffectText: "Play: throw 1 Rock",
},
{
Name: "Fish", Power: 2, Suits: []Suit{SuitYellow, SuitRed},
Effects: []Effect{{Trigger: TriggerTriple, Action: ActionGainApple}},
EffectText: "Triple: add an Apple to your hand",
},
},
{ // Tier 2
{
Name: "Worm", Power: 2, Suits: []Suit{SuitBlue, SuitYellow},
Effects: []Effect{{Trigger: TriggerBuy, Action: ActionGainApple, Count: 2}},
EffectText: "Buy: add 2 Apples to your hand",
},
{
Name: "Flamingo", Power: 1, Suits: []Suit{SuitRed, SuitYellow},
Effects: []Effect{{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "apple", Count: 2}},
EffectText: "Faint: add 2 Apples on top of your deck",
},
{
Name: "Peacock", Power: 2, Suits: []Suit{SuitBlue, SuitRed},
Effects: []Effect{{Trigger: TriggerHurt, Action: ActionEatApple}},
EffectText: "Hurt: if this pet hasn't fainted, it eats 1 Apple",
},
{
Name: "Swan", Power: 1, Suits: []Suit{SuitRed, SuitBlue},
Effects: []Effect{{Trigger: TriggerTriple, Action: ActionRefreshGold, MinRound: 3}},
EffectText: "Triple: if it is round 3 or later, refresh a spent Gold",
},
{
Name: "Rat", Power: 4, Suits: []Suit{SuitRed, SuitYellow},
Effects: []Effect{{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "bee", Target: "enemy"}},
EffectText: "Faint: add a Bee on top of the enemy deck",
},
{
Name: "Spider", Power: 2, Suits: []Suit{SuitYellow, SuitBlue},
Effects: []Effect{
{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "bee"},
{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "apple"},
},
EffectText: "Faint: add a Bee, then an Apple, on top of your deck",
},
},
{ // Tier 3
{
Name: "Dog", Power: 2, Suits: []Suit{SuitYellow, SuitBlue},
Effects: []Effect{{Trigger: TriggerPlay, Action: ActionEatApple, Per: PerFaintedBees}},
EffectText: "Play: eats 1 Apple for each friendly fainted Bee",
},
{
Name: "Dolphin", Power: 2, Suits: []Suit{SuitBlue, SuitRed},
Effects: []Effect{{Trigger: TriggerPlay, Action: ActionThrowRock, Count: 3}},
EffectText: "Play: throw 3 Rocks",
},
{
Name: "Giraffe", Power: 2, Suits: []Suit{SuitRed, SuitBlue},
Effects: []Effect{{Trigger: TriggerBattlePrep, Action: ActionGainApple, Count: 2}},
EffectText: "Battle Prep: add 2 Apples to your hand",
},
{
Name: "Camel", Power: 3, Suits: []Suit{SuitYellow, SuitBlue},
Effects: []Effect{{Trigger: TriggerHurt, Action: ActionSummonTop, Card: "apple"}},
EffectText: "Hurt: add an Apple on top of your deck",
},
{
Name: "Sheep", Power: 3, Suits: []Suit{SuitYellow, SuitRed},
Effects: []Effect{{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "bee", Count: 2}},
EffectText: "Faint: add 2 Bees on top of your deck",
},
{
Name: "Dodo", Power: 3, Suits: []Suit{SuitRed, SuitBlue},
Effects: []Effect{{Trigger: TriggerFaint, Action: ActionRecycleApples, Count: 3}},
EffectText: "Faint: put up to 3 of this pet's Apples on top of your deck",
},
{
Name: "Badger", Power: 3, Suits: []Suit{SuitRed, SuitYellow},
Effects: []Effect{{Trigger: TriggerFaint, Action: ActionDelayedRocks, Count: 2}},
EffectText: "Faint: set aside — when you play your next pet, throw 2 Rocks at each active pet",
},
},
placeholderTier([6]string{"Skunk", "Hippo", "Bison", "Deer", "Squirrel", "Whale"},
[6]int{5, 6, 6, 4, 4, 5}),
placeholderTier([6]string{"Scorpion", "Rhino", "Monkey", "Cow", "Seal", "Shark"},
[6]int{5, 7, 6, 6, 6, 7}),
placeholderTier([6]string{"Leopard", "Boar", "Gorilla", "Mammoth", "Snake", "Tiger"},
[6]int{8, 9, 9, 10, 8, 9}),
}
// foodTiers defines the food cards mixed into each tier's shop deck.
var foodTiers = [MaxRounds][]foodTemplate{
{}, // Tier 1
{ // Tier 2
{
Name: "Honey", Food: FoodHoney, Copies: 2, Perk: true,
Effects: []Effect{{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "bee"}},
EffectText: "Faint: add a Bee on top of your deck",
},
},
{ // Tier 3
{
Name: "Garlic", Food: FoodGarlic, Copies: 2, Perk: true,
Effects: []Effect{{Trigger: TriggerPassive, Action: ActionPreventDamage}},
EffectText: "Every attack that hits this pet deals 1 less damage",
},
},
{}, {}, {},
}
// placeholderTier mirrors tier 1's suit distribution (each suit appears on 4
// of the 12 cards) for pets whose real definitions aren't in yet.
func placeholderTier(names [6]string, powers [6]int) []petTemplate {
suitPairs := [6][]Suit{
{SuitBlue, SuitYellow},
{SuitRed, SuitBlue},
{SuitYellow, SuitBlue},
{SuitYellow, SuitRed},
{SuitRed, SuitBlue},
{SuitYellow, SuitRed},
}
tier := make([]petTemplate, 6)
for i := range names {
tier[i] = petTemplate{Name: names[i], Power: powers[i], Suits: suitPairs[i]}
}
return tier
}
// newCardID mints a unique card ID within the game.
func (g *Game) newCardID() string {
g.NextCardID++
return fmt.Sprintf("c%d", g.NextCardID)
}
// buildShopDecks creates all six tier decks (unshuffled).
func (g *Game) buildShopDecks() {
g.ShopDecks = make([][]Card, MaxRounds)
for tierIdx := range petTiers {
var deck []Card
for _, t := range petTiers[tierIdx] {
for _, suit := range t.Suits {
deck = append(deck, Card{
ID: g.newCardID(),
Kind: KindPet,
Name: t.Name,
Tier: tierIdx + 1,
Power: t.Power,
Suit: suit,
Effects: t.Effects,
EffectText: t.EffectText,
})
}
}
for _, f := range foodTiers[tierIdx] {
for range f.Copies {
deck = append(deck, Card{
ID: g.newCardID(),
Kind: KindFood,
Name: f.Name,
Tier: tierIdx + 1,
Food: f.Food,
Perk: f.Perk,
Effects: f.Effects,
EffectText: f.EffectText,
})
}
}
g.ShopDecks[tierIdx] = deck
}
}
// newApple mints an apple food card. Apples are temporary: they vanish from
// the deck after the next battle.
func (g *Game) newApple() Card {
return Card{
ID: g.newCardID(),
Kind: KindFood,
Name: "Apple",
Food: FoodApple,
Temporary: true,
}
}
// newBee mints a bee: a temporary 1-power pet with no effect, summoned by
// other pets' effects. It counts as a pet while it exists (foods can attach
// to it in battle).
func (g *Game) newBee() Card {
return Card{
ID: g.newCardID(),
Kind: KindPet,
Name: "Bee",
Power: 1,
Temporary: true,
}
}