aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-02-15 15:49:50 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-08-20 15:50:10 +0200
commitb810c024c301fdbe3d0833422bd18449d66d276a (patch)
tree9fe207e1dc0c74c8670a9a86d9bda0eb88ed6b07
parent259b2f2b2af8f23bc2fe193838e7f7ecfc61c23a (diff)
downloadmuhqs-game-b810c024c301fdbe3d0833422bd18449d66d276a.tar.gz
muhqs-game-b810c024c301fdbe3d0833422bd18449d66d276a.zip
implement the basics of the rest of the base cards
* Add more TODOs * Implement permanents granting more spawn tiles
-rw-r--r--go/TODO8
-rw-r--r--go/game/card.go18
-rw-r--r--go/game/cardImplementations.go102
-rw-r--r--go/game/cardParsing.go22
-rw-r--r--go/game/state.go12
-rw-r--r--go/game/tile.go4
6 files changed, 156 insertions, 10 deletions
diff --git a/go/TODO b/go/TODO
index 74869443..f2cde287 100644
--- a/go/TODO
+++ b/go/TODO
@@ -1,3 +1,11 @@
+* implement pile dropping
+* implement artifcat action
+ * move
+ * switch
+* implement equipment actions
+ * equip
+ * drop
+* implement target disjunction
* implement Pile UI hint
* implement spell target parsing / selection
* implement triggers
diff --git a/go/game/card.go b/go/game/card.go
index 031b95bf..7701b5f2 100644
--- a/go/game/card.go
+++ b/go/game/card.go
@@ -71,6 +71,15 @@ var CardTypeNames = map[string]CardType{
"intention": intentionType,
}
+func (ct CardType) IsPermanent() bool {
+ switch ct {
+ case CardTypes.Unit, CardTypes.Artifact, CardTypes.Equipment, CardTypes.Potion:
+ return true
+ default:
+ return false
+ }
+}
+
type cardImplementation interface {
spawnTiles(*State, *Player) []*Tile
fullActions(*Unit) []*FullAction
@@ -78,6 +87,8 @@ type cardImplementation interface {
stateBasedActions(*State, Permanent)
+ additionalSpawnsFor(Permanent, CardType) []*Tile
+
onEntering(*Tile)
onLeaving(*Tile)
onPlay(*State, *Card, *Player, *Targets)
@@ -98,6 +109,8 @@ func (*cardImplementationBase) freeActions(Permanent) []*FreeAction { return nil
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) {}
@@ -306,10 +319,7 @@ func (c *Card) Path() string {
}
func (c *Card) IsPermanent() bool {
- return c.Type == CardTypes.Unit ||
- c.Type == CardTypes.Artifact ||
- c.Type == CardTypes.Equipment ||
- c.Type == CardTypes.Potion
+ return c.Type.IsPermanent()
}
func (c *Card) IsToken() bool {
diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go
index 038ce2d6..32208f09 100644
--- a/go/game/cardImplementations.go
+++ b/go/game/cardImplementations.go
@@ -10,6 +10,12 @@ func adjustMelee(p Permanent, damage int) {
}
}
+func adjustHealth(p Permanent, delta int) {
+ if u, ok := p.(*Unit); ok {
+ u.Health += delta
+ }
+}
+
// ====== Base Set ======
type advisorImpl struct{ cardImplementationBase }
@@ -64,6 +70,96 @@ func (*misinformationImpl) onPlay(s *State, c *Card, _ *Player, _ *Targets) {
s.Exile.AddCard(c)
}
+type pioneerImpl struct{ cardImplementationBase }
+
+func (*pioneerImpl) fullActions(u *Unit) []*FullAction {
+ resolvePrototype := func(a Action) ActionResolveFunc {
+ return func(s *State) {
+ target := a.Target().sel[0]
+ if artifact, ok := target.(*Artifact); ok {
+ s.DestroyPermanent(artifact)
+ } else {
+ tile := target.(*Tile)
+ tile.neutralize()
+ }
+ }
+ }
+
+ s := u.Controller().gameState
+ a := NewFullAction(u, resolvePrototype, "neutralize tile or artifact")
+ // TODO: add target disjunktion 'or'
+ a.targets = newTargets(newTarget(s, newTargetDesc("adjacent tile or artifcat"), a))
+ return []*FullAction{a}
+}
+
+type recruiterImpl struct{ cardImplementationBase }
+
+func (*recruiterImpl) fullActions(u *Unit) []*FullAction {
+ resolvePrototype := func(a Action) ActionResolveFunc {
+ u := a.Source().(*Unit)
+ return func(s *State) {
+ t := a.Target().sel[0].(*Tile)
+ s.AddNewUnit(NewCard("base/recruit"), t.Position, u.Controller())
+ }
+ }
+
+ s := u.Controller().gameState
+ a := NewFullAction(u, resolvePrototype, "create recruit")
+ a.targets = newTargets(newTarget(s, newTargetDesc("adjacent tile"), a))
+ return []*FullAction{a}
+}
+
+func (*recruiterImpl) additionalSpawnsFor(p Permanent, ct CardType) []*Tile {
+ if ct != CardTypes.Unit {
+ return nil
+ }
+
+ s := p.Controller().gameState
+ return TilesInRange(s.Map, p, 1)
+}
+
+type shieldImpl struct{ cardImplementationBase }
+
+func (*shieldImpl) onPile(p Permanent) { adjustHealth(p, 1) }
+func (*shieldImpl) onUnpile(p Permanent) { adjustHealth(p, -1) }
+
+type taxCollectorImpl struct{ cardImplementationBase }
+
+func (*taxCollectorImpl) fullActions(u *Unit) []*FullAction {
+ resolvePrototype := func(a Action) ActionResolveFunc {
+ u := a.Source().(*Unit)
+ return func(s *State) {
+ controller := u.Controller()
+ controller.gainResource(2)
+ }
+ }
+
+ a := NewFullAction(u, resolvePrototype, "gain 2")
+ return []*FullAction{a}
+}
+
+type towerShieldImpl struct{ cardImplementationBase }
+
+func (*towerShieldImpl) onPile(p Permanent) { adjustHealth(p, 2) }
+func (*towerShieldImpl) onUnpile(p Permanent) { adjustHealth(p, -2) }
+
+type wormtongueImpl struct{ cardImplementationBase }
+
+func (*wormtongueImpl) fullActions(u *Unit) []*FullAction {
+ resolvePrototype := func(a Action) ActionResolveFunc {
+ return func(s *State) {
+ t := a.Target().sel[0].(*Player)
+ t.DiscardPile.AddCard(NewCard("base/misinformation"))
+ }
+ }
+
+ s := u.Controller().gameState
+ a := NewFullAction(u, resolvePrototype, "misinform")
+ // TODO: implement player targeting
+ a.targets = newTargets(newTarget(s, newTargetDesc("opponent"), a))
+ return []*FullAction{a}
+}
+
// ====== Nautics Set ======
var fishTrapAoE areaEffect = newGrantFullActionEffect("nautics/fisher",
@@ -316,6 +412,12 @@ func init() {
"base/sword": &swordImpl{},
"base/greatsword": &greatSwordImpl{},
"base/misinformation": &misinformationImpl{},
+ "base/pioneer": &pioneerImpl{},
+ "base/recruiter": &recruiterImpl{},
+ "base/shield": &shieldImpl{},
+ "base/tax_collector": &taxCollectorImpl{},
+ "base/tower_shield": &towerShieldImpl{},
+ "base/wormtongue": &wormtongueImpl{},
"nautics/captain": &captainImpl{},
"nautics/fish_trap": &fishTrapImpl{aoe: fishTrapAoE},
diff --git a/go/game/cardParsing.go b/go/game/cardParsing.go
index 315de293..ad231fd2 100644
--- a/go/game/cardParsing.go
+++ b/go/game/cardParsing.go
@@ -14,12 +14,15 @@ type dynamicCardImplementation struct {
genFreeActions func(Permanent) []*FreeAction
_stateBasedActions func(*State, Permanent)
- _onEntering func(*Tile)
- _onLeaving func(*Tile)
- _onPlay func(*State, *Card, *Player, *Targets)
- _onPile func(Permanent)
- _onUnpile func(Permanent)
- _onDrop func(Permanent)
+
+ _additionalSpawnsFor func(Permanent, CardType) []*Tile
+
+ _onEntering func(*Tile)
+ _onLeaving func(*Tile)
+ _onPlay func(*State, *Card, *Player, *Targets)
+ _onPile func(Permanent)
+ _onUnpile func(Permanent)
+ _onDrop func(Permanent)
}
func (impl *dynamicCardImplementation) spawnTiles(s *State, p *Player) []*Tile {
@@ -49,6 +52,13 @@ func (impl *dynamicCardImplementation) stateBasedActions(s *State, p Permanent)
}
}
+func (impl *dynamicCardImplementation) additionalSpawnsFor(p Permanent, ct CardType) []*Tile {
+ if impl._additionalSpawnsFor != nil {
+ return impl._additionalSpawnsFor(p, ct)
+ }
+ return nil
+}
+
func (impl *dynamicCardImplementation) onEntering(t *Tile) {
if impl._onEntering != nil {
impl._onEntering(t)
diff --git a/go/game/state.go b/go/game/state.go
index 5c2fd053..a5a9de82 100644
--- a/go/game/state.go
+++ b/go/game/state.go
@@ -409,6 +409,16 @@ func (s *State) resolvePlay(p *Player, c *Card, targets *Targets) {
}
}
+func (s *State) additionalSpawnsFor(p *Player, cType CardType) (spawns []*Tile) {
+ for _, perm := range s.Permanents {
+ if perm.Controller() != p {
+ continue
+ }
+ spawns = append(spawns, perm.Card().Impl.additionalSpawnsFor(perm, cType)...)
+ }
+ return
+}
+
func (s *State) SpawnTiles(p *Player) []*Tile {
spawns := s.Map.FilterTiles(func(t *Tile) bool {
if t.Type != TileTypes.Spawn {
@@ -459,6 +469,8 @@ func (s *State) AvailableSpawnTiles(p *Player, c *Card) []*Tile {
} else {
candidates = s.SpawnTiles(p)
}
+
+ candidates = append(candidates, s.additionalSpawnsFor(p, c.Type)...)
}
tiles := []*Tile{}
diff --git a/go/game/tile.go b/go/game/tile.go
index ce7518cb..9034203d 100644
--- a/go/game/tile.go
+++ b/go/game/tile.go
@@ -197,6 +197,10 @@ func (t *Tile) removeEffect(effect areaEffect) {
}
func (t *Tile) neutralize() {
+ if t.Type == TileTypes.Farm {
+ t.removeEffect(farmEffect)
+ }
+
t.Type = TileTypes.Neutral
t.Water = false
}