Add bot to play against.
This commit is contained in:
+36
-9
@@ -3,6 +3,7 @@ package game
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -49,6 +50,13 @@ type Player struct {
|
||||
// TripledThisRound records whether the player used the Triple (trade-in)
|
||||
// action during the current round's shop (Bison's Battle Prep).
|
||||
TripledThisRound bool `json:"tripledThisRound"`
|
||||
// IsBot marks a computer-controlled seat. The engine treats bots exactly
|
||||
// like humans; the server drives their actions. BotLevel is the bot's
|
||||
// skill in [0, 1]; BotMemory is the bot's private notebook, opaque to the
|
||||
// engine and persisted with the game so knowledge survives restarts.
|
||||
IsBot bool `json:"isBot,omitempty"`
|
||||
BotLevel float64 `json:"botLevel,omitempty"`
|
||||
BotMemory json.RawMessage `json:"botMemory,omitempty"`
|
||||
}
|
||||
|
||||
// PetCount counts pet cards in the player's deck.
|
||||
@@ -196,6 +204,19 @@ func (g *Game) AddPlayer(name string) (*Player, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// AddBot seats a computer-controlled player. Bots count as connected from
|
||||
// the start; the server is responsible for driving their actions.
|
||||
func (g *Game) AddBot(name string, level float64) (*Player, error) {
|
||||
p, err := g.AddPlayer(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.IsBot = true
|
||||
p.BotLevel = min(max(level, 0), 1)
|
||||
p.Connected = true
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// PlayerByID returns the player, or nil.
|
||||
func (g *Game) PlayerByID(id string) *Player {
|
||||
for _, p := range g.Players {
|
||||
@@ -278,7 +299,8 @@ func (g *Game) Buy(playerID string, rowIdx int) error {
|
||||
p.Coins--
|
||||
bought := g.ShopRow[rowIdx]
|
||||
p.Deck = append(p.Deck, bought)
|
||||
g.logf(p.Seat, "🛒", "%s bought %s %s.", p.Name, article(bought.Name), bought.Name)
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🛒", Kind: LogBuy, Source: bought.ID, CardName: bought.Name,
|
||||
Text: fmt.Sprintf("%s bought %s %s.", p.Name, article(bought.Name), bought.Name)})
|
||||
g.ShopRow[rowIdx] = g.drawFromTier(g.Round)
|
||||
g.applyShopTrigger(p, bought, TriggerBuy)
|
||||
g.advanceShopTurn()
|
||||
@@ -320,7 +342,7 @@ func (g *Game) applyShopTrigger(p *Player, c Card, trigger EffectTrigger) {
|
||||
for range n {
|
||||
p.Deck = append(p.Deck, g.newApple())
|
||||
}
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🍎", Source: c.ID, Spawn: "apple",
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🍎", Source: c.ID, Spawn: "apple", Count: n,
|
||||
Text: fmt.Sprintf("%s adds %d apple%s to %s's deck.", c.Name, n, plural(n), p.Name)})
|
||||
case ActionRefreshGold:
|
||||
p.Coins = min(p.Coins+e.count(), CoinsPerRound)
|
||||
@@ -336,7 +358,7 @@ func (g *Game) applyShopTrigger(p *Player, c Card, trigger EffectTrigger) {
|
||||
p.Deck = append(p.Deck, g.newApple())
|
||||
}
|
||||
if apples > 0 {
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🍎", Source: c.ID, Spawn: "apple",
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🍎", Source: c.ID, Spawn: "apple", Count: apples,
|
||||
Text: fmt.Sprintf("%s doubles %s's apples (+%d).", c.Name, p.Name, apples)})
|
||||
}
|
||||
}
|
||||
@@ -363,7 +385,7 @@ func (g *Game) sellCards(p *Player, cardIDs []string) error {
|
||||
}
|
||||
for _, c := range sold {
|
||||
p.Deck = append(p.Deck, g.newApple())
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🍎", Source: c.ID, Spawn: "apple",
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🍎", Kind: LogSell, Source: c.ID, CardName: c.Name, Spawn: "apple",
|
||||
Text: fmt.Sprintf("%s sold %s — it becomes an apple.", p.Name, c.Name)})
|
||||
g.applyShopTrigger(p, c, TriggerSell)
|
||||
}
|
||||
@@ -427,11 +449,14 @@ func (g *Game) TradeStart(playerID string, cardIDs []string) error {
|
||||
// The discarded trio is public — everyone sees what was given up — even
|
||||
// though the pet ultimately chosen stays secret (see TradeChoose).
|
||||
names := make([]string, len(traded))
|
||||
ids := make([]string, len(traded))
|
||||
for i, c := range traded {
|
||||
names[i] = c.Name
|
||||
ids[i] = c.ID
|
||||
}
|
||||
g.logf(p.Seat, "🔄", "%s traded in %s (%s) for a tier %d pick.",
|
||||
p.Name, strings.Join(names, ", "), suit, nextTier)
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🔄", Kind: LogTrade, Cards: ids,
|
||||
Text: fmt.Sprintf("%s traded in %s (%s) for a tier %d pick.",
|
||||
p.Name, strings.Join(names, ", "), suit, nextTier)})
|
||||
g.Pending = &PendingTrade{
|
||||
PlayerID: playerID,
|
||||
Tier: nextTier,
|
||||
@@ -463,10 +488,12 @@ func (g *Game) TradeChoose(playerID string, pick int) error {
|
||||
// can't see the deck. But a pet with a Buy ability performs it publicly, so
|
||||
// we have to reveal that pet (its effect log names it anyway).
|
||||
if hasBuyEffect(chosen) {
|
||||
g.logf(p.Seat, "🔄", "%s's trade pick is %s %s — its buy ability triggers.",
|
||||
p.Name, article(chosen.Name), chosen.Name)
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🔄", Kind: LogTradePick, CardName: chosen.Name,
|
||||
Text: fmt.Sprintf("%s's trade pick is %s %s — its buy ability triggers.",
|
||||
p.Name, article(chosen.Name), chosen.Name)})
|
||||
} else {
|
||||
g.logf(p.Seat, "🔄", "%s keeps their trade pick hidden.", p.Name)
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🔄", Kind: LogTradePick,
|
||||
Text: fmt.Sprintf("%s keeps their trade pick hidden.", p.Name)})
|
||||
}
|
||||
// Pets obtained via the Triple action trigger their Buy effects.
|
||||
g.applyShopTrigger(p, chosen, TriggerBuy)
|
||||
|
||||
Reference in New Issue
Block a user