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" FoodPineapple = "pineapple" FoodChili = "chili" FoodMelon = "melon" ) // 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" // TriggerEnemyFaint fires on a pet in play whenever an enemy pet faints. TriggerEnemyFaint EffectTrigger = "enemyFaint" // TriggerEnemyPlay fires on a pet in play whenever the enemy plays a pet. TriggerEnemyPlay EffectTrigger = "enemyPlay" // 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. With Target "all" (Badger) // the volley hits EACH active pet — the owner's own included; // otherwise it targets the enemy pet as usual (Blowfish). ActionDelayedRocks EffectAction = "delayedRocks" // ActionRecurringRocks (faint) sets the pet aside: EVERY time its owner // plays a pet, it throws Count rocks at the enemy pet (Snake). ActionRecurringRocks EffectAction = "recurringRocks" // ActionEnemyLastPetRocks (faint) sets the pet aside: when the enemy // plays the last pet in their deck, it throws Count rocks (Crocodile). ActionEnemyLastPetRocks EffectAction = "enemyLastPetRocks" // ActionShieldNext (faint) sets the pet aside: the next Count times a // friendly pet is hit, ALL damage from that hit is prevented (Turtle). ActionShieldNext EffectAction = "shieldNext" // ActionShieldSelf gives the pet itself Count charges that each prevent // all damage from one hit (Gorilla on hurt, Melon on play). ActionShieldSelf EffectAction = "shieldSelf" // ActionStripFoods (play) discards every food attached to the enemy // pet in play (losing their buffs and perks, which can faint it). ActionStripFoods EffectAction = "stripFoods" // ActionStealApples (play) moves up to Count apples from the enemy pet // to this pet (Wolverine). Losing apples can faint the victim. ActionStealApples EffectAction = "stealApples" // ActionMillEnemy (play) discards cards off the top of the enemy deck // until a non-Bee pet is showing (Chili). ActionMillEnemy EffectAction = "millEnemy" // ActionHeal removes Count damage markers from the pet, if it hasn't // fainted. ActionHeal EffectAction = "heal" // ActionKnockout (passive) makes any pet this pet hurts with its clash // attack faint outright (Scorpion). Rocks don't count, and a fully // prevented attack KOs nothing. ActionKnockout EffectAction = "knockout" // ActionApplesInPlay (battle prep) starts the battle with Count apples // already in play, attached to the owner's first pet (Monkey). ActionApplesInPlay EffectAction = "applesInPlay" // ActionDoubleApples doubles the apples in the player's hand (Cat). // Shop-time. ActionDoubleApples EffectAction = "doubleApples" // ActionBeeAura (faint) sets the pet aside: the owner's Bees have // +Count power for the rest of the battle (Turkey). ActionBeeAura EffectAction = "beeAura" // ActionPetAura (faint) sets the pet aside: the owner's pets have // +Count power for the rest of the battle (Mammoth). ActionPetAura EffectAction = "petAura" ) // Per multipliers for dynamic effect counts. const ( PerFaintedBees = "faintedBees" // × friendly bees fainted this battle PerFaintedPets = "faintedPets" // × friendly pets fainted this battle PerEatenApples = "eatenApples" // × apples this pet ate (its attached apples) PerPower = "power" // × this pet's current power ) // Effect conditions. const ( ConditionTripled = "tripledThisRound" // player Tripled during this round's shop ConditionHasPerk = "hasPerk" // this pet has a perk attached ) // 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"` // Cap limits the final count when > 0 ("up to N"). Cap int `json:"cap,omitempty"` // Condition gates the effect (e.g. ConditionTripled). Condition string `json:"condition,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). All six tiers are real card data. 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, Target: "all"}}, EffectText: "Faint: set aside — when you play your next pet, throw 2 Rocks at each active pet", }, }, { // Tier 4 { Name: "Squirrel", Power: 2, Suits: []Suit{SuitRed, SuitBlue}, Effects: []Effect{ {Trigger: TriggerBuy, Action: ActionGainApple, Count: 2}, {Trigger: TriggerSell, Action: ActionGainApple, Count: 2}, }, EffectText: "Buy: add 2 Apples to your hand · Sell: add 2 extra Apples to your hand", }, { Name: "Turtle", Power: 2, Suits: []Suit{SuitBlue, SuitYellow}, Effects: []Effect{{Trigger: TriggerFaint, Action: ActionShieldNext}}, EffectText: "Faint: set aside — the next time a friendly pet is hit, prevent all damage", }, { Name: "Rooster", Power: 4, Suits: []Suit{SuitYellow, SuitRed}, Effects: []Effect{{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "bee", Per: PerEatenApples, Cap: 3}}, EffectText: "Faint: add a Bee on top of your deck for each Apple this pet ate, up to 3", }, { Name: "Bison", Power: 3, Suits: []Suit{SuitBlue, SuitYellow}, Effects: []Effect{{Trigger: TriggerBattlePrep, Action: ActionGainApple, Count: 3, Condition: ConditionTripled}}, EffectText: "Battle Prep: if you Tripled during this round's shop, add 3 Apples to your hand", }, { Name: "Blowfish", Power: 3, Suits: []Suit{SuitBlue, SuitRed}, Effects: []Effect{{Trigger: TriggerFaint, Action: ActionDelayedRocks, Count: 3}}, EffectText: "Faint: set aside — when you play your next pet, throw 3 Rocks", }, { Name: "Skunk", Power: 2, Suits: []Suit{SuitBlue, SuitYellow}, Effects: []Effect{{Trigger: TriggerPlay, Action: ActionStripFoods}}, EffectText: "Play: discard all Food from the enemy pet", }, { Name: "Hippo", Power: 4, Suits: []Suit{SuitRed, SuitYellow}, Effects: []Effect{{Trigger: TriggerEnemyFaint, Action: ActionHeal}}, EffectText: "Enemy Faints: if this pet hasn't fainted, heal 1 damage", }, }, { // Tier 5 { Name: "Monkey", Power: 3, Suits: []Suit{SuitRed, SuitYellow}, Effects: []Effect{{Trigger: TriggerBattlePrep, Action: ActionApplesInPlay, Count: 3}}, EffectText: "Battle Prep: start with 3 Apples in play, attached to your first pet", }, { Name: "Rhino", Power: 5, Suits: []Suit{SuitRed, SuitYellow}, Effects: []Effect{{Trigger: TriggerEnemyPlay, Action: ActionThrowRock}}, EffectText: "Enemy Played: throw 1 Rock", }, { Name: "Crocodile", Power: 4, Suits: []Suit{SuitRed, SuitBlue}, Effects: []Effect{{Trigger: TriggerFaint, Action: ActionEnemyLastPetRocks, Count: 3}}, EffectText: "Faint: set aside — when the last pet in the enemy deck is played, throw 3 Rocks", }, { Name: "Scorpion", Power: 1, Suits: []Suit{SuitBlue, SuitYellow}, Effects: []Effect{{Trigger: TriggerPassive, Action: ActionKnockout}}, EffectText: "This pet KOs any pet it hurts with an attack", }, { Name: "Seal", Power: 3, Suits: []Suit{SuitYellow, SuitRed}, Effects: []Effect{{Trigger: TriggerPlay, Action: ActionSummonTop, Card: "apple", Count: 3, Condition: ConditionHasPerk}}, EffectText: "Play: if this pet has a Perk, add 3 Apples on top of your deck", }, { Name: "Shark", Power: 1, Suits: []Suit{SuitRed, SuitBlue}, Effects: []Effect{{Trigger: TriggerPlay, Action: ActionEatApple, Per: PerFaintedPets}}, EffectText: "Play: eats 1 Apple for each friendly fainted pet", }, { Name: "Turkey", Power: 4, Suits: []Suit{SuitRed, SuitYellow}, Effects: []Effect{{Trigger: TriggerFaint, Action: ActionBeeAura}}, EffectText: "Faint: set aside — your Bees have +1 power", }, }, { // Tier 6 { Name: "Gorilla", Power: 6, Suits: []Suit{SuitBlue, SuitYellow}, Effects: []Effect{{Trigger: TriggerHurt, Action: ActionShieldSelf}}, EffectText: "Hurt: the next time this pet is hit, prevent all damage", }, { Name: "Fly", Power: 4, Suits: []Suit{SuitRed, SuitYellow}, Effects: []Effect{{Trigger: TriggerFaint, Action: ActionSummonTop, Card: "bee", Count: 3}}, EffectText: "Faint: add 3 Bees on top of your deck", }, { Name: "Leopard", Power: 4, Suits: []Suit{SuitRed, SuitBlue}, Effects: []Effect{{Trigger: TriggerPlay, Action: ActionThrowRock, Per: PerPower}}, EffectText: "Play: throw Rocks equal to this pet's power", }, { Name: "Mammoth", Power: 4, Suits: []Suit{SuitYellow, SuitRed}, Effects: []Effect{{Trigger: TriggerFaint, Action: ActionPetAura}}, EffectText: "Faint: set aside — your pets have +1 power", }, { Name: "Cat", Power: 2, Suits: []Suit{SuitYellow, SuitBlue}, Effects: []Effect{ {Trigger: TriggerBuy, Action: ActionGainApple, Count: 2}, {Trigger: TriggerBuy, Action: ActionDoubleApples}, }, EffectText: "Buy: add 2 Apples to your hand, then double your Apples in hand", }, { Name: "Snake", Power: 2, Suits: []Suit{SuitRed, SuitBlue}, Effects: []Effect{{Trigger: TriggerFaint, Action: ActionRecurringRocks, Count: 2}}, EffectText: "Faint: set aside — each time you play a pet, throw 2 Rocks", }, { Name: "Wolverine", Power: 5, Suits: []Suit{SuitBlue, SuitRed}, Effects: []Effect{{Trigger: TriggerPlay, Action: ActionStealApples, Count: 3}}, EffectText: "Play: steal up to 3 Apples from the enemy pet", }, }, } // 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", }, }, { // Tier 4 { Name: "Pineapple", Food: FoodPineapple, Copies: 2, Perk: true, Effects: []Effect{{Trigger: TriggerPlay, Action: ActionThrowRock, Count: 3}}, EffectText: "Play: throw 3 Rocks", }, }, { // Tier 5 { Name: "Chili", Food: FoodChili, Copies: 2, Perk: true, Effects: []Effect{{Trigger: TriggerPlay, Action: ActionMillEnemy}}, EffectText: "Play: discard cards from the top of the enemy deck until a non-Bee pet is showing", }, }, { // Tier 6 { Name: "Melon", Food: FoodMelon, Copies: 2, Perk: true, Effects: []Effect{{Trigger: TriggerPlay, Action: ActionShieldSelf}}, EffectText: "The first time this pet is hit, prevent all damage", }, }, } // 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, } }