From c98dd8aacd47eb3732392f41c412b3f54036ef3d Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Tue, 7 Feb 2023 14:58:22 +0100 Subject: intermediate commit * implement most of the kraken cards * unify permanent marks * add state based actions hook for card implementations --- go/TODO | 1 - go/game/card.go | 23 ++-- go/game/cardImplementations.go | 232 +++++++++++++++++++++++++++++++++++------ go/game/cardParsing.go | 20 ++-- go/game/map.go | 4 + go/game/marks.go | 68 ++++++++++++ go/game/permanent.go | 93 +++++++++-------- go/game/player.go | 6 ++ go/game/state.go | 8 +- go/game/tile.go | 19 ++++ go/game/unit.go | 50 +-------- 11 files changed, 380 insertions(+), 144 deletions(-) create mode 100644 go/game/marks.go diff --git a/go/TODO b/go/TODO index 590bb641..23274884 100644 --- a/go/TODO +++ b/go/TODO @@ -3,7 +3,6 @@ * implement Pile UI hint * implement spell target parsing / selection * implement triggers -* implement area of effects * implement draft * implement game log * finish AIs diff --git a/go/game/card.go b/go/game/card.go index 8b4cd889..c24e7585 100644 --- a/go/game/card.go +++ b/go/game/card.go @@ -76,12 +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. @@ -93,12 +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) onEntering(*Tile) {} -func (*cardImplementationBase) onLeaving(*Tile) {} -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 df78ade6..de1c7be4 100644 --- a/go/game/cardParsing.go +++ b/go/game/cardParsing.go @@ -12,12 +12,14 @@ type dynamicCardImplementation struct { spawnTilesFunc func(*State, *Player) []*Tile genFullActions func(*Unit) []*FullAction genFreeActions func(Permanent) []*FreeAction - _onEntering func(*Tile) - _onLeaving func(*Tile) - _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 *dynamicCardImplementation) spawnTiles(s *State, p *Player) []*Tile { @@ -41,6 +43,12 @@ func (impl *dynamicCardImplementation) freeActions(p Permanent) []*FreeAction { return nil } +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) 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 596e3c69..9188a206 100644 --- a/go/game/permanent.go +++ b/go/game/permanent.go @@ -14,12 +14,15 @@ type Permanent interface { 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) @@ -29,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) @@ -46,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 { @@ -61,33 +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) SetTile(t *Tile) { - p.tile = t -} - -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 { @@ -143,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 { @@ -167,23 +165,32 @@ func (p *PermanentBase) RemovePermanentFromPile(rm Permanent) { } } -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) { 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 01cee403..5a4714f7 100644 --- a/go/game/state.go +++ b/go/game/state.go @@ -96,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) } } } diff --git a/go/game/tile.go b/go/game/tile.go index a7bf2b26..510497f8 100644 --- a/go/game/tile.go +++ b/go/game/tile.go @@ -150,3 +150,22 @@ func (t *Tile) leaving(p Permanent) { 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 82aa8528..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, } @@ -120,6 +71,7 @@ func (u *Unit) resetBaseActions() { } func (u *Unit) onUpkeep() { + // TODO: handle state marks u.resetBaseActions() } -- cgit v1.2.3