Improve AI to stop selling everything.

This commit is contained in:
Greyson Parrelli
2026-07-24 22:34:55 -04:00
parent 95499026d7
commit 5a396e6efe
3 changed files with 91 additions and 0 deletions
+24
View File
@@ -242,11 +242,27 @@ func (b *Bot) decideShop(v *game.View, mem *Memory) *Action {
}
return 0
})
// Selling a pet permanently trades a body for a temporary apple — the
// apple's one-battle buff shows up in the rollout, but it vanishes next
// round, so the persistent value of the pet is simply destroyed. The
// future-value term barely registers this (normFuture squashes small deck
// swings), which once let the bot treat "sell my worst pet" as free and
// bleed its board down to a single pet over successive shop turns. This
// explicit penalty prices the loss back in: it scales with the persistent
// worth of the pets sold and with how much the future still matters
// (1-alpha), so it bites hardest early and fades to nothing in the final
// round, where selling for a decisive last battle is a legitimate play the
// rollout can judge on its own.
futureWeight := 1 - immediateWeight(v)
for k := 1; k <= min(3, len(sellable)); k++ {
ids := make([]string, 0, k)
nd := slices.Clone(deck)
petValueSold := 0.0
for _, s := range sellable[:k] {
ids = append(ids, s.ID)
if s.IsPet() {
petValueSold += keepValue(s)
}
idx := slices.IndexFunc(nd, func(c game.Card) bool { return c.ID == s.ID })
nd = slices.Delete(nd, idx, idx+1)
nd = append(nd, cx.simApple())
@@ -260,6 +276,7 @@ func (b *Bot) decideShop(v *game.View, mem *Memory) *Action {
cands = append(cands, candidate{
act: &Action{Type: "sell", Cards: ids},
decks: [][]game.Card{nd},
bias: -sellPetPenalty * futureWeight * petValueSold,
})
}
@@ -297,6 +314,13 @@ func (b *Bot) decideShop(v *game.View, mem *Memory) *Action {
base = slices.Delete(base, idx, idx+1)
base = cx.applyTemplateShopEffects(base, t, game.TriggerTriple)
}
// Never bet the whole board on the trade: the reward is the top of
// the next tier's deck, which can be a food card (or nothing, if the
// deck is spent), so trading away the last pets risks an all-food,
// auto-losing deck. Mirror the sell guard and skip such a trade.
if !deckHasPet(base) {
continue
}
pool := cx.unseenPool(v.Round + 1)
if len(pool) == 0 {
pool = game.TierContentsForPack(v.Pack, v.Round+1)