diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-02-07 00:32:39 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:50:08 +0200 |
| commit | 0b12a11962ae0c94945a2b599d0b28f24c6428cf (patch) | |
| tree | 640aac52133738ced7cdd17168292c5fec482559 | |
| parent | 4db8db0e847552077be9ebd9177ddc573c2b51d4 (diff) | |
| download | muhqs-game-0b12a11962ae0c94945a2b599d0b28f24c6428cf.tar.gz muhqs-game-0b12a11962ae0c94945a2b599d0b28f24c6428cf.zip | |
intermediate commit
adding area effects and hooks for permanents entering and leaving tiles
| -rw-r--r-- | go/TODO | 10 | ||||
| -rw-r--r-- | go/game/ai.go | 1 | ||||
| -rw-r--r-- | go/game/areaEffect.go | 27 | ||||
| -rw-r--r-- | go/game/areaEffect_test.go | 69 | ||||
| -rw-r--r-- | go/game/artifact.go | 4 | ||||
| -rw-r--r-- | go/game/card.go | 23 | ||||
| -rw-r--r-- | go/game/cardImplementations.go | 232 | ||||
| -rw-r--r-- | go/game/cardParsing.go | 56 | ||||
| -rw-r--r-- | go/game/equipment.go | 1 | ||||
| -rw-r--r-- | go/game/map.go | 4 | ||||
| -rw-r--r-- | go/game/marks.go | 68 | ||||
| -rw-r--r-- | go/game/permanent.go | 105 | ||||
| -rw-r--r-- | go/game/player.go | 6 | ||||
| -rw-r--r-- | go/game/state.go | 28 | ||||
| -rw-r--r-- | go/game/tile.go | 38 | ||||
| -rw-r--r-- | go/game/unit.go | 62 | ||||
| -rw-r--r-- | go/game/unit_test.go | 6 |
17 files changed, 571 insertions, 169 deletions
diff --git a/go/TODO b/go/TODO new file mode 100644 index 00000000..23274884 --- /dev/null +++ b/go/TODO @@ -0,0 +1,10 @@ +* implement upkeep action +* implement target selection for triggers +* implement Pile UI hint +* implement spell target parsing / selection +* implement triggers +* implement draft +* implement game log +* finish AIs + * shy ai + * target-oriented diff --git a/go/game/ai.go b/go/game/ai.go index 181f6dc1..daeeff4f 100644 --- a/go/game/ai.go +++ b/go/game/ai.go @@ -317,6 +317,7 @@ func WanderingAI(ai *UnitAI, x int) { break } } + // 2. if ai.u.AvailMoveActions > 0 { moveToRandomTile(ai) diff --git a/go/game/areaEffect.go b/go/game/areaEffect.go new file mode 100644 index 00000000..8991bc3c --- /dev/null +++ b/go/game/areaEffect.go @@ -0,0 +1,27 @@ +package game + +type areaEffect interface { + onEntering(Permanent) + onLeaving(Permanent) +} + +type dynamicAreaEffect struct { + _onEntering func(Permanent) + _onLeaving func(Permanent) +} + +func newDynamicAreaEffect(enter func(Permanent), leave func(Permanent)) *dynamicAreaEffect { + return &dynamicAreaEffect{enter, leave} +} + +func (e *dynamicAreaEffect) onEntering(p Permanent) { + if e._onEntering != nil { + e._onEntering(p) + } +} + +func (e *dynamicAreaEffect) onLeaving(p Permanent) { + if e._onLeaving != nil { + e._onLeaving(p) + } +} diff --git a/go/game/areaEffect_test.go b/go/game/areaEffect_test.go new file mode 100644 index 00000000..4f55bffd --- /dev/null +++ b/go/game/areaEffect_test.go @@ -0,0 +1,69 @@ +package game + +import ( + "strings" + "testing" +) + +func TestEnteringLeaving(t *testing.T) { + mapDef := `map: |1- + HH +symbols: + H: house +` + s := NewState() + r := strings.NewReader(mapDef) + s.Map, _ = readMap(r) + + s.AddNewPlayer("player", NewDeck()) + p := s.Players[0] + + t0_0 := s.Map.TileAt(Position{0, 0}) + entered0_0, left0_0 := false, false + t0_0.effects = append(t0_0.effects, newDynamicAreaEffect( + func(Permanent) { entered0_0 = true }, + func(Permanent) { left0_0 = true }, + )) + + t0_1 := s.Map.TileAt(Position{1, 0}) + entered0_1, left0_1 := false, false + t0_1.effects = append(t0_1.effects, newDynamicAreaEffect( + func(Permanent) { entered0_1 = true }, + func(Permanent) { left0_1 = true }, + )) + + palisade := NewUnit(NewCard("base/archer"), t0_0, p) + s.AddPermanent(palisade) + if !entered0_0 || palisade.Tile() != t0_0 || t0_0.Permanent != palisade { + t.Fatalf("palisade did not enter tile %v", t0_0) + } + + s.DestroyPermanent(palisade) + if !left0_0 || palisade.Tile() != nil || t0_0.Permanent != nil { + t.Fatalf("destroyed palisade did not leave tile %v", t0_0) + } + + entered0_0, left0_0 = false, false + archer := NewUnit(NewCard("base/archer"), t0_0, p) + s.AddPermanent(archer) + if !entered0_0 || archer.Tile() != t0_0 || t0_0.Permanent != archer { + t.Fatalf("archer did not enter tile %v", t0_0) + } + + archer.move(s, t0_1) + if !left0_0 || t0_0.Permanent != nil { + t.Fatalf("moved archer did not leave tile %v", t0_0) + } + + if !entered0_1 || archer.Tile() != t0_1 || t0_1.Permanent != archer { + t.Fatalf("moved archer did not leave tile %v", t0_1) + } + + s.DestroyPermanent(archer) + if !left0_1 || archer.Tile() != nil || t0_1.Permanent != nil { + t.Fatalf("destroyed archer did not leave tile %v", t0_1) + } + + sword := newEquipmentFromPath("base/sword", t, p) + s.AddPermanent(sword) +} diff --git a/go/game/artifact.go b/go/game/artifact.go index 916917d8..50fa2653 100644 --- a/go/game/artifact.go +++ b/go/game/artifact.go @@ -19,10 +19,6 @@ func NewArtifactFromPath(cardPath string, tile *Tile, owner *Player) *Artifact { func NewArtifact(card *Card, tile *Tile, owner *Player) *Artifact { a := &Artifact{NewPermanentBase(card, tile, owner), 0} - if tile != nil { - tile.Permanent = a - } - effects := card.getEffects() for _, e := range effects { if strings.HasPrefix(e, "Solid") { diff --git a/go/game/card.go b/go/game/card.go index b1d913a8..c24e7585 100644 --- a/go/game/card.go +++ b/go/game/card.go @@ -76,10 +76,14 @@ type cardImplementation interface { fullActions(*Unit) []*FullAction freeActions(Permanent) []*FreeAction + stateBasedActions(*State, Permanent) + + onEntering(*Tile) + onLeaving(*Tile) onPlay(*State, *Player, *Targets) - onPile(Permanent) - onUnpile(Permanent) - onDrop(Permanent) + onPile(containing Permanent) + onUnpile(containing Permanent) + onDrop(dropped Permanent) } // Default implementation of the cardImplementation interface. @@ -91,10 +95,15 @@ type cardImplementationBase struct{} func (*cardImplementationBase) spawnTiles(*State, *Player) []*Tile { return nil } func (*cardImplementationBase) fullActions(*Unit) []*FullAction { return nil } func (*cardImplementationBase) freeActions(Permanent) []*FreeAction { return nil } -func (*cardImplementationBase) onPlay(*State, *Player, *Targets) {} -func (*cardImplementationBase) onPile(Permanent) {} -func (*cardImplementationBase) onUnpile(Permanent) {} -func (*cardImplementationBase) onDrop(Permanent) {} + +func (*cardImplementationBase) stateBasedActions(*State, Permanent) {} + +func (*cardImplementationBase) onEntering(*Tile) {} +func (*cardImplementationBase) onLeaving(*Tile) {} +func (*cardImplementationBase) onPlay(*State, *Player, *Targets) {} +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 271c88ea..cb32ab2c 100644 --- a/go/game/cardImplementations.go +++ b/go/game/cardImplementations.go @@ -1,14 +1,12 @@ package game -type farmerImplementation struct { - cardImplementationBase -} +import ( + "log" +) -type advisorImplementation struct { - cardImplementationBase -} +type advisorImpl struct{ cardImplementationBase } -func (impl *advisorImplementation) fullActions(u *Unit) []*FullAction { +func (impl *advisorImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *State) { @@ -23,61 +21,229 @@ func (impl *advisorImplementation) fullActions(u *Unit) []*FullAction { return []*FullAction{NewFullAction(u, resolvePrototype, "draw 1. exile 1")} } -type missionaryImplementation struct { - cardImplementationBase -} +type missionaryImpl struct{ cardImplementationBase } -func (impl *missionaryImplementation) fullActions(u *Unit) []*FullAction { +func (impl *missionaryImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) target := a.Targets().sel[0].(*Unit) return func(s *State) { - faith, found := target.States[UnitMarks.Faith] - if !found { - target.States[UnitMarks.Faith] = 2 - } else { - target.States[UnitMarks.Faith] = faith + 2 - if faith+2 > target.Card().BuyCost { - target.controller = u.Controller() - } + target.addMarks(UnitMarks.Faith, 2) + if target.Marks(UnitMarks.Faith) > target.Card().BuyCost { + target.controller = u.Controller() } } } return []*FullAction{NewFullAction(u, resolvePrototype, "put faith ...")} } -type swordImplementation struct { - cardImplementationBase -} +type swordImpl struct{ cardImplementationBase } -func (*swordImplementation) onPile(p Permanent) { +func (*swordImpl) onPile(p Permanent) { if u, ok := p.(*Unit); ok && !INVALID_ATTACK(u.Attack) { u.Attack.attacks[0] += 1 } } -func (*swordImplementation) onUnpile(p Permanent) { +func (*swordImpl) onUnpile(p Permanent) { if u, ok := p.(*Unit); ok && !INVALID_ATTACK(u.Attack) { u.Attack.attacks[0] -= 1 } } -type tidesChangeImplementation struct { +type tidesChangeImpl struct{ cardImplementation } + +func (*tidesChangeImpl) onPlay(s *State, _ *Player, _ *Targets) { + s.Map.redistributeStoreCards(s.Rand) +} + +type dejaVuImpl struct{ cardImplementation } + +func (*dejaVuImpl) onPlay(s *State, _ *Player, _ *Targets) { + for _, p := range s.Players { + if !p.IsKraken() { + p.Resource = s.Map.ResourceGain + } + } +} + +type devourThePoorImpl struct{ cardImplementation } + +func (*devourThePoorImpl) onPlay(s *State, kraken *Player, _ *Targets) { + for _, u := range s.EnemyUnits(kraken) { + if u.Card().BuyCost < 7 { + s.DestroyPermanent(u) + } + } +} + +type drownedSailorImpl struct{ cardImplementation } + +func (*drownedSailorImpl) onPile(containing Permanent) { + if u, ok := containing.(*Unit); ok && u.Movement.Swimming { + u.Attack.attacks[0] += 1 + } +} + +func (*drownedSailorImpl) onUnpile(containing Permanent) { + if u, ok := containing.(*Unit); ok && u.Movement.Swimming { + u.Attack.attacks[0] -= 1 + } +} + +func (*drownedSailorImpl) onDrop(sailor Permanent) { + sailor.Controller().gameState.DestroyPermanent(sailor) +} + +// TODO: implement death trigger +// TODO: implement hook for the containing permanent on pile +type flyingDutchmenImpl struct{ cardImplementation } + +type frostPylonAoE struct{} + +func (*frostPylonAoE) onEntering(Permanent) { + // TODO: prevent actions and grant new full action +} + +func (*frostPylonAoE) onLeaving(Permanent) { + // TODO: remove effect +} + +type frostPylon struct { cardImplementation + aoe frostPylonAoE } -func (*tidesChangeImplementation) onPlay(s *State, _ *Player, _ *Targets) { - s.Map.redistributeStoreCards(s.Rand) +func (*frostPylon) spawnTiles(s *State, kraken *Player) (spawns []*Tile) { + for _, u := range s.EnemyUnits(kraken) { + spawns = append(spawns, TilesInRange(s.Map, u, 1)...) + } + return +} + +func (*frostPylon) stateBasedActions(s *State, p Permanent) { + if p.Marks(PermanentMarks.Crack) >= 3 { + s.DestroyPermanent(p) + } +} + +func (i *frostPylon) onEntering(t *Tile) { + s := t.Permanent.Controller().gameState + for _, tile := range TilesInRange(s.Map, t.Permanent, 1) { + tile.addEffect(&i.aoe) + } +} + +func (i *frostPylon) onLeaving(t *Tile) { + s := t.Permanent.Controller().gameState + for _, tile := range TilesInRange(s.Map, t.Permanent, 1) { + tile.removeEffect(&i.aoe) + } +} + +type giganticHailImpl struct{ cardImplementation } + +func (*giganticHailImpl) onPlay(s *State, kraken *Player, _ *Targets) { + emptyTiles := s.Map.FreeTiles() + choices := len(emptyTiles) + for i := 0; i < 3; i++ { + if choices == 0 { + return + } + + choice := s.Rand.Intn(choices - 1) + s.AddNewArtifact(NewCard("kraken/ice_berg"), emptyTiles[choice].Position, kraken) + emptyTiles[choice] = emptyTiles[choices-1] + choices-- + } +} + +type soldOutImpl struct{ cardImplementation } + +func (*soldOutImpl) onPlay(s *State, kraken *Player, _ *Targets) { + nStores := len(s.Map.Stores) + storePositions := make([]Position, 0, nStores) + for pos := range s.Map.Stores { + storePositions = append(storePositions, pos) + } + + storePos := storePositions[s.Rand.Intn(nStores-1)] + s.Map.StoreOn(storePos).MoveInto(s.Exile) + + storeTile := s.Map.TileAt(storePos) + storeTile.neutralize() +} + +type supriseImpl struct{ cardImplementation } + +func (*supriseImpl) onPlay(s *State, kraken *Player, _ *Targets) { + // TODO: implement modification of next spawn tile +} + +type tentacleSlapImpl struct{ cardImplementation } + +func (*tentacleSlapImpl) onPlay(s *State, kraken *Player, _ *Targets) { + x := 0 + for _, p := range s.Players { + if p.IsKraken() { + continue + } + + x += p.Hand.Size() + p.discardHand() + } + + gain := 5 - x + if gain > 0 { + kraken.gainResource(gain) + } +} + +type unholyCannonballImpl struct{ cardImplementation } + +func (*unholyCannonballImpl) onPlay(s *State, kraken *Player, _ *Targets) { + 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") + } + + enemiesInRange2 := []*Unit{} + for _, u := range s.EnemyUnits(kraken) { + if u.Tile() == nil { + continue + } + + if IsPositionInRange(u.Tile().Position, krakenTiles[0].Position, 2) { + enemiesInRange2 = append(enemiesInRange2, u) + break + } + } + + if len(enemiesInRange2) == 0 { + kraken.gainResource(5) + } else { + for _, u := range enemiesInRange2 { + u.addMarks(UnitStates.Panic, 1) + } + } } func init() { cardImplementations = map[string]cardImplementation{ - "base/advisor": &advisorImplementation{}, - "base/missionary": &missionaryImplementation{}, - "base/sword": &swordImplementation{}, - - "misc/farmer": &farmerImplementation{}, + "base/advisor": &advisorImpl{}, + "base/missionary": &missionaryImpl{}, + "base/sword": &swordImpl{}, - "kraken/tides_change!": &tidesChangeImplementation{}, + "kraken/deja_vu!": &dejaVuImpl{}, + "kraken/devour_the_poor!": &devourThePoorImpl{}, + "kraken/drowned_sailor": &drownedSailorImpl{}, + "kraken/flying_dutchmen": &flyingDutchmenImpl{}, + "kraken/frost_pylon": &frostPylon{}, + "kraken/gigantic_hail": &giganticHailImpl{}, + "kraken/sold_out!": &soldOutImpl{}, + "kraken/suprise!": &supriseImpl{}, + "kraken/tentacle_slap": &tentacleSlapImpl{}, + "kraken/tides_change!": &tidesChangeImpl{}, + "kraken/unholy_cannonball": &unholyCannonballImpl{}, } } diff --git a/go/game/cardParsing.go b/go/game/cardParsing.go index 9fcb48aa..de1c7be4 100644 --- a/go/game/cardParsing.go +++ b/go/game/cardParsing.go @@ -7,64 +7,86 @@ import ( "strings" ) -type parsedCardImplementation struct { +type dynamicCardImplementation struct { card *Card spawnTilesFunc func(*State, *Player) []*Tile genFullActions func(*Unit) []*FullAction genFreeActions func(Permanent) []*FreeAction - _onPlay func(*State, *Player, *Targets) - _onPile func(Permanent) - _onUnpile func(Permanent) - _onDrop func(Permanent) + + _stateBasedActions func(*State, Permanent) + _onEntering func(*Tile) + _onLeaving func(*Tile) + _onPlay func(*State, *Player, *Targets) + _onPile func(Permanent) + _onUnpile func(Permanent) + _onDrop func(Permanent) } -func (impl *parsedCardImplementation) spawnTiles(s *State, p *Player) []*Tile { +func (impl *dynamicCardImplementation) spawnTiles(s *State, p *Player) []*Tile { if impl.spawnTilesFunc != nil { return impl.spawnTilesFunc(s, p) } return nil } -func (impl *parsedCardImplementation) fullActions(u *Unit) []*FullAction { +func (impl *dynamicCardImplementation) fullActions(u *Unit) []*FullAction { if impl.genFullActions != nil { return impl.genFullActions(u) } return nil } -func (impl *parsedCardImplementation) freeActions(p Permanent) []*FreeAction { +func (impl *dynamicCardImplementation) freeActions(p Permanent) []*FreeAction { if impl.genFreeActions != nil { return impl.genFreeActions(p) } return nil } -func (impl *parsedCardImplementation) onPlay(s *State, p *Player, t *Targets) { +func (impl *dynamicCardImplementation) stateBasedActions(s *State, p Permanent) { + if impl._stateBasedActions != nil { + impl._stateBasedActions(s, p) + } +} + +func (impl *dynamicCardImplementation) onEntering(t *Tile) { + if impl._onEntering != nil { + impl._onEntering(t) + } +} + +func (impl *dynamicCardImplementation) onLeaving(t *Tile) { + if impl._onLeaving != nil { + impl._onLeaving(t) + } +} + +func (impl *dynamicCardImplementation) onPlay(s *State, p *Player, t *Targets) { if impl._onPlay != nil { impl._onPlay(s, p, t) } } -func (impl *parsedCardImplementation) onPile(p Permanent) { +func (impl *dynamicCardImplementation) onPile(p Permanent) { if impl._onPile != nil { impl._onPile(p) } } -func (impl *parsedCardImplementation) onUnpile(p Permanent) { +func (impl *dynamicCardImplementation) onUnpile(p Permanent) { if impl._onUnpile != nil { impl._onUnpile(p) } } -func (impl *parsedCardImplementation) onDrop(p Permanent) { +func (impl *dynamicCardImplementation) onDrop(p Permanent) { if impl._onDrop != nil { impl._onDrop(p) } } -func newParsedCardImplementation(c *Card) *parsedCardImplementation { - impl := &parsedCardImplementation{card: c} +func newParsedCardImplementation(c *Card) *dynamicCardImplementation { + impl := &dynamicCardImplementation{card: c} for _, effect := range c.getEffects() { impl.parseEffect(effect) } @@ -76,13 +98,13 @@ func newParsedCardImplementation(c *Card) *parsedCardImplementation { return impl } -func (impl *parsedCardImplementation) parseEffect(effect string) { +func (impl *dynamicCardImplementation) parseEffect(effect string) { if strings.Contains(effect, "can be played") { impl.parsePlayModification(effect) } } -func (impl *parsedCardImplementation) parseFullAction(fullAction string) { +func (impl *dynamicCardImplementation) parseFullAction(fullAction string) { var resolveProtos []ActionFuncPrototype gainRessourceRegex := regexp.MustCompile(`gain [1-9][0-9]* resource`) @@ -190,7 +212,7 @@ func parseLocation(location string) func(interface{}, *State) []*Tile { return nil } -func (impl *parsedCardImplementation) parsePlayModification(effect string) { +func (impl *dynamicCardImplementation) parsePlayModification(effect string) { tokens := strings.SplitN(effect, " can be played ", 2) log.Println(tokens) what, how := tokens[0], tokens[1] diff --git a/go/game/equipment.go b/go/game/equipment.go index af06fdb1..89821458 100644 --- a/go/game/equipment.go +++ b/go/game/equipment.go @@ -19,7 +19,6 @@ func newEquipment(card *Card, containing interface{}, owner *Player) *Equipment switch containing := containing.(type) { case *Tile: e.tile = containing - containing.Permanent = e case *Unit: AddPermanentToPile(containing, e) } diff --git a/go/game/map.go b/go/game/map.go index 2115e0b0..b86debad 100644 --- a/go/game/map.go +++ b/go/game/map.go @@ -269,6 +269,10 @@ func (m *Map) AllTiles() []*Tile { return m.FilterTiles(func(*Tile) bool { return true }) } +func (m *Map) FreeTiles() []*Tile { + return m.FilterTiles(func(t *Tile) bool { return t.IsFree() }) +} + func (m *Map) distributeStoreCards(cards PileOfCards, rand *rand.Rand) { nStores := len(m.Stores) stores := make([]*Store, 0, nStores) diff --git a/go/game/marks.go b/go/game/marks.go new file mode 100644 index 00000000..a3ea0e32 --- /dev/null +++ b/go/game/marks.go @@ -0,0 +1,68 @@ +package game + +import ( + "log" +) + +type PermanentMark int + +const ( + crack PermanentMark = iota + 1 + paralysis + poison + panic_ + rage + ward + faith +) + +func (mark PermanentMark) String() string { + switch mark { + case crack: + return "crack" + case paralysis: + return "paralysis" + case poison: + return "poison" + case panic_: + return "panic" + case rage: + return "rage" + case ward: + return "ward" + case faith: + return "faith" + default: + log.Fatalf("Unknown permanent mark %d", mark) + return "" + } +} + +var ( + PermanentMarks = struct { + Crack PermanentMark + }{ + Crack: crack, + } + + unitStates = []PermanentMark{paralysis, poison, panic_, rage} + UnitStates = struct { + Paralysis PermanentMark + Poison PermanentMark + Panic PermanentMark + Rage PermanentMark + }{ + Paralysis: paralysis, + Poison: poison, + Panic: panic_, + Rage: rage, + } + + UnitMarks = struct { + Ward PermanentMark + Faith PermanentMark + }{ + Ward: ward, + Faith: faith, + } +) diff --git a/go/game/permanent.go b/go/game/permanent.go index 67c39e0b..9188a206 100644 --- a/go/game/permanent.go +++ b/go/game/permanent.go @@ -12,13 +12,17 @@ import ( type Permanent interface { Card() *Card Tile() *Tile + SetTile(*Tile) Damage() int + ContainingPerm() Permanent Pile() []Permanent Controller() *Player Owner() *Player Effects() []string HasEffect(string) bool XEffect(string) (int, error) + Marks(PermanentMark) int + addMarks(PermanentMark, int) String() string Fight(p Permanent) @@ -28,8 +32,9 @@ type Permanent interface { AvailActions() []Action IsAvailableTile(*Tile) bool - SetContainingPerm(p Permanent) - AddPermanentToPile(p Permanent) + setContainingPerm(p Permanent) + addPermanentToPile(p Permanent) + clearPile() RemovePermanentFromPile(p Permanent) onPile(containing Permanent) onUnpile(containing Permanent) @@ -45,6 +50,7 @@ type PermanentBase struct { controller *Player owner *Player tmpEffects []string + marks map[PermanentMark]int } func NewPermanentBase(card *Card, tile *Tile, owner *Player) PermanentBase { @@ -60,29 +66,14 @@ func NewPermanentBase(card *Card, tile *Tile, owner *Player) PermanentBase { } } -func (p *PermanentBase) Card() *Card { - return p.card -} - -func (p *PermanentBase) Tile() *Tile { - return p.tile -} - -func (p *PermanentBase) Damage() int { - return p.damage -} - -func (p *PermanentBase) Pile() []Permanent { - return p.pile -} - -func (p *PermanentBase) Controller() *Player { - return p.controller -} - -func (p *PermanentBase) Owner() *Player { - return p.owner -} +func (p *PermanentBase) Card() *Card { return p.card } +func (p *PermanentBase) Tile() *Tile { return p.tile } +func (p *PermanentBase) SetTile(t *Tile) { p.tile = t } +func (p *PermanentBase) Damage() int { return p.damage } +func (p *PermanentBase) ContainingPerm() Permanent { return p.containingPerm } +func (p *PermanentBase) Pile() []Permanent { return p.pile } +func (p *PermanentBase) Controller() *Player { return p.controller } +func (p *PermanentBase) Owner() *Player { return p.owner } func (p *PermanentBase) String() string { if p.Tile() != nil { @@ -138,18 +129,30 @@ func (p *PermanentBase) XEffect(effect string) (int, error) { return x, err } -func (p *PermanentBase) IsAvailableTile(tile *Tile) bool { - return tile.IsAvailableForCard(p.card) +func (p *PermanentBase) Marks(mark PermanentMark) int { + if amount, found := p.marks[mark]; found { + return amount + } + + return 0 } -func (p *PermanentBase) SetContainingPerm(containing Permanent) { - p.containingPerm = containing +func (p *PermanentBase) addMarks(mark PermanentMark, amount int) { + if amount, found := p.marks[mark]; found { + p.marks[mark] += amount + } else { + p.marks[mark] = amount + } } -func (p *PermanentBase) AddPermanentToPile(a Permanent) { - p.pile = append(p.pile, a) +func (p *PermanentBase) IsAvailableTile(tile *Tile) bool { + return tile.IsAvailableForCard(p.card) } +func (p *PermanentBase) setContainingPerm(containing Permanent) { p.containingPerm = containing } +func (p *PermanentBase) addPermanentToPile(a Permanent) { p.pile = append(p.pile, a) } +func (p *PermanentBase) clearPile() { p.pile = nil } + func (p *PermanentBase) RemovePermanentFromPile(rm Permanent) { for i, piled := range p.pile { if piled != rm { @@ -161,21 +164,45 @@ func (p *PermanentBase) RemovePermanentFromPile(rm Permanent) { return } } -func (b *PermanentBase) onPile(p Permanent) { b.Card().Impl.onPile(p) } -func (b *PermanentBase) onUnpile(p Permanent) { b.Card().Impl.onUnpile(p) } -func (b *PermanentBase) onDrop(p Permanent) { - b.onUnpile(p) - b.Card().Impl.onDrop(p) + +func (b *PermanentBase) onPile(containing Permanent) { b.Card().Impl.onPile(containing) } +func (b *PermanentBase) onUnpile(containing Permanent) { b.Card().Impl.onUnpile(containing) } +func (b *PermanentBase) onDrop(dropped Permanent) { + b.onUnpile(dropped.ContainingPerm()) + b.Card().Impl.onDrop(dropped) } func AddPermanentToPile(containing, p Permanent) { - p.SetContainingPerm(containing) - containing.AddPermanentToPile(p) + p.setContainingPerm(containing) + containing.addPermanentToPile(p) p.onPile(containing) } func RemovePermanentFromPile(containing, rm Permanent) { containing.RemovePermanentFromPile(rm) rm.onUnpile(containing) - rm.SetContainingPerm(nil) + rm.setContainingPerm(nil) +} + +func DropPile(containing Permanent) { + for _, p := range containing.Pile() { + p.onDrop(p) + p.setContainingPerm(nil) + } + + // TODO: reset the containing's pile +} + +func enterTile(p Permanent, t *Tile) { + p.SetTile(t) + p.Card().Impl.onEntering(t) + t.entering(p) +} + +func leaveTile(p Permanent) { + if p.Tile() != nil { + p.Card().Impl.onLeaving(p.Tile()) + p.Tile().leaving(p) + p.SetTile(nil) + } } diff --git a/go/game/player.go b/go/game/player.go index b1cb6916..cb89e1ae 100644 --- a/go/game/player.go +++ b/go/game/player.go @@ -92,6 +92,12 @@ func (p *Player) BuyPhase() { p.gameState.Stack.Resolve() } +func (p *Player) discardHand() { + for _, c := range p.Hand.Cards() { + p.DiscardCard(c) + } +} + func (p *Player) DiscardCard(c *Card) { p.Hand.MoveCard(c, p.DiscardPile) } diff --git a/go/game/state.go b/go/game/state.go index 87e2133a..5a4714f7 100644 --- a/go/game/state.go +++ b/go/game/state.go @@ -24,7 +24,6 @@ type State struct { ActivePhase PhaseType Permanents []Permanent Units []*Unit - Artifacts []*Artifact Rand *rand.Rand } @@ -97,9 +96,11 @@ func (s *State) Loop() []*Player { } func (s *State) stateBasedActions() { - for _, u := range s.Units { - if u.IsDestroyed() { - s.DestroyPermanent(u) + for _, p := range s.Permanents { + p.Card().Impl.stateBasedActions(s, p) + + if p.IsDestroyed() { + s.DestroyPermanent(p) } } } @@ -288,8 +289,10 @@ func (s *State) AddPermanent(perm Permanent) { switch p := perm.(type) { case *Unit: s.Units = append(s.Units, p) - case *Artifact: - s.Artifacts = append(s.Artifacts, p) + } + + if perm.Tile() != nil { + enterTile(perm, perm.Tile()) } } @@ -301,6 +304,10 @@ func (s *State) AddNewArtifact(card *Card, pos Position, owner *Player) { s.AddPermanent(NewArtifact(card, s.Map.TileAt(pos), owner)) } +func (s *State) addNewEquipment(card *Card, pos Position, owner *Player) { + s.AddPermanent(newEquipment(card, s.Map.TileAt(pos), owner)) +} + func (s *State) AddNewPermFromPath(cardPath string, pos Position, owner *Player) { card := NewCard(cardPath) switch card.Type { @@ -320,7 +327,7 @@ func (s *State) removePermanent(p Permanent) { } } -func removePtr[P *Unit | *Artifact](collection []P, ptr P) []P { +func removePtr[P *Unit](collection []P, ptr P) []P { for i, p := range collection { if p == ptr { collection[i] = collection[len(collection)-1] @@ -331,13 +338,12 @@ func removePtr[P *Unit | *Artifact](collection []P, ptr P) []P { } func (s *State) DestroyPermanent(p Permanent) { + leaveTile(p) s.removePermanent(p) switch p := p.(type) { case *Unit: s.Units = removePtr(s.Units, p) - case *Artifact: - s.Artifacts = removePtr(s.Artifacts, p) } p.Owner().DiscardPile.AddCard(p.Card()) } @@ -381,6 +387,10 @@ func (s *State) resolvePlay(p *Player, c *Card, targets *Targets) { case CardTypes.Artifact: tile := targets.sel[0].(*Tile) s.AddNewArtifact(c, tile.Position, p) + case CardTypes.Equipment: + if tile, ok := targets.sel[0].(*Tile); ok { + s.addNewEquipment(c, tile.Position, p) + } case CardTypes.Spell: c.Impl.onPlay(s, p, targets) default: diff --git a/go/game/tile.go b/go/game/tile.go index 3f9f9f5f..510497f8 100644 --- a/go/game/tile.go +++ b/go/game/tile.go @@ -77,6 +77,7 @@ type Tile struct { Type TileType Water bool Raw string + effects []areaEffect } func (t *Tile) String() string { @@ -84,7 +85,7 @@ func (t *Tile) String() string { } func INVALID_TILE() Tile { - return Tile{INVALID_POSITION(), nil, 0, false, ""} + return Tile{Position: INVALID_POSITION()} } func NewTileFromString(t string, pos Position) (Tile, error) { @@ -97,7 +98,7 @@ func NewTileFromString(t string, pos Position) (Tile, error) { } } water := strings.Contains(tile, "water") - return Tile{pos, nil, tileType, water, t}, nil + return Tile{Position: pos, Type: tileType, Water: water, Raw: t}, nil } func (t *Tile) IsFree() bool { @@ -135,3 +136,36 @@ func (t *Tile) OnDiagonal(other *Tile) bool { p1.X == p2.X+1 && p1.Y == p2.Y+1 || p1.X == p2.X-1 && p1.Y == p2.Y+1 } + +func (t *Tile) entering(p Permanent) { + t.Permanent = p + for _, effect := range t.effects { + effect.onEntering(p) + } +} + +func (t *Tile) leaving(p Permanent) { + t.Permanent = nil + for _, effect := range t.effects { + effect.onLeaving(p) + } +} + +func (t *Tile) addEffect(effect areaEffect) { + t.effects = append(t.effects, effect) +} + +func (t *Tile) removeEffect(effect areaEffect) { + for i, e := range t.effects { + if e == effect { + t.effects[i] = t.effects[len(t.effects)-1] + t.effects = t.effects[:len(t.effects)-1] + break + } + } +} + +func (t *Tile) neutralize() { + t.Type = TileTypes.Neutral + t.Water = false +} diff --git a/go/game/unit.go b/go/game/unit.go index d38d156d..a2934296 100644 --- a/go/game/unit.go +++ b/go/game/unit.go @@ -4,53 +4,6 @@ import ( "log" ) -type UnitMark int - -const ( - paralysis UnitMark = iota + 1 - poison - panic_ - rage - ward - faith -) - -func (mark UnitMark) String() string { - switch mark { - case paralysis: - return "paralysis" - case poison: - return "poison" - case panic_: - return "panic" - case rage: - return "rage" - case ward: - return "ward" - case faith: - return "faith" - default: - log.Fatalf("Unhandled unit mark %d", mark) - return "" - } -} - -var UnitMarks = struct { - Paralysis UnitMark - Poison UnitMark - Panic UnitMark - Rage UnitMark - Ward UnitMark - Faith UnitMark -}{ - Paralysis: paralysis, - Poison: poison, - Panic: panic_, - Rage: rage, - Ward: ward, - Faith: faith, -} - const ( DEFAULT_AVAIL_MOVE_ACTIONS = 1 DEFAULT_AVAIL_ATTACK_ACTIONS = 1 @@ -64,7 +17,6 @@ type Unit struct { Upkeep int FullActions []*FullAction FreeActions []*FreeAction - States map[UnitMark]int // Effects []Effects AvailMoveActions int additionalMoveActions int @@ -97,7 +49,6 @@ func NewUnit(card *Card, tile *Tile, owner *Player) *Unit { Movement: movement, Attack: attack, Upkeep: upkeep, - States: make(map[UnitMark]int), AvailMoveActions: DEFAULT_AVAIL_MOVE_ACTIONS, AvailAttackActions: DEFAULT_AVAIL_ATTACK_ACTIONS, } @@ -107,7 +58,6 @@ func NewUnit(card *Card, tile *Tile, owner *Player) *Unit { u.FullActions = card.Impl.fullActions(u) u.FreeActions = card.Impl.freeActions(u) - tile.Permanent = u return u } @@ -121,25 +71,23 @@ func (u *Unit) resetBaseActions() { } func (u *Unit) onUpkeep() { + // TODO: handle state marks u.resetBaseActions() } func (u *Unit) move(s *State, tile *Tile) { if !u.IsAvailableTile(tile) { - log.Fatalf("moveing to not available tile %v", tile) + log.Fatalf("moving to not available tile %v", tile) } - if u.tile != nil { - u.tile.Permanent = nil - } else if u.containingPerm != nil { + if u.containingPerm != nil { RemovePermanentFromPile(u.containingPerm, u) } else { - log.Fatalf("unit has no current location") + leaveTile(u) } if tile.Permanent == nil { - tile.Permanent = u - u.tile = tile + enterTile(u, tile) } else { // crew permanent at tile AddPermanentToPile(tile.Permanent, u) u.tile = nil diff --git a/go/game/unit_test.go b/go/game/unit_test.go index 0f16176c..53b585d8 100644 --- a/go/game/unit_test.go +++ b/go/game/unit_test.go @@ -20,9 +20,15 @@ symbols: s := NewState() r := strings.NewReader(mapDef) s.Map, _ = readMap(r) + f := NewUnit(NewCard("nautics/fisher"), s.Map.TileAt(Position{2, 2}), nil) + s.AddPermanent(f) + a := NewUnit(NewCard("base/archer"), s.Map.TileAt(Position{1, 1}), nil) + s.AddPermanent(a) + sword := newEquipmentFromPath("base/sword", s.Map.TileAt(Position{0, 1}), nil) + s.AddPermanent(sword) a.move(s, f.Tile()) if len(f.Pile()) != 1 { |
