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
+83 -2
View File
@@ -18,7 +18,26 @@ func (cx *ctx) applyTemplateShopEffects(deck []game.Card, c game.Card, trigger g
}
switch e.Action {
case game.ActionGainApple:
for range max(e.Count, 1) {
n := max(e.Count, 1)
switch e.Per {
case game.PerShopFaintPets:
n *= countShopFaintPets(cx.v.ShopRow)
case game.PerBuysThisRound:
// This buy will bump the counter, so count it (Blue-Ringed Octopus).
n *= cx.me.BuysThisRound + 1
}
for range n {
deck = append(deck, cx.simApple())
}
case game.ActionRevealForApples:
// Cockatoo: the bot would reveal its highest-power other pet.
best := 0
for _, d := range deck {
if d.IsPet() && d.ID != c.ID && d.Power > best {
best = d.Power
}
}
for range best {
deck = append(deck, cx.simApple())
}
case game.ActionDoubleApples:
@@ -31,11 +50,54 @@ func (cx *ctx) applyTemplateShopEffects(deck []game.Card, c game.Card, trigger g
for range apples {
deck = append(deck, cx.simApple())
}
case game.ActionApplesInPlay:
// Golden pack (Hercules Beetle, sold): in-play apples aren't in the
// deck, but they buff the front pet, so approximate them as deck
// apples for scoring. MinRound is already gated above.
for range max(e.Count, 1) {
deck = append(deck, cx.simApple())
}
case game.ActionBuyTopFree:
// Golden pack (Stoat): grabs an unknown top-of-deck card; approximate
// with a sampled card of the current tier.
pool := cx.unseenPool(cx.v.Round)
if len(pool) == 0 {
pool = game.TierContentsForPack(cx.v.Pack, cx.v.Round)
}
if len(pool) > 0 {
rc := pool[rand.IntN(len(pool))]
rc.ID = cx.nextSimID()
deck = append(deck, rc)
}
case game.ActionSetAside:
// Golden pack (Avocado): the just-bought token is set aside, not
// kept in the deck being scored.
if i := slices.IndexFunc(deck, func(d game.Card) bool { return d.ID == c.ID }); i >= 0 {
deck = slices.Delete(deck, i, i+1)
}
}
}
return deck
}
// countShopFaintPets counts pets in the shop row with a Faint effect (mirrors
// the engine's Opossum payout).
func countShopFaintPets(row []game.Card) int {
n := 0
for _, c := range row {
if c.ID == "" || !c.IsPet() {
continue
}
for _, e := range c.Effects {
if e.Trigger == game.TriggerFaint {
n++
break
}
}
}
return n
}
func (cx *ctx) simApple() game.Card {
return game.Card{
ID: cx.nextSimID(),
@@ -133,6 +195,25 @@ func (b *Bot) decideShop(v *game.View, mem *Memory) *Action {
}
}
// Golden pack: buying by discarding an Avocado yields the same deck as a
// coin buy, so it's only preferred when coins are scarce — a small negative
// bias keeps the token in reserve otherwise.
if cx.me.Avocados > 0 {
for i, c := range v.ShopRow {
if c.ID == "" {
continue
}
nd := append(slices.Clone(deck), c)
nd = cx.applyTemplateShopEffects(nd, c, game.TriggerBuy)
nd = cx.previewSellDown(nd)
cands = append(cands, candidate{
act: &Action{Type: "buyAvocado", Row: i},
decks: [][]game.Card{nd},
bias: -0.05,
})
}
}
// Sell candidates: the worst 1, 2, or 3 keepers. Selling is free and
// takes any number of cards, so bulk-dumping junk before a battle is one
// action. Temporary cards are excluded — selling an apple for an apple
@@ -201,7 +282,7 @@ func (b *Bot) decideShop(v *game.View, mem *Memory) *Action {
}
pool := cx.unseenPool(v.Round + 1)
if len(pool) == 0 {
pool = game.TierContents(v.Round + 1)
pool = game.TierContentsForPack(v.Pack, v.Round+1)
}
var decks [][]game.Card
for range 3 {