From 5684983b963eeb2f498028360ebc17cc2fb32564 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Thu, 26 Dec 2024 18:52:15 +0100 Subject: pass the PlayAction to the cards onPlay implementations This is done to propagate the chosen variadic play cost to the card implementation. --- go/game/action.go | 42 +++++++++------ go/game/card.go | 16 +++--- go/game/cardImplementations.go | 114 +++++++++++++++++++++++------------------ go/game/cardParsing.go | 6 +-- go/game/state.go | 7 ++- 5 files changed, 106 insertions(+), 79 deletions(-) diff --git a/go/game/action.go b/go/game/action.go index 30bad0b1..5a8fc35b 100644 --- a/go/game/action.go +++ b/go/game/action.go @@ -192,6 +192,7 @@ func newPileDropAction(perm Permanent, tile *Tile, pile []Permanent) *PileDropAc type PlayAction struct { ActionBase + ChoosenVariadicCost int } var ( @@ -199,11 +200,24 @@ var ( equipmentPlayActionTarget = TargetDesc{"available spawn tile", "?"} ) +func NewPlayActionCostFunc(player *Player, cost int) ActionCostFunc { + return func(*State) bool { + if player.Resource < cost { + return false + } + player.Resource -= cost + return true + } +} + func NewPlayAction(p *Player, c *Card, args ...interface{}) *PlayAction { - a := &PlayAction{ActionBase{ - source: p, - Card: c, - }} + a := &PlayAction{ + ActionBase{ + source: p, + Card: c, + }, + -1, + } if len(args) > 0 { if costFunc, ok := args[0].(ActionCostFunc); ok { @@ -211,19 +225,11 @@ func NewPlayAction(p *Player, c *Card, args ...interface{}) *PlayAction { } } + s := p.gameState if a.costFunc == nil { - a.costFunc = func(s *State) bool { - cost := c.PlayCosts.Costs(s) - - if p.Resource < cost { - return false - } - p.Resource -= cost - return true - } + a.costFunc = NewPlayActionCostFunc(p, c.PlayCosts.Costs(s)) } - s := p.gameState if c.IsPermanent() { if c.Type == CardTypes.Equipment { a.targets = newTargets(newTarget(s, equipmentPlayActionTarget, a)) @@ -237,12 +243,18 @@ func NewPlayAction(p *Player, c *Card, args ...interface{}) *PlayAction { } a.resolveFunc = func(s *State) { - s.resolvePlay(p, c, a.targets) + s.resolvePlay(a) } return a } +func NewPlayActionVariadicCosts(p *Player, c *Card, variadicCosts int) *PlayAction { + a := NewPlayAction(p, c, NewPlayActionCostFunc(p, c.PlayCosts.Costs(p.gameState, variadicCosts))) + a.ChoosenVariadicCost = variadicCosts + return a +} + func (a *PlayAction) String() string { p := a.source.(*Player) if a.targets.RequireSelection() { diff --git a/go/game/card.go b/go/game/card.go index be198d5a..ee16346c 100644 --- a/go/game/card.go +++ b/go/game/card.go @@ -102,7 +102,7 @@ type cardImplementation interface { onEntering(*Tile) onLeaving(*Tile) - onPlay(*State, *Card, *Player, *Targets) + onPlay(*PlayAction) onETB(*State, Permanent) onPile(containing Permanent) onUnpile(containing Permanent) @@ -125,13 +125,13 @@ func (*cardImplementationBase) stateBasedActions(*State, Permanent) {} func (*cardImplementationBase) additionalSpawnsFor(Permanent, CardType) []*Tile { return nil } -func (*cardImplementationBase) onEntering(*Tile) {} -func (*cardImplementationBase) onLeaving(*Tile) {} -func (*cardImplementationBase) onPlay(*State, *Card, *Player, *Targets) {} -func (*cardImplementationBase) onETB(*State, Permanent) {} -func (*cardImplementationBase) onPile(Permanent) {} -func (*cardImplementationBase) onUnpile(Permanent) {} -func (*cardImplementationBase) onDrop(Permanent) {} +func (*cardImplementationBase) onEntering(*Tile) {} +func (*cardImplementationBase) onLeaving(*Tile) {} +func (*cardImplementationBase) onPlay(*PlayAction) {} +func (*cardImplementationBase) onETB(*State, Permanent) {} +func (*cardImplementationBase) onPile(Permanent) {} +func (*cardImplementationBase) onUnpile(Permanent) {} +func (*cardImplementationBase) onDrop(Permanent) {} // Map of all card implementations var cardImplementations map[string]cardImplementation diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go index ac2b5431..8213432b 100644 --- a/go/game/cardImplementations.go +++ b/go/game/cardImplementations.go @@ -108,8 +108,8 @@ func (*greatSwordImpl) onUnpile(p Permanent) { adjustMelee(p, -2) } type misinformationImpl struct{ cardImplementationBase } -func (*misinformationImpl) onPlay(s *State, c *Card, _ *Player, _ *Targets) { - s.Exile.AddCard(c) +func (*misinformationImpl) onPlay(a *PlayAction) { + a.GameState().Exile.AddCard(a.Card) } type pioneerImpl struct{ cardImplementationBase } @@ -209,8 +209,9 @@ func (*attackImpl) playTargets() TargetDesc { return TargetDesc{"unit", "0-2"} } -func (*attackImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - sel := t.Cur().sel +func (*attackImpl) onPlay(a *PlayAction) { + s := a.GameState() + sel := a.targets.Cur().sel switch len(sel) { case 1: s.addEotEffect(newAttackModificationEffect(sel[0].(*Unit), 2)) @@ -226,15 +227,19 @@ func (*appearImpl) playTargets() TargetDesc { return newTargetDesc("free tile") } -func (*appearImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - selection := p.PromptHandCardSelection(1, 1) +func (*appearImpl) onPlay(a *PlayAction) { + selection := a.Controller().PromptHandCardSelection(1, 1) if len(selection) != 1 || selection[0].Type != CardTypes.Unit { + return } unitCard := selection[0] - p.Hand.RemoveCard(unitCard) + if unitCard.PlayCosts.Costs(a.GameState()) > a.ChoosenVariadicCost { + return + } + a.Controller().Hand.RemoveCard(unitCard) - tile := t.Cur().sel[0].(*Tile) - s.addNewUnit(unitCard, tile.Position, p) + tile := a.targets.Cur().sel[0].(*Tile) + a.GameState().addNewUnit(unitCard, tile.Position, a.Controller()) } type healImpl struct{ cardImplementationBase } @@ -243,8 +248,8 @@ func (*healImpl) playTargets() TargetDesc { return newTargetDesc("unit") } -func (*healImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - u := t.Cur().sel[0].(*Unit) +func (*healImpl) onPlay(a *PlayAction) { + u := a.targets.Cur().sel[0].(*Unit) // TODO: prompt damage or poison choice u.adjustDamage(-2) } @@ -257,32 +262,32 @@ func (*dieImpl) playTargets() TargetDesc { type moreImpl struct{ cardImplementationBase } -func (*moreImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - p.DrawN(2) +func (*moreImpl) onPlay(a *PlayAction) { + a.Controller().DrawN(2) } type mineImpl struct{ cardImplementationBase } -func (*mineImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { +func (*mineImpl) onPlay(a *PlayAction) { stolen := 0 - for _, player := range s.Players { - if p == player || !p.IsEnemy(player) { + for _, player := range a.GameState().Players { + if a.Controller() == player || !a.Controller().IsEnemy(player) { continue } player.reduceResource(2) stolen += 2 } - p.gainResource(stolen) + a.Controller().gainResource(stolen) } -func (*dieImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - s.destroyPermanent(t.Cur().sel[0].(Permanent)) +func (*dieImpl) onPlay(a *PlayAction) { + a.GameState().destroyPermanent(a.targets.Cur().sel[0].(Permanent)) } type ritualImpl struct{ cardImplementationBase } -func (*ritualImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - p.gainResource(3) +func (*ritualImpl) onPlay(a *PlayAction) { + a.Controller().gainResource(3) } type rushImpl struct{ cardImplementationBase } @@ -291,8 +296,9 @@ func (*rushImpl) playTargets() TargetDesc { return TargetDesc{"unit", "0-2"} } -func (*rushImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - sel := t.Cur().sel +func (*rushImpl) onPlay(a *PlayAction) { + s := a.GameState() + sel := a.targets.Cur().sel switch len(sel) { case 1: s.addEotEffect(newMovementModificationEffect(sel[0].(*Unit), 2)) @@ -304,11 +310,12 @@ func (*rushImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { type selectImpl struct{ cardImplementationBase } -func (*selectImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { +func (*selectImpl) onPlay(a *PlayAction) { + p := a.Controller() p.DrawN(1) cards := p.PromptHandCardSelection(0, 1) if cards != nil { - p.Hand.MoveCard(cards[0], s.Exile) + p.Hand.MoveCard(cards[0], a.GameState().Exile) } } @@ -318,9 +325,9 @@ func (*shroudImpl) playTargets() TargetDesc { return newTargetDesc("unit") } -func (*shroudImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - u := t.Cur().sel[0].(*Unit) - s.addEotEffect(newTmpEffect(u, "shroud")) +func (*shroudImpl) onPlay(a *PlayAction) { + u := a.targets.Cur().sel[0].(*Unit) + a.GameState().addEotEffect(newTmpEffect(u, "shroud")) } type switchImpl struct{ cardImplementationBase } @@ -329,9 +336,9 @@ func (*switchImpl) playTargets() TargetDesc { return TargetDesc{"permanent", "2"} } -func (*switchImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - perms := utils.InterfaceSliceToTypedSlice[Permanent](t.Cur().sel) - s.switchPermanents(perms[0], perms[1]) +func (*switchImpl) onPlay(a *PlayAction) { + perms := utils.InterfaceSliceToTypedSlice[Permanent](a.targets.Cur().sel) + a.GameState().switchPermanents(perms[0], perms[1]) } type transmuteImpl struct{ cardImplementationBase } @@ -340,10 +347,10 @@ func (*transmuteImpl) playTargets() TargetDesc { return newTargetDesc("store card") } -func (*transmuteImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { - storeCard := t.Cur().sel[0].(*Card) - p.Store.MoveCard(storeCard, p.Hand) - s.Exile.AddCard(c) +func (*transmuteImpl) onPlay(a *PlayAction) { + storeCard := a.targets.Cur().sel[0].(*Card) + a.Controller().Store.MoveCard(storeCard, a.Controller().Hand) + a.GameState().Exile.AddCard(a.Card) } // ====== Nautics Set ====== @@ -423,24 +430,25 @@ func (*sailorImpl) onUnpile(containing Permanent) { type tidesChangeImpl struct{ cardImplementationBase } -func (*tidesChangeImpl) onPlay(s *State, _ *Card, _ *Player, _ *Targets) { - s.redistributeMapStoreCards() +func (*tidesChangeImpl) onPlay(a *PlayAction) { + a.GameState().redistributeMapStoreCards() } type dejaVuImpl struct{ cardImplementationBase } -func (*dejaVuImpl) onPlay(s *State, _ *Card, _ *Player, _ *Targets) { - for _, p := range s.Players { +func (*dejaVuImpl) onPlay(a *PlayAction) { + for _, p := range a.GameState().Players { if !p.IsKraken() { - p.Resource = s.Map.ResourceGain + p.Resource = a.GameState().Map.ResourceGain } } } type devourThePoorImpl struct{ cardImplementationBase } -func (*devourThePoorImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { - for _, u := range s.EnemyUnits(kraken) { +func (*devourThePoorImpl) onPlay(a *PlayAction) { + s := a.GameState() + for _, u := range s.EnemyUnits(a.Controller()) { if !u.Card().IsBuyable() || u.Card().BuyCosts.Costs(s) < 7 { s.destroyPermanent(u) } @@ -552,7 +560,8 @@ type giganticHailImpl struct { func (*giganticHailImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } -func (*giganticHailImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { +func (*giganticHailImpl) onPlay(a *PlayAction) { + s := a.GameState() emptyTiles := s.Map.FreeTiles() choices := len(emptyTiles) for i := 0; i < 3; i++ { @@ -561,7 +570,7 @@ func (*giganticHailImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { } choice := s.Rand.Intn(choices - 1) - s.addNewArtifact(NewCard("kraken/ice_berg"), emptyTiles[choice].Position, kraken) + s.addNewArtifact(NewCard("kraken/ice_berg"), emptyTiles[choice].Position, a.Controller()) emptyTiles[choice] = emptyTiles[choices-1] choices-- } @@ -569,7 +578,8 @@ func (*giganticHailImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { type soldOutImpl struct{ cardImplementationBase } -func (*soldOutImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { +func (*soldOutImpl) onPlay(a *PlayAction) { + s := a.GameState() nStores := len(s.Map.Stores) storePositions := make([]Position, 0, nStores) for pos := range s.Map.Stores { @@ -585,17 +595,17 @@ func (*soldOutImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { type supriseImpl struct{ cardImplementationBase } -func (*supriseImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { - kraken.Ctrl.(*KrakenControl).activeSuprise = true +func (*supriseImpl) onPlay(a *PlayAction) { + a.Controller().Ctrl.(*KrakenControl).activeSuprise = true } type tentacleSlapImpl struct{ cardImplementationBase } func (*tentacleSlapImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } -func (*tentacleSlapImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { +func (*tentacleSlapImpl) onPlay(a *PlayAction) { x := 0 - for _, p := range s.Players { + for _, p := range a.GameState().Players { if p.IsKraken() { continue } @@ -606,7 +616,7 @@ func (*tentacleSlapImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { gain := 5 - x if gain > 0 { - kraken.gainResource(gain) + a.Controller().gainResource(gain) } } @@ -614,7 +624,9 @@ type unholyCannonballImpl struct{ cardImplementationBase } func (*unholyCannonballImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } -func (*unholyCannonballImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { +func (*unholyCannonballImpl) onPlay(a *PlayAction) { + s := a.GameState() + kraken := a.Controller() krakenTiles := s.Map.FilterTiles(func(t *Tile) bool { return t.Raw == "the kraken" }) if len(krakenTiles) != 1 { log.Fatal("No kraken tile found in Map") diff --git a/go/game/cardParsing.go b/go/game/cardParsing.go index 2a53926e..58dea594 100644 --- a/go/game/cardParsing.go +++ b/go/game/cardParsing.go @@ -21,7 +21,7 @@ type dynamicCardImplementation struct { _onEntering func(*Tile) _onLeaving func(*Tile) - _onPlay func(*State, *Card, *Player, *Targets) + _onPlay func(*PlayAction) _onETB func(*State, Permanent) _onPile func(Permanent) _onUnpile func(Permanent) @@ -78,9 +78,9 @@ func (impl *dynamicCardImplementation) onLeaving(t *Tile) { } } -func (impl *dynamicCardImplementation) onPlay(s *State, c *Card, p *Player, t *Targets) { +func (impl *dynamicCardImplementation) onPlay(a *PlayAction) { if impl._onPlay != nil { - impl._onPlay(s, c, p, t) + impl._onPlay(a) } } diff --git a/go/game/state.go b/go/game/state.go index 021d88ab..078815da 100644 --- a/go/game/state.go +++ b/go/game/state.go @@ -557,7 +557,10 @@ func (s *State) EnemyUnits(player *Player) []*Unit { }) } -func (s *State) resolvePlay(p *Player, c *Card, targets *Targets) { +func (s *State) resolvePlay(a *PlayAction) { + p := a.Controller() + c := a.Card + targets := a.targets switch c.Type { case CardTypes.Unit: tile := targets.ts[0].sel[0].(*Tile) @@ -579,7 +582,7 @@ func (s *State) resolvePlay(p *Player, c *Card, targets *Targets) { s.addNewEquipment(c, tile.Position, p) } case CardTypes.Spell: - c.Impl.onPlay(s, c, p, targets) + c.Impl.onPlay(a) default: log.Printf("Resolving unhandled card type: %s\n", c.Type.String()) } -- cgit v1.2.3