aboutsummaryrefslogtreecommitdiff
path: root/go
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2024-12-26 18:52:15 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-08-20 15:57:11 +0200
commit59cf4dd9b651c10bc323985d68c37a1053837f40 (patch)
tree6a0569f63e3269cec8a9756ca2268e989c19374d /go
parent65451383dcd1754c558379abf6e36d4cba5eee4a (diff)
downloadmuhqs-game-59cf4dd9b651c10bc323985d68c37a1053837f40.tar.gz
muhqs-game-59cf4dd9b651c10bc323985d68c37a1053837f40.zip
pass the PlayAction to the cards onPlay implementations
This is done to propagate the chosen variadic play cost to the card implementation.
Diffstat (limited to 'go')
-rw-r--r--go/game/action.go42
-rw-r--r--go/game/card.go16
-rw-r--r--go/game/cardImplementations.go114
-rw-r--r--go/game/cardParsing.go6
-rw-r--r--go/game/state.go7
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())
}