Fix nurse shark and remove mid-battle choice.
This commit is contained in:
+1
-14
@@ -35,13 +35,12 @@ import (
|
||||
|
||||
// Action is one move the bot wants to make, mirroring the client protocol.
|
||||
type Action struct {
|
||||
Type string // buy | buyAvocado | sell | trade | tradeChoose | pass | arrange | ready | revealChoose | battleChoose
|
||||
Type string // buy | buyAvocado | sell | trade | tradeChoose | pass | arrange | ready | revealChoose
|
||||
Row int // buy / buyAvocado
|
||||
Cards []string // sell / trade
|
||||
Pick int // tradeChoose
|
||||
Order []string // arrange
|
||||
CardID string // revealChoose (Cockatoo): the pet to reveal
|
||||
Value int // battleChoose (Nurse Shark): Trumpets to spend
|
||||
}
|
||||
|
||||
// Bot is a computer player at a fixed difficulty level.
|
||||
@@ -86,13 +85,6 @@ func (b *Bot) Act(v *game.View, mem *Memory) *Action {
|
||||
return b.decideArrange(v, mem)
|
||||
}
|
||||
case game.PhaseBattle:
|
||||
if v.PendingBattle != nil {
|
||||
if v.PendingBattle.Seat == v.YouSeat {
|
||||
// Spend as many Trumpets as allowed — more rocks is better.
|
||||
return &Action{Type: "battleChoose", Value: v.PendingBattle.Max}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !me.Ready {
|
||||
return &Action{Type: "ready"}
|
||||
}
|
||||
@@ -134,11 +126,6 @@ func Pending(v *game.View) bool {
|
||||
case game.PhaseArrange:
|
||||
return !me.Ready
|
||||
case game.PhaseBattle:
|
||||
// A pending mid-battle decision is owed only by the deciding seat;
|
||||
// otherwise everyone owes the battle acknowledgement.
|
||||
if v.PendingBattle != nil {
|
||||
return v.PendingBattle.Seat == v.YouSeat
|
||||
}
|
||||
return !me.Ready
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -100,8 +100,6 @@ func applyAction(g *game.Game, playerID string, a *Action) error {
|
||||
return g.TradeChoose(playerID, a.Pick)
|
||||
case "revealChoose":
|
||||
return g.RevealChoose(playerID, a.CardID)
|
||||
case "battleChoose":
|
||||
return g.BattleChoose(playerID, a.Value)
|
||||
case "pass":
|
||||
return g.Pass(playerID)
|
||||
case "arrange":
|
||||
|
||||
+12
-40
@@ -287,27 +287,18 @@ func effectCount(e Effect, s *battleSide, u *BattleUnit, enemy *battleSide) int
|
||||
// attack manages to hurt. A clash that changes nothing ends the battle as a
|
||||
// stalemate.
|
||||
//
|
||||
// resolveBattle is the orchestrator: it re-runs the (deterministic) simulation
|
||||
// from the recorded dice/decision tapes, publishing either a completed result
|
||||
// or a suspended one awaiting a mid-battle decision (Golden pack: Nurse Shark).
|
||||
// resolveBattle is the orchestrator: it runs the (deterministic) simulation and
|
||||
// publishes the completed result.
|
||||
func (g *Game) resolveBattle() {
|
||||
g.NextCardID = g.BattleCardBase
|
||||
g.battleRollCursor = 0
|
||||
g.battleDecisionCursor = 0
|
||||
res, pending := g.runBattle()
|
||||
res := g.runBattle()
|
||||
g.Battle = res
|
||||
if pending != nil {
|
||||
g.PendingBattle = pending
|
||||
return
|
||||
}
|
||||
g.PendingBattle = nil
|
||||
g.finalizeBattle(res)
|
||||
}
|
||||
|
||||
// finalizeBattle applies the persistent effects of a completed battle: trophies,
|
||||
// the priority token hand-off, the result log line, and clearing the per-round
|
||||
// apples-in-play bank. Deferred here (not inside runBattle) because runBattle
|
||||
// may re-run several times before the battle actually completes.
|
||||
// apples-in-play bank. Kept separate from runBattle, which mutates no persistent
|
||||
// player state.
|
||||
func (g *Game) finalizeBattle(res *BattleResult) {
|
||||
n := len(g.Players)
|
||||
winner := res.WinnerSeat
|
||||
@@ -332,13 +323,11 @@ func (g *Game) finalizeBattle(res *BattleResult) {
|
||||
}
|
||||
}
|
||||
|
||||
// runBattle plays the simulation to completion or until it needs a mid-battle
|
||||
// decision, returning the (partial) result and a non-nil pending in the latter
|
||||
// case. It mutates no persistent player state — that is finalizeBattle's job.
|
||||
func (g *Game) runBattle() (*BattleResult, *PendingBattleDecision) {
|
||||
// runBattle plays the simulation to completion, returning the result. It
|
||||
// mutates no persistent player state — that is finalizeBattle's job.
|
||||
func (g *Game) runBattle() *BattleResult {
|
||||
n := len(g.Players)
|
||||
res := &BattleResult{Round: g.Round, WinnerSeat: -1, StackSizes: make([]int, n), Lineups: make([][]Card, n)}
|
||||
var suspended *PendingBattleDecision
|
||||
sides := make([]*battleSide, n)
|
||||
emit := func(ev BattleEvent) { res.Events = append(res.Events, ev) }
|
||||
// pname is the owning player's display name for a seat, for log text.
|
||||
@@ -1190,19 +1179,10 @@ func (g *Game) runBattle() (*BattleResult, *PendingBattleDecision) {
|
||||
Text: fmt.Sprintf("%s shuffles an apple into %s's deck.", q.unit.Card.Name, pname(q.seat))})
|
||||
}
|
||||
case ActionSpendRocks:
|
||||
// Nurse Shark: the owner chooses how many Trumpets (0..available,
|
||||
// capped at Count) to spend; each throws two rocks. This is the one
|
||||
// mid-battle decision — it may suspend the whole simulation.
|
||||
// Nurse Shark: spend as many Trumpets as available (up to Count) to
|
||||
// throw two rocks each.
|
||||
s := sides[q.seat]
|
||||
maxSpend := min(q.effect.count(), s.trumpets)
|
||||
choice, pending := g.decideBattle(PendingBattleDecision{
|
||||
Seat: q.seat, Kind: "nurseShark", PetName: q.unit.Card.Name,
|
||||
Min: 0, Max: maxSpend, Trumpets: s.trumpets,
|
||||
})
|
||||
if pending != nil {
|
||||
suspended = pending
|
||||
break
|
||||
}
|
||||
choice := min(q.effect.count(), s.trumpets)
|
||||
if choice > 0 {
|
||||
s.trumpets -= choice
|
||||
emit(BattleEvent{Type: "trumpet", Seat: q.seat, Count: -choice,
|
||||
@@ -1214,14 +1194,6 @@ func (g *Game) runBattle() (*BattleResult, *PendingBattleDecision) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if suspended != nil {
|
||||
break // stop mid-plays; the battle re-runs once the choice is in
|
||||
}
|
||||
}
|
||||
// A pending decision unwinds the whole simulation; the events emitted so
|
||||
// far are a valid prefix the re-run reproduces exactly.
|
||||
if suspended != nil {
|
||||
return res, suspended
|
||||
}
|
||||
// Battle over? A side that can no longer field a pet is out (checked
|
||||
// after play effects so parting shots land, using canField so a pet that
|
||||
@@ -1337,5 +1309,5 @@ func (g *Game) runBattle() (*BattleResult, *PendingBattleDecision) {
|
||||
res.Trophies = 2
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -185,9 +185,8 @@ const (
|
||||
// ActionStripApples (play) discards every apple attached to the enemy pet
|
||||
// in play (Durian perk) — like ActionStripFoods but apples only.
|
||||
ActionStripApples EffectAction = "stripApples"
|
||||
// ActionSpendRocks (play) asks the owner how many Trumpets to spend (0..Count)
|
||||
// and throws twice that many rocks at the enemy (Nurse Shark). This is the
|
||||
// one battle-time player decision.
|
||||
// ActionSpendRocks (play) spends as many Trumpets as available (up to Count)
|
||||
// and throws twice that many rocks at the enemy (Nurse Shark).
|
||||
ActionSpendRocks EffectAction = "spendRocks"
|
||||
// ActionFirstBuyFree (shop start) makes the player's first Buy this round
|
||||
// cost no gold, if they hold Count or fewer pets (Manta Ray).
|
||||
|
||||
+7
-98
@@ -137,96 +137,28 @@ type Game struct {
|
||||
Log []LogEntry `json:"log,omitempty"`
|
||||
LogSeq int `json:"logSeq"` // last assigned entry sequence number
|
||||
|
||||
// --- Golden pack: resumable battle (Nurse Shark's mid-battle choice) ---
|
||||
// A battle is deterministic given its decks, the recorded dice tape, and the
|
||||
// recorded decisions, so it can be re-run from scratch each time a decision
|
||||
// is made. BattleCardBase snapshots NextCardID at battle start so re-runs
|
||||
// mint identical ephemeral card ids.
|
||||
BattleDice []int `json:"battleDice,omitempty"`
|
||||
BattleDecisions []int `json:"battleDecisions,omitempty"`
|
||||
BattleCardBase int `json:"battleCardBase,omitempty"`
|
||||
PendingBattle *PendingBattleDecision `json:"pendingBattle,omitempty"`
|
||||
|
||||
// RollDie overrides the rock die (faces 0,0,1,1,2,2) for tests. Nil
|
||||
// (including after loading from storage) means a fair random roll.
|
||||
RollDie func() int `json:"-"`
|
||||
|
||||
// Transient per-run battle state (not serialized): the tape cursors and the
|
||||
// rollout auto-decide flag.
|
||||
battleRollCursor int
|
||||
battleDecisionCursor int
|
||||
autoBattleDecide bool
|
||||
}
|
||||
|
||||
// PendingBattleDecision is a choice a player owes mid-battle (Golden pack: Nurse
|
||||
// Shark). The battle suspends until BattleChoose supplies a value in [Min, Max].
|
||||
type PendingBattleDecision struct {
|
||||
Seat int `json:"seat"`
|
||||
Kind string `json:"kind"` // "nurseShark"
|
||||
PetName string `json:"petName"` // for the prompt
|
||||
Min int `json:"min"`
|
||||
Max int `json:"max"`
|
||||
Trumpets int `json:"trumpets"` // the side's current Trumpet pool
|
||||
}
|
||||
|
||||
// battleDraw returns a random value in [0, n), replaying from the recorded
|
||||
// battle tape when re-running a battle and recording fresh draws otherwise.
|
||||
// This keeps re-runs (after a mid-battle decision) deterministic across every
|
||||
// source of battle randomness — rock dice and Komodo's apple shuffle alike.
|
||||
// battleDraw returns a random value in [0, n) for a battle's randomness — rock
|
||||
// dice and Komodo's apple shuffle alike. The RollDie test override applies to
|
||||
// rock dice (n == 3).
|
||||
func (g *Game) battleDraw(n int) int {
|
||||
if g.battleRollCursor < len(g.BattleDice) {
|
||||
v := g.BattleDice[g.battleRollCursor]
|
||||
g.battleRollCursor++
|
||||
return v
|
||||
}
|
||||
var v int
|
||||
switch {
|
||||
case n <= 0:
|
||||
v = 0
|
||||
return 0
|
||||
case n == 3 && g.RollDie != nil:
|
||||
v = g.RollDie() // test override applies to rock dice
|
||||
return g.RollDie() // test override applies to rock dice
|
||||
default:
|
||||
v = randInt(n)
|
||||
return randInt(n)
|
||||
}
|
||||
g.BattleDice = append(g.BattleDice, v)
|
||||
g.battleRollCursor++
|
||||
return v
|
||||
}
|
||||
|
||||
// rollRockDie rolls one rock die: 0, 1, or 2 with equal probability.
|
||||
func (g *Game) rollRockDie() int { return g.battleDraw(3) }
|
||||
|
||||
// decideBattle resolves a mid-battle decision. In rollouts it auto-picks; when
|
||||
// replaying it reads the recorded decision; otherwise it signals a suspend by
|
||||
// returning a non-nil pending for the caller to surface.
|
||||
func (g *Game) decideBattle(pd PendingBattleDecision) (int, *PendingBattleDecision) {
|
||||
if g.autoBattleDecide {
|
||||
return autoBattleChoice(pd), nil
|
||||
}
|
||||
if g.battleDecisionCursor < len(g.BattleDecisions) {
|
||||
v := clampInt(g.BattleDecisions[g.battleDecisionCursor], pd.Min, pd.Max)
|
||||
g.battleDecisionCursor++
|
||||
return v, nil
|
||||
}
|
||||
return 0, &pd
|
||||
}
|
||||
|
||||
// autoBattleChoice is the fixed policy used in rollouts and by bots: for Nurse
|
||||
// Shark, spend as many Trumpets as allowed (more rocks is better).
|
||||
func autoBattleChoice(pd PendingBattleDecision) int {
|
||||
return pd.Max
|
||||
}
|
||||
|
||||
func clampInt(v, lo, hi int) int {
|
||||
if v < lo {
|
||||
return lo
|
||||
}
|
||||
if v > hi {
|
||||
return hi
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotYourTurn = errors.New("not your turn")
|
||||
ErrWrongPhase = errors.New("action not allowed in this phase")
|
||||
@@ -962,14 +894,8 @@ func (g *Game) SubmitOrder(playerID string, orderedIDs []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// startBattle enters the battle phase and resolves it. It snapshots the card-id
|
||||
// base and clears the dice/decision tapes so the (possibly resumable) battle
|
||||
// re-runs deterministically as decisions come in.
|
||||
// startBattle enters the battle phase and resolves it.
|
||||
func (g *Game) startBattle() {
|
||||
g.BattleCardBase = g.NextCardID
|
||||
g.BattleDice = nil
|
||||
g.BattleDecisions = nil
|
||||
g.PendingBattle = nil
|
||||
for _, p := range g.Players {
|
||||
p.Ready = false
|
||||
}
|
||||
@@ -977,29 +903,12 @@ func (g *Game) startBattle() {
|
||||
g.resolveBattle()
|
||||
}
|
||||
|
||||
// BattleChoose supplies a value for the pending mid-battle decision (Golden
|
||||
// pack: Nurse Shark) and re-runs the battle from the recorded tape.
|
||||
func (g *Game) BattleChoose(playerID string, value int) error {
|
||||
if g.PendingBattle == nil {
|
||||
return fmt.Errorf("%w: no battle decision pending", ErrInvalidAction)
|
||||
}
|
||||
if g.Players[g.PendingBattle.Seat].ID != playerID {
|
||||
return ErrNotYourTurn
|
||||
}
|
||||
g.BattleDecisions = append(g.BattleDecisions, clampInt(value, g.PendingBattle.Min, g.PendingBattle.Max))
|
||||
g.resolveBattle()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcknowledgeBattle marks the player done reviewing the battle. When all
|
||||
// players acknowledge, the next round starts (or the game ends).
|
||||
func (g *Game) AcknowledgeBattle(playerID string) error {
|
||||
if g.Phase != PhaseBattle {
|
||||
return ErrWrongPhase
|
||||
}
|
||||
if g.PendingBattle != nil {
|
||||
return fmt.Errorf("%w: a battle decision is still pending", ErrInvalidAction)
|
||||
}
|
||||
p := g.PlayerByID(playerID)
|
||||
if p == nil {
|
||||
return errors.New("unknown player")
|
||||
|
||||
@@ -2,11 +2,11 @@ package game
|
||||
|
||||
import "testing"
|
||||
|
||||
// --- Nurse Shark: the mid-battle decision channel ---
|
||||
// --- Nurse Shark ---
|
||||
|
||||
// Nurse Shark suspends the battle to ask how many Trumpets to spend, then
|
||||
// resumes and throws two rocks per Trumpet spent.
|
||||
func TestNurseSharkSuspendsAndResumes(t *testing.T) {
|
||||
// Nurse Shark automatically spends every available Trumpet (up to 3), throwing
|
||||
// two rocks per Trumpet spent.
|
||||
func TestNurseSharkSpendsAllTrumpets(t *testing.T) {
|
||||
g, _, _ := testGame(t)
|
||||
g.RollDie = func() int { return 2 } // each rock deals 2
|
||||
res := forceBattle(t, g,
|
||||
@@ -14,25 +14,9 @@ func TestNurseSharkSuspendsAndResumes(t *testing.T) {
|
||||
[]Card{g.pet("Big", 4), g.pet("Tank", 6)},
|
||||
)
|
||||
// Nyala trades with Big (banking 2 Trumpets), then Nurse Shark enters and
|
||||
// the battle suspends on its choice.
|
||||
if g.PendingBattle == nil {
|
||||
t.Fatal("expected the battle to suspend on Nurse Shark's choice")
|
||||
}
|
||||
if g.PendingBattle.Seat != 0 || g.PendingBattle.Max != 2 || g.PendingBattle.Kind != "nurseShark" {
|
||||
t.Fatalf("unexpected pending decision: %+v", g.PendingBattle)
|
||||
}
|
||||
if res.WinnerSeat != -1 {
|
||||
t.Fatalf("a suspended battle has no winner yet, got %d", res.WinnerSeat)
|
||||
}
|
||||
// Spend both Trumpets: 4 rocks (2+2+2+2 = 8) kill the 6-power Tank.
|
||||
if err := g.BattleChoose(g.Players[0].ID, 2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g.PendingBattle != nil {
|
||||
t.Fatalf("battle should have completed: %+v", g.PendingBattle)
|
||||
}
|
||||
// spends both: 4 rocks (2+2+2+2 = 8) kill the 6-power Tank.
|
||||
spends := 0
|
||||
for _, ev := range eventsOfType(g.Battle, "trumpet") {
|
||||
for _, ev := range eventsOfType(res, "trumpet") {
|
||||
if ev.Count < 0 {
|
||||
spends++
|
||||
}
|
||||
@@ -40,44 +24,51 @@ func TestNurseSharkSuspendsAndResumes(t *testing.T) {
|
||||
if spends != 1 {
|
||||
t.Fatalf("expected one trumpet spend, got %d", spends)
|
||||
}
|
||||
rocks := eventsOfType(g.Battle, "rock")
|
||||
rocks := eventsOfType(res, "rock")
|
||||
if len(rocks) != 1 || len(rocks[0].Dice) != 4 || !rocks[0].TargetDied {
|
||||
t.Fatalf("Nurse Shark should throw 4 rocks (2 per Trumpet) and kill Tank: %+v", rocks)
|
||||
}
|
||||
if g.Battle.WinnerSeat != 0 {
|
||||
t.Fatalf("seat 0 should win, got %d", g.Battle.WinnerSeat)
|
||||
if res.WinnerSeat != 0 {
|
||||
t.Fatalf("seat 0 should win, got %d", res.WinnerSeat)
|
||||
}
|
||||
}
|
||||
|
||||
// Choosing to spend zero Trumpets throws no rocks.
|
||||
func TestNurseSharkSpendZero(t *testing.T) {
|
||||
// The spend is capped at 3 Trumpets even when more are banked.
|
||||
func TestNurseSharkCapsAtThree(t *testing.T) {
|
||||
g, _, _ := testGame(t)
|
||||
g.RollDie = func() int { return 2 }
|
||||
forceBattle(t, g,
|
||||
[]Card{g.goldenPet(t, "Nyala"), g.goldenPet(t, "Nurse Shark")},
|
||||
[]Card{g.pet("Big", 4), g.pet("Tank", 6)},
|
||||
g.RollDie = func() int { return 1 } // each rock deals 1
|
||||
g.Players[0].PendingTrumpets = 5 // seed a pool larger than the cap
|
||||
res := forceBattle(t, g,
|
||||
[]Card{g.goldenPet(t, "Nurse Shark")},
|
||||
[]Card{g.pet("Wall", 20)},
|
||||
)
|
||||
if g.PendingBattle == nil {
|
||||
t.Fatal("expected a pending decision")
|
||||
var spend *BattleEvent
|
||||
for i, ev := range res.Events {
|
||||
if ev.Type == "trumpet" && ev.Count < 0 {
|
||||
spend = &res.Events[i]
|
||||
}
|
||||
}
|
||||
if err := g.BattleChoose(g.Players[0].ID, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
if spend == nil || spend.Count != -3 {
|
||||
t.Fatalf("Nurse Shark should spend exactly 3 Trumpets: %+v", eventsOfType(res, "trumpet"))
|
||||
}
|
||||
if len(eventsOfType(g.Battle, "rock")) != 0 {
|
||||
t.Fatal("spending zero Trumpets should throw no rocks")
|
||||
rocks := eventsOfType(res, "rock")
|
||||
if len(rocks) != 1 || len(rocks[0].Dice) != 6 {
|
||||
t.Fatalf("spending 3 Trumpets should throw 6 rocks: %+v", rocks)
|
||||
}
|
||||
}
|
||||
|
||||
// With no Trumpets, Nurse Shark's choice is trivial (max 0) and the rollout
|
||||
// path auto-resolves without suspending.
|
||||
func TestNurseSharkAutoResolvesInRollout(t *testing.T) {
|
||||
// With no Trumpets, Nurse Shark throws no rocks and the battle still resolves.
|
||||
func TestNurseSharkNoTrumpets(t *testing.T) {
|
||||
res := SimulateBattle(1, 0,
|
||||
[]Card{cardWithName("Nurse Shark", 3, []Effect{{Trigger: TriggerPlay, Action: ActionSpendRocks, Count: 3}})},
|
||||
[]Card{{ID: "x", Kind: KindPet, Name: "Foe", Power: 2}},
|
||||
func() int { return 2 },
|
||||
)
|
||||
if res == nil || res.WinnerSeat < -1 {
|
||||
t.Fatalf("rollout should complete without suspending: %+v", res)
|
||||
t.Fatalf("battle should resolve: %+v", res)
|
||||
}
|
||||
if len(eventsOfType(res, "rock")) != 0 {
|
||||
t.Fatal("with no Trumpets, Nurse Shark should throw no rocks")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,6 @@ func SimulateBattle(round, prioritySeat int, deckA, deckB []Card, rollDie func()
|
||||
{Name: "A", Seat: 0, Deck: append([]Card(nil), deckA...)},
|
||||
{Name: "B", Seat: 1, Deck: append([]Card(nil), deckB...)},
|
||||
},
|
||||
// Rollouts never pause for a mid-battle decision; a fixed policy resolves
|
||||
// them (Golden pack: Nurse Shark).
|
||||
autoBattleDecide: true,
|
||||
}
|
||||
g.startBattle()
|
||||
return g.Battle
|
||||
|
||||
@@ -53,11 +53,8 @@ type View struct {
|
||||
// see a reveal is in progress; the eligible options are only sent to the
|
||||
// buyer (they name the buyer's own hidden pets).
|
||||
PendingReveal *PendingReveal `json:"pendingReveal,omitempty"`
|
||||
// PendingBattle (Golden pack: Nurse Shark) is a mid-battle decision owed by
|
||||
// one seat; public since the battle replay is public.
|
||||
PendingBattle *PendingBattleDecision `json:"pendingBattle,omitempty"`
|
||||
Battle *BattleResult `json:"battle,omitempty"`
|
||||
WinnerSeat int `json:"winnerSeat"`
|
||||
Battle *BattleResult `json:"battle,omitempty"`
|
||||
WinnerSeat int `json:"winnerSeat"`
|
||||
// Log is the shared, public event log shown across every phase.
|
||||
Log []LogEntry `json:"log,omitempty"`
|
||||
// Debug is set by the server when its DEBUG flag is on, unlocking the
|
||||
@@ -125,7 +122,6 @@ func (g *Game) ViewFor(playerID string) View {
|
||||
}
|
||||
v.PendingReveal = &reveal
|
||||
}
|
||||
v.PendingBattle = g.PendingBattle
|
||||
// Battle results (lineups, events) are public once resolved. Keep the
|
||||
// battle around during the following shop phase too, so late joiners /
|
||||
// reconnects can still see the last result.
|
||||
|
||||
@@ -157,8 +157,6 @@ func applyBotAction(g *game.Game, playerID string, a *ai.Action) error {
|
||||
return g.TradeChoose(playerID, a.Pick)
|
||||
case "revealChoose":
|
||||
return g.RevealChoose(playerID, a.CardID)
|
||||
case "battleChoose":
|
||||
return g.BattleChoose(playerID, a.Value)
|
||||
case "pass":
|
||||
return g.Pass(playerID)
|
||||
case "arrange":
|
||||
@@ -203,9 +201,6 @@ func botFallback(g *game.Game, playerID string) error {
|
||||
}
|
||||
return g.SubmitOrder(playerID, ids)
|
||||
case game.PhaseBattle:
|
||||
if g.PendingBattle != nil && g.Players[g.PendingBattle.Seat].ID == playerID {
|
||||
return g.BattleChoose(playerID, g.PendingBattle.Max)
|
||||
}
|
||||
return g.AcknowledgeBattle(playerID)
|
||||
}
|
||||
return game.ErrInvalidAction
|
||||
|
||||
@@ -32,7 +32,6 @@ type clientMessage struct {
|
||||
Difficulty string `json:"difficulty"` // addBot
|
||||
Target string `json:"target"` // removePlayer (player ID)
|
||||
Card string `json:"card"` // revealChoose (Cockatoo): pet card id
|
||||
Value int `json:"value"` // battleChoose (Nurse Shark): Trumpets to spend
|
||||
}
|
||||
|
||||
type serverMessage struct {
|
||||
@@ -152,8 +151,6 @@ func (s *Server) apply(r *room, c *client, msg clientMessage) {
|
||||
err = g.TradeChoose(c.playerID, msg.Pick)
|
||||
case "revealChoose":
|
||||
err = g.RevealChoose(c.playerID, msg.Card)
|
||||
case "battleChoose":
|
||||
err = g.BattleChoose(c.playerID, msg.Value)
|
||||
case "pass":
|
||||
err = g.Pass(c.playerID)
|
||||
case "arrange":
|
||||
|
||||
Reference in New Issue
Block a user