From ea54efacae089aa3644cfe00c8e180d5cc2c8d9e Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Sat, 4 Feb 2023 14:37:15 +0100 Subject: support crewing * Introduce new callback for leaving a pile without beeing dropped * Fix equipment contrsuctor * Implement attack inheritance --- go/game/artifact.go | 5 ++++- go/game/attack.go | 2 +- go/game/card.go | 2 ++ go/game/cardParsing.go | 7 ++++++ go/game/permanent.go | 52 +++++++++++++++++++++++++++++++++---------- go/game/tile.go | 20 ++++++++++++++--- go/game/unit.go | 56 ++++++++++++++++++++++++++++++++++++++-------- go/game/unit_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 178 insertions(+), 26 deletions(-) create mode 100644 go/game/unit_test.go diff --git a/go/game/artifact.go b/go/game/artifact.go index acbaeb74..916917d8 100644 --- a/go/game/artifact.go +++ b/go/game/artifact.go @@ -18,7 +18,10 @@ 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} - tile.Permanent = a + + if tile != nil { + tile.Permanent = a + } effects := card.getEffects() for _, e := range effects { diff --git a/go/game/attack.go b/go/game/attack.go index 35af7ff2..0d3bef7c 100644 --- a/go/game/attack.go +++ b/go/game/attack.go @@ -12,7 +12,7 @@ type Attack struct { } func INVALID_ATTACK(a Attack) bool { - return a.attacks == nil + return a.attacks == nil || len(a.attacks) == 0 } func (a Attack) MaxRange() int { diff --git a/go/game/card.go b/go/game/card.go index 23562f0c..81464cce 100644 --- a/go/game/card.go +++ b/go/game/card.go @@ -77,6 +77,7 @@ type cardImplementation interface { freeActions(Permanent) []*FreeAction onPile(Permanent) + onUnpile(Permanent) onDrop(Permanent) } @@ -90,6 +91,7 @@ func (*cardImplementationBase) spawnTiles(*State, *Player) []*Tile { return nil func (*cardImplementationBase) fullActions(*Unit) []*FullAction { return nil } func (*cardImplementationBase) freeActions(Permanent) []*FreeAction { return nil } func (*cardImplementationBase) onPile(Permanent) {} +func (*cardImplementationBase) onUnpile(Permanent) {} func (*cardImplementationBase) onDrop(Permanent) {} // Map of all card implementations diff --git a/go/game/cardParsing.go b/go/game/cardParsing.go index ad0b6ea8..dd64c7b8 100644 --- a/go/game/cardParsing.go +++ b/go/game/cardParsing.go @@ -13,6 +13,7 @@ type parsedCardImplementation struct { genFullActions func(*Unit) []*FullAction genFreeActions func(Permanent) []*FreeAction _onPile func(Permanent) + _onUnpile func(Permanent) _onDrop func(Permanent) } @@ -43,6 +44,12 @@ func (impl *parsedCardImplementation) onPile(p Permanent) { } } +func (impl *parsedCardImplementation) onUnpile(p Permanent) { + if impl._onUnpile != nil { + impl._onUnpile(p) + } +} + func (impl *parsedCardImplementation) onDrop(p Permanent) { if impl._onDrop != nil { impl._onDrop(p) diff --git a/go/game/permanent.go b/go/game/permanent.go index 36c5e1ba..91aa0cda 100644 --- a/go/game/permanent.go +++ b/go/game/permanent.go @@ -28,19 +28,23 @@ type Permanent interface { AvailActions() []Action IsAvailableTile(*Tile) bool + SetContainingPerm(p Permanent) AddPermanentToPile(p Permanent) - OnPile(containing Permanent) - OnDrop(containing Permanent) + RemovePermanentFromPile(p Permanent) + onPile(containing Permanent) + onUnpile(containing Permanent) + onDrop(containing Permanent) } type PermanentBase struct { - card *Card - tile *Tile - damage int - pile []Permanent - controller *Player - owner *Player - tmpEffects []string + card *Card + tile *Tile + damage int + pile []Permanent + containingPerm Permanent + controller *Player + owner *Player + tmpEffects []string } func NewPermanentBase(card *Card, tile *Tile, owner *Player) PermanentBase { @@ -134,13 +138,37 @@ 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 (b *PermanentBase) OnPile(p Permanent) { p.Card().Impl.onPile(p) } -func (b *PermanentBase) OnDrop(p Permanent) { p.Card().Impl.onDrop(p) } + +func (p *PermanentBase) RemovePermanentFromPile(rm Permanent) { + for i, piled := range p.pile { + if piled != rm { + continue + } + + p.pile[i] = p.pile[len(p.pile)-1] + p.pile = p.pile[:len(p.pile)-1] + return + } +} +func (b *PermanentBase) onPile(p Permanent) { p.Card().Impl.onPile(p) } +func (b *PermanentBase) onUnpile(p Permanent) { p.Card().Impl.onUnpile(p) } +func (b *PermanentBase) onDrop(p Permanent) { p.Card().Impl.onDrop(p) } func AddPermanentToPile(containing, p Permanent) { + p.SetContainingPerm(containing) containing.AddPermanentToPile(p) - p.OnPile(p) + p.onPile(p) +} + +func RemovePermanentFromPile(containing, rm Permanent) { + containing.RemovePermanentFromPile(rm) + rm.onUnpile(containing) + rm.SetContainingPerm(nil) } diff --git a/go/game/tile.go b/go/game/tile.go index ba600c66..3f9f9f5f 100644 --- a/go/game/tile.go +++ b/go/game/tile.go @@ -105,13 +105,27 @@ func (t *Tile) IsFree() bool { } func (t *Tile) IsAvailableForCard(card *Card) bool { - if t.Water && !card.hasPlacementConstrain("swimming") { + if t.IsFree() { + if t.Water && !card.hasPlacementConstrain("swimming") { + return false + } + if !t.Water && card.hasPlacementConstrain("swimming") { + return false + } + + return true + } + + x, err := t.Permanent.Card().getXEffect("crew") + if err != nil { return false } - if !t.Water && card.hasPlacementConstrain("swimming") { + + if len(t.Permanent.Pile()) == x { return false } - return t.IsFree() + + return true } func (t *Tile) OnDiagonal(other *Tile) bool { diff --git a/go/game/unit.go b/go/game/unit.go index fba85f30..a0d6bfa4 100644 --- a/go/game/unit.go +++ b/go/game/unit.go @@ -125,9 +125,25 @@ func (u *Unit) onUpkeep() { } func (u *Unit) move(s *State, tile *Tile) { - u.tile.Permanent = nil - tile.Permanent = u - u.tile = tile + if !u.IsAvailableTile(tile) { + log.Fatalf("moveing to not available tile %v", tile) + } + + if u.tile != nil { + u.tile.Permanent = nil + } else if u.containingPerm != nil { + RemovePermanentFromPile(u.containingPerm, u) + } else { + log.Fatalf("unit has no current location") + } + + if tile.Permanent == nil { + tile.Permanent = u + u.tile = tile + } else { // crew permanent at tile + AddPermanentToPile(tile.Permanent, u) + u.tile = nil + } } func (u *Unit) MoveRangeTiles(m *Map) []*Tile { @@ -226,14 +242,36 @@ func (u *Unit) AvailActions() (actions []Action) { return actions } -func (u *Unit) OnPile(containing Permanent) { - if _, ok := containing.(*Unit); ok { - // TODO: add attack +func (u *Unit) onPile(containing Permanent) { + u.PermanentBase.onPile(u) + if baseUnit, ok := containing.(*Unit); ok { + baseUnitAttackRange := baseUnit.Attack.MaxRange() + for r, a := range u.Attack.attacks { + if r > baseUnitAttackRange-1 { + baseUnit.Attack.attacks = append(baseUnit.Attack.attacks, a) + } else { + baseUnit.Attack.attacks[r] += a + } + } } } -func (u *Unit) OnDrop(containing Permanent) { - if _, ok := containing.(*Unit); ok { - // TODO: remove attack +func (u *Unit) onUnpile(containing Permanent) { + u.PermanentBase.onUnpile(u) + if baseUnit, ok := containing.(*Unit); ok { + for r, a := range baseUnit.Attack.attacks { + if a == u.Attack.attacks[r] { + // Remove all further attacks since they must all be from the + // dropped unit + baseUnit.Attack.attacks = baseUnit.Attack.attacks[:r] + break + } else { + baseUnit.Attack.attacks[r] -= u.Attack.attacks[r] + } + } } } + +func (u *Unit) onDrop(containing Permanent) { + u.onUnpile(containing) +} diff --git a/go/game/unit_test.go b/go/game/unit_test.go new file mode 100644 index 00000000..341fb350 --- /dev/null +++ b/go/game/unit_test.go @@ -0,0 +1,60 @@ +package game + +import ( + "strings" + "testing" +) + +func TestCrew(t *testing.T) { + mapDef := `map: |1- + HST + HSF + TSW +symbols: + T: tower + H: house + F: farm + S: street + W: deep water +` + s := NewState() + r := strings.NewReader(mapDef) + s.Map, _ = readMap(r) + f := NewUnit(NewCard("nautics/fisher"), s.Map.TileAt(Position{2, 2}), nil) + a := NewUnit(NewCard("base/archer"), s.Map.TileAt(Position{1, 1}), nil) + sword := newEquipment(NewCard("base/sword"), s.Map.TileAt(Position{0, 1}), nil) + + a.move(s, f.Tile()) + if len(f.Pile()) != 1 { + t.Fatalf("fisher's pile size is not 1") + } + + if f.Pile()[0] != a { + t.Fatalf("fisher's pile does not contain the archer") + } + + if a.Tile() != nil { + t.Fatalf("piled archer has still a tile %v", a.Tile()) + } + + for r, d := range f.Attack.attacks { + if d != a.Attack.attacks[r] { + t.Fatalf("fisher's attack %d is not equal to the archer's attack %d at range %d", + d, a.Attack.attacks[r], r) + } + } + + a.move(s, s.Map.TileAt(Position{1, 1})) + if len(f.Pile()) != 0 { + t.Fatalf("fisher's pile is not empty") + } + + if !INVALID_ATTACK(f.Attack) { + t.Fatalf("fisher has still a valid atack") + } + + AddPermanentToPile(a, sword) + if a.Attack.attacks[0] != 2 { + t.Fatalf("archer's mele attack is not 2") + } +} -- cgit v1.2.3