From 43306199e6405f08ff1446b1bd8c6fb8850f465f Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Tue, 22 Jul 2025 18:35:16 -0400 Subject: implement tower tile effect --- go/game/action_test.go | 32 ++++++++++++++++++++++++++++++++ go/game/attack.go | 18 ++++++++++++++++++ go/game/tile.go | 30 +++++++++++++++++++++++++++++- go/game/unit_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) diff --git a/go/game/action_test.go b/go/game/action_test.go index 5fa0b64d..b7a92bae 100644 --- a/go/game/action_test.go +++ b/go/game/action_test.go @@ -111,6 +111,7 @@ func TestAttackAction(t *testing.T) { mapDef := `map: |1- H T SSS + symbols: T: tower H: house @@ -126,6 +127,8 @@ symbols: t1 := s.addNewUnit(NewCard("base/fighter"), Position{0, 0}, o) t2 := s.addNewUnit(NewCard("base/fighter"), Position{0, 1}, o) + t3 := s.addNewUnit(NewCard("base/fighter"), Position{0, 2}, o) + a := s.addNewUnit(NewCard("base/archer"), Position{2, 0}, p) ca := s.addNewUnit(NewCard("base/cavalry_archer"), Position{2, 1}, p) @@ -147,4 +150,33 @@ symbols: if ca.Damage() > 0 { t.Fatal("cavalry archer took damage in range combat from melee unit") } + + aa3 := NewAttackAction(a) + err = aa3.Target().AddSelection(t2) + if err != nil { + t.Fatal("unexpected target error:", err) + } + s.ResolveAction(aa3) + if t2.Damage() != 2 { + t.Fatal("fighter did not took 1 damage") + } + if !t2.IsDestroyed() { + t.Fatal("fighter is still alive") + } + if ca.Damage() > 0 { + t.Fatal("archer took damage in range combat from melee unit") + } + + aa4 := NewAttackAction(a) + err = aa4.Target().AddSelection(t3) + if err != nil { + t.Fatal("unexpected target error:", err) + } + s.ResolveAction(aa4) + if t3.Damage() != 1 { + t.Fatal("fighter did not took 1 damage") + } + if ca.Damage() > 0 { + t.Fatal("archer took damage in range combat from melee unit") + } } diff --git a/go/game/attack.go b/go/game/attack.go index 9be465f2..6b33e8d9 100644 --- a/go/game/attack.go +++ b/go/game/attack.go @@ -32,6 +32,24 @@ func (a Attack) DamageInRange(r int) int { return a.attacks[r-1] } +// Ranged reports if the attack has more range than 1. +func (a Attack) Ranged() bool { + return a.MaxRange() > 1 +} + +// Extend increases the MaxRange by amount. +func (a *Attack) Extend(amount int) { + d := a.DamageInRange(a.MaxRange()) + for range amount { + a.attacks = append(a.attacks, d) + } +} + +// Shorten decreases the MaxRange by amount. +func (a *Attack) Shorten(amount int) { + a.attacks = a.attacks[:len(a.attacks)-amount] +} + func (a Attack) String() string { if INVALID_ATTACK(a) { return "invalid attack" diff --git a/go/game/tile.go b/go/game/tile.go index 06770ff3..d5a256d9 100644 --- a/go/game/tile.go +++ b/go/game/tile.go @@ -97,6 +97,31 @@ var farmEffect areaEffect = newGrantFullActionEffect("misc/farmer", } }, "gain 3 resource", "farmAction") +var towerEffect areaEffect = newDynamicAreaEffect( + func(p Permanent) { + var u *Unit + var ok bool + if u, ok = p.(*Unit); !ok { + return + } + if u.Attack.MaxRange() < 2 { + return + } + u.Attack.Extend(1) + }, + func(p Permanent) { + var u *Unit + var ok bool + if u, ok = p.(*Unit); !ok { + return + } + if u.Attack.MaxRange() < 3 { + return + } + u.Attack.Shorten(1) + }, +) + type Tile struct { Position Position Permanent Permanent @@ -131,8 +156,11 @@ func NewTileFromString(raw string, pos Position) (Tile, error) { t := Tile{Position: pos, Type: tileType, Water: water, Raw: raw} - if tileType == TileTypes.Farm { + switch tileType { + case TileTypes.Farm: t.effects = append(t.effects, farmEffect) + case TileTypes.Tower: + t.effects = append(t.effects, towerEffect) } return t, nil diff --git a/go/game/unit_test.go b/go/game/unit_test.go index f2a15ddc..e707c560 100644 --- a/go/game/unit_test.go +++ b/go/game/unit_test.go @@ -79,3 +79,53 @@ symbols: t.Fatalf("archer's mele attack is not 2") } } + +func TestAttackablePerms(t *testing.T) { + mapDef := `map: |1- + HST + HSF + TSW +symbols: + T: tower + H: house + F: farm + S: street + W: deep water +` + s := NewLocalState() + r := strings.NewReader(mapDef) + m, _ := readMap(r) + s.SetMap(m) + + p := s.AddNewPlayer("player", NewDeck()) + o := s.AddNewPlayer("opponent", NewDeck()) + + a := s.addNewUnit(NewCard("base/archer"), Position{2, 0}, p) + units := []*Unit{} + for x := range 3 { + for y := range 3 { + var u *Unit + pos := Position{x, y} + if x == 2 && y == 0 { + continue + } else if x == 2 && y == 2 { + u = s.addNewUnit(NewCard("nautics/fisher"), pos, o) + } else { + u = s.addNewUnit(NewCard("misc/farmer"), pos, o) + } + units = append(units, u) + } + } + + aTiles := a.AttackableTiles(m) + is := len(aTiles) + if is != 6 { + t.Fatal("6 attackable tiles expected not:", is) + } + + aPerms := a.AttackableEnemyPermanents(m) + is = len(aPerms) + if is != 6 { + t.Fatal("6 attackable permanents expected not:", is) + } +} -- cgit v1.2.3