aboutsummaryrefslogtreecommitdiff
path: root/go/game
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-01-20 03:18:06 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-01-27 16:43:44 +0100
commitc4cb9637f5c85d03aa162968f0dfec3c193fc9dc (patch)
tree3ad32e5dfd7c4ae72f66b86e77edef7b700a8b7a /go/game
parent9df5cc7b55d8ac9ecd775a14a14f80a6c36c4d74 (diff)
downloadmuhqs-game-c4cb9637f5c85d03aa162968f0dfec3c193fc9dc.tar.gz
muhqs-game-c4cb9637f5c85d03aa162968f0dfec3c193fc9dc.zip
intermediate commit
Implement actions and multiple ui widgets
Diffstat (limited to 'go/game')
-rw-r--r--go/game/action.go133
-rw-r--r--go/game/ai.go123
-rw-r--r--go/game/artifact.go48
-rw-r--r--go/game/card.go21
-rw-r--r--go/game/equipment.go5
-rw-r--r--go/game/hand.go5
-rw-r--r--go/game/permanent.go60
-rw-r--r--go/game/player.go9
-rw-r--r--go/game/pos.go12
-rw-r--r--go/game/range.go8
-rw-r--r--go/game/stack.go29
-rw-r--r--go/game/state.go77
-rw-r--r--go/game/tile.go15
-rw-r--r--go/game/unit.go100
14 files changed, 606 insertions, 39 deletions
diff --git a/go/game/action.go b/go/game/action.go
index 723d4866..58eea8da 100644
--- a/go/game/action.go
+++ b/go/game/action.go
@@ -1,6 +1,133 @@
package game
-type Action struct {
- Owner *Player
- Card *Card
+import (
+ "fmt"
+ "log"
+)
+
+type ActionPrototype interface {
+ Specialize(...interface{}) Action
+ String() string
+}
+
+type Action interface {
+ Declare(s *State)
+ Resolve(s *State)
+ String() string
+}
+
+type FastAction interface {
+ Action
+ FastDeclare(s *State)
+}
+
+type ActionBase struct {
+ Source interface{}
+ Card *Card
+ resolveFunc func(s *State)
+ desc string
+}
+
+func (a *ActionBase) Declare(s *State) {
+ if !s.Stack.IsEmpty() {
+ log.Fatalf("Slow action can not be declared while other actions are on the stack")
+ }
+ s.Stack.Push(a)
+}
+
+func (a *ActionBase) Resolve(s *State) {
+ a.resolveFunc(s)
+}
+
+func (a *ActionBase) String() string {
+ if p, ok := a.Source.(Permanent); ok {
+ return fmt.Sprintf("%s: %s %s", p.GetController().Name, p.GetTile().Position.String(), a.desc)
+ }
+ return fmt.Sprintf("%s %s", a.Source, a.desc)
+}
+
+type MoveActionPrototype struct {
+ Unit *Unit
+}
+
+func NewMoveActionPrototype(u *Unit) *MoveActionPrototype {
+ return &MoveActionPrototype{u}
+}
+
+func (proto *MoveActionPrototype) String() string {
+ return fmt.Sprintf("move %d", proto.Unit.Movement)
+}
+
+func (proto *MoveActionPrototype) Specialize(args ...interface{}) Action {
+ target := args[0].(*Tile)
+ u := proto.Unit
+
+ resolveFunc := func(s *State) {
+ u.move(s, target)
+ }
+ desc := fmt.Sprintf("-> %s", target.Position.String())
+ return &ActionBase{u, u.GetCard(), resolveFunc, desc}
+}
+
+type AttackActionPrototype struct {
+ Unit *Unit
+}
+
+func NewAttackActionPrototype(u *Unit) *AttackActionPrototype {
+ return &AttackActionPrototype{u}
+}
+
+func (proto *AttackActionPrototype) String() string {
+ return fmt.Sprintf("attack %s", proto.Unit.Attack.String())
+}
+
+func (proto *AttackActionPrototype) Specialize(args ...interface{}) Action {
+ target := args[0].(Permanent)
+ u := proto.Unit
+ resolveFunc := func(s *State) {
+ s.Fight(u, target)
+ }
+ desc := fmt.Sprintf("x %s", target.GetTile().Position.String())
+ return &ActionBase{u, u.GetCard(), resolveFunc, desc}
+}
+
+type FullActionPrototype struct {
+ Permanent Permanent
+}
+
+func (proto *FullActionPrototype) String() string {
+ return "full_action"
+}
+
+func (proto *FullActionPrototype) Specialize(args ...interface{}) Action {
+ p := proto.Permanent
+ resolveFunc := args[0].(func(s *State))
+ desc := "full_action"
+ return &ActionBase{p, p.GetCard(), resolveFunc, desc}
+}
+
+type FreeActionPrototype struct {
+ Permanent Permanent
+}
+
+func (proto *FreeActionPrototype) String() string {
+ return "free_action"
+}
+
+func (proto *FreeActionPrototype) Specialize(args ...interface{}) Action {
+ p := proto.Permanent
+ resolveFunc := args[0].(func(s *State))
+ desc := "free_action"
+ return &FreeAction{ActionBase{p, p.GetCard(), resolveFunc, desc}}
+}
+
+type FreeAction struct {
+ ActionBase
+}
+
+func (a *FreeAction) FastDeclare(s *State) {
+ s.Stack.Push(a)
+}
+
+func (a *FreeAction) Resolve(s *State) {
}
diff --git a/go/game/ai.go b/go/game/ai.go
new file mode 100644
index 00000000..b1921639
--- /dev/null
+++ b/go/game/ai.go
@@ -0,0 +1,123 @@
+package game
+
+func moveToRandomTile(s *State, u *Unit) {
+ moveTargets := u.MoveRangeTiles(s.Map)
+ if len(moveTargets) > 0 {
+ moveTarget := moveTargets[s.Rand.Intn(len(moveTargets))-1]
+ s.MoveUnit(u, moveTarget)
+ }
+}
+
+func attackAttackableEnemyPerm(s *State, u *Unit) {
+ attackablePerms := u.AttackablePermanents(s.Map)
+ if len(attackablePerms) > 0 {
+ attackablePerm := attackablePerms[s.Rand.Intn(len(attackablePerms))-1]
+ s.Fight(u, attackablePerm)
+ }
+}
+
+// 1. If enemy Unit in attack Range then
+// attack enemy Unit in Range
+// 2. While move action available do
+// 3a. If enemy unit exists
+// move towards nearest enemy unit until it is in attack range
+// if attack action avail then proceed at 1.
+// 3b. Otherwise
+// move to random tile in movement range
+func AggressiveAi(s *State, u *Unit) {
+ // 1.
+ attackAttackableEnemyPerm(s, u)
+
+ // 2.
+ if u.AvailMoveActions > 0 {
+ enemyUnits := s.EnemyUnits(u.GetController())
+ if len(enemyUnits) > 0 { // 3a.
+ // TODO: implement path finding
+ if u.AvailAttackActions > 0 {
+ AggressiveAi(s, u)
+ }
+ } else { // 3b.
+ moveToRandomTile(s, u)
+ }
+ }
+}
+
+// 2.2 Shy
+// 1. If not in the attack Range of an enemy Unit then
+// activate full action if available
+// 2. If move action available then
+// move to most distant point from all enemy units
+// 3. If enemy Unit in attack Range then
+// attack enemy Unit in Range
+func ShyAi(s *State, u *Unit) {
+ // 1.
+ inEnemyAttackRange := false
+out:
+ for _, enemyUnit := range s.EnemyUnits(u.GetController()) {
+ for _, p := range enemyUnit.AttackablePermanents(s.Map) {
+ if p == u {
+ inEnemyAttackRange = true
+ break out
+ }
+ }
+ }
+ if !inEnemyAttackRange && u.HasFullAction() {
+ u.FullAction()
+ }
+
+ // 2.
+ if u.AvailMoveActions > 0 {
+ // TODO: implement path finding
+ }
+
+ // 3.
+ if u.AvailAttackActions > 0 {
+ attackAttackableEnemyPerm(s, u)
+ }
+}
+
+// 2.3 Wandering X
+// 1. If enemy unit in Range X then
+// execute aggressive AI
+// 2. If move action available then
+// move to random tile in movement Range
+// proceed at 1.
+func WanderingAi(s *State, u *Unit, x int) {
+ // 1.
+ for _, enemyUnit := range s.EnemyUnits(u.GetController()) {
+ if IsPositionInRange(u.GetTile().Position, enemyUnit.GetTile().Position, x) {
+ AggressiveAi(s, u)
+ break
+ }
+ }
+ // 2.
+ if u.AvailMoveActions > 0 {
+ moveToRandomTile(s, u)
+ WanderingAi(s, u, x)
+ }
+}
+
+// 2.4 Target-oriented TARGET
+// 1. Use full action if possible
+// 2a. If a TARGET exists and is reachable
+//
+// Move onto or towards nearest TARGET
+// 3a. If enemy Unit in attack Range
+// attack enemy Unit in Range
+//
+// 2b. Otherwise
+//
+// execute wandering 3 AI
+func TargetOrientedAi(s *State, u *Unit, t *Position) {
+ // 1.
+ if u.HasFullAction() {
+ u.FullAction()
+ }
+
+ if t != nil {
+ // TODO: path finding again
+ attackAttackableEnemyPerm(s, u)
+ } else {
+ WanderingAi(s, u, 3)
+ }
+}
diff --git a/go/game/artifact.go b/go/game/artifact.go
new file mode 100644
index 00000000..452d5a6a
--- /dev/null
+++ b/go/game/artifact.go
@@ -0,0 +1,48 @@
+package game
+
+import (
+ "log"
+ "strconv"
+ "strings"
+)
+
+type Artifact struct {
+ PermanentBase
+ Solid int
+}
+
+func NewArtifact(cardPath string, tile *Tile, owner *Player) *Artifact {
+ card := NewCard(cardPath)
+
+ a := &Artifact{NewPermanentBase(card, tile, owner), 0}
+ tile.Permanent = a
+
+ effects := card.getEffects()
+ for _, e := range effects {
+ if strings.HasPrefix(e, "Solid") {
+ tokens := strings.Split(e, " ")
+ var err error
+ a.Solid, err = strconv.Atoi(tokens[1])
+ if err != nil {
+ log.Fatalf("Invalid Solid definition %s\n", e)
+ }
+ }
+ }
+
+ return a
+}
+
+func (a *Artifact) Fight(p Permanent) {
+}
+
+func (a *Artifact) IsDestroyed() bool {
+ return a.Damage >= a.Solid
+}
+
+func (a *Artifact) Attackable() bool {
+ return a.Solid > 0
+}
+
+func (a *Artifact) GetAvailActionPrototypes() []ActionPrototype {
+ return []ActionPrototype{}
+}
diff --git a/go/game/card.go b/go/game/card.go
index f77a274a..bfa476a9 100644
--- a/go/game/card.go
+++ b/go/game/card.go
@@ -132,6 +132,27 @@ func (card *Card) parseDefinition(definition []byte) {
}
}
+func (card *Card) getEffects() []string {
+ effects, found := card.Values["effect"]
+ if !found {
+ return []string{}
+ }
+
+ if effect, ok := effects.(string); ok {
+ return []string{effect}
+ }
+
+ if effectsMap, ok := effects.(map[string]interface{}); ok {
+ enEffects := effectsMap["en"]
+ if effect, ok := enEffects.(string); ok {
+ return []string{effect}
+ }
+ return enEffects.([]string)
+ }
+ return effects.([]string)
+
+}
+
func NewCard(cardPath string) *Card {
cardName := path.Base(cardPath)
set := path.Dir(cardPath)
diff --git a/go/game/equipment.go b/go/game/equipment.go
new file mode 100644
index 00000000..1b78c8ad
--- /dev/null
+++ b/go/game/equipment.go
@@ -0,0 +1,5 @@
+package game
+
+type Equipment struct {
+ Artifact
+}
diff --git a/go/game/hand.go b/go/game/hand.go
index fde4a686..169c8cb3 100644
--- a/go/game/hand.go
+++ b/go/game/hand.go
@@ -22,11 +22,14 @@ func (h *Hand) AddCard(card *Card) {
}
func (h *Hand) RemoveCard(card *Card) {
+ ncards := len(h.Cards)
for i, c := range h.Cards {
if c != card {
continue
}
- h.Cards = append(h.Cards[:i], h.Cards[i+1:]...)
+ h.Cards[i] = h.Cards[ncards-1]
+ h.Cards = h.Cards[:ncards-1]
+ return
}
}
diff --git a/go/game/permanent.go b/go/game/permanent.go
index 37111c4d..7a9988e1 100644
--- a/go/game/permanent.go
+++ b/go/game/permanent.go
@@ -1,10 +1,66 @@
package game
-type Permanent struct {
+import (
+ "fmt"
+ "log"
+)
+
+type Permanent interface {
+ GetCard() *Card
+ GetTile() *Tile
+ GetDamage() int
+ GetPile() []Permanent
+ GetController() *Player
+ GetOwner() *Player
+
+ String() string
+ Fight(p Permanent)
+ AddDamage(damage int)
+ IsDestroyed() bool
+ Attackable() bool
+ GetAvailActionPrototypes() []ActionPrototype
+}
+
+type PermanentBase struct {
Card *Card
Tile *Tile
Damage int
- Pile []*Permanent
+ Pile []Permanent
Controller *Player
Owner *Player
}
+
+func NewPermanentBase(card *Card, tile *Tile, owner *Player) PermanentBase {
+ if tile.Permanent != nil {
+ log.Fatal("Tile at %v already occupied by %v", tile.Position, tile.Permanent)
+ }
+ return PermanentBase{card, tile, 0, []Permanent{}, owner, owner}
+}
+
+func (p *PermanentBase) GetCard() *Card {
+ return p.Card
+}
+func (p *PermanentBase) GetTile() *Tile {
+ return p.Tile
+}
+func (p *PermanentBase) GetDamage() int {
+ return p.Damage
+}
+func (p *PermanentBase) GetPile() []Permanent {
+ return p.Pile
+}
+func (p *PermanentBase) GetController() *Player {
+ return p.Controller
+}
+func (p *PermanentBase) GetOwner() *Player {
+ return p.Owner
+}
+
+func (p *PermanentBase) String() string {
+ return fmt.Sprintf("%s's %s@%s",
+ p.GetController().Name, p.GetCard().Name, p.GetTile().Position.String())
+}
+
+func (p *PermanentBase) AddDamage(damage int) {
+ p.Damage += damage
+}
diff --git a/go/game/player.go b/go/game/player.go
index 20ac13a5..78ba69a7 100644
--- a/go/game/player.go
+++ b/go/game/player.go
@@ -1,5 +1,9 @@
package game
+import (
+ "image/color"
+)
+
const (
MAX_DRAW int = 3
)
@@ -12,10 +16,11 @@ type Player struct {
Deck *Deck
Ressource int
gameState *State
+ Color color.Color
}
-func NewPlayer(id int, name string, deck *Deck, gameState *State) *Player {
- p := Player{id, name, NewHand(), NewDiscardPile(), deck, 0, gameState}
+func NewPlayer(id int, name string, deck *Deck, gameState *State, color color.Color) *Player {
+ p := Player{id, name, NewHand(), NewDiscardPile(), deck, 0, gameState, color}
return &p
}
diff --git a/go/game/pos.go b/go/game/pos.go
index 2b3459cd..2a646a83 100644
--- a/go/game/pos.go
+++ b/go/game/pos.go
@@ -1,10 +1,22 @@
package game
+import (
+ "fmt"
+)
+
type Position struct {
X int
Y int
}
+func (p *Position) String() string {
+ if *p == INVALID_POSITION() {
+ return "INVALID_POSITION"
+ }
+
+ return fmt.Sprintf("(%d, %d)", p.X, p.Y)
+}
+
func INVALID_POSITION() Position {
return Position{-1, -1}
}
diff --git a/go/game/range.go b/go/game/range.go
index 86ec2f50..e12e1e67 100644
--- a/go/game/range.go
+++ b/go/game/range.go
@@ -37,3 +37,11 @@ func PositionsInRange(origin Position, r int, includeOrigin bool) []Position {
}
return positions
}
+
+func TilesInRange(m *Map, p Permanent, r int) []*Tile {
+ tiles := []*Tile{}
+ for _, pos := range PositionsInRange(p.GetTile().Position, r, false) {
+ tiles = append(tiles, m.TileAt(pos))
+ }
+ return tiles
+}
diff --git a/go/game/stack.go b/go/game/stack.go
index 5d62e601..22c20fa3 100644
--- a/go/game/stack.go
+++ b/go/game/stack.go
@@ -1,5 +1,32 @@
package game
+import (
+ "log"
+)
+
type Stack struct {
- Actions []Action
+ gameState *State
+ Actions []Action
+}
+
+func NewStack(s *State) *Stack {
+ return &Stack{s, []Action{}}
+}
+
+func (s *Stack) IsEmpty() bool {
+ return len(s.Actions) == 0
+}
+
+func (s *Stack) Push(a Action) {
+ s.Actions = append(s.Actions, a)
+}
+
+func (s *Stack) Pop() {
+ l := len(s.Actions)
+ if l == 0 {
+ log.Fatalf("Can not pop from empty stack")
+ }
+ a := s.Actions[l-1]
+ s.Actions = s.Actions[:l-1]
+ a.Resolve(s.gameState)
}
diff --git a/go/game/state.go b/go/game/state.go
index caae8106..3d600093 100644
--- a/go/game/state.go
+++ b/go/game/state.go
@@ -1,5 +1,10 @@
package game
+import (
+ "image/color"
+ "math/rand"
+)
+
type State struct {
Decks []*Deck
Stores []*Store
@@ -8,25 +13,87 @@ type State struct {
Hands []*Hand
DiscardPiles []*DiscardPile
Players []*Player
- Permanents []*Permanent
+ Permanents []Permanent
Units []*Unit
+ Artifacts []*Artifact
+ Rand rand.Rand
}
func NewState() *State {
- return &State{}
+ s := &State{}
+ s.Stack = NewStack(s)
+ return s
}
-func (s *State) AddNewPlayer(name string, deck *Deck) {
- p := NewPlayer(len(s.Players), name, deck, s)
+func (s *State) AddNewPlayer(name string, deck *Deck, color color.Color) {
+ p := NewPlayer(len(s.Players), name, deck, s, color)
s.Players = append(s.Players, p)
}
func (s *State) AddNewUnit(card string, pos Position, owner *Player) {
unit := NewUnit(card, s.Map.TileAt(pos), owner)
- s.Permanents = append(s.Permanents, &unit.Permanent)
+ s.Permanents = append(s.Permanents, unit)
s.Units = append(s.Units, unit)
}
+func (s *State) removePermanent(p Permanent) {
+ for i, perm := range s.Permanents {
+ if perm == p {
+ s.Permanents[i] = s.Permanents[len(s.Permanents)-1]
+ s.Permanents = s.Permanents[:len(s.Permanents)-1]
+ }
+ }
+}
+
+func removePtr[P *Unit | *Artifact](collection []P, ptr P) []P {
+ for i, p := range collection {
+ if p == ptr {
+ collection[i] = collection[len(collection)-1]
+ return collection[:len(collection)-1]
+ }
+ }
+ return collection
+}
+
+func (s *State) DestroyPermanent(p Permanent) {
+ s.removePermanent(p)
+
+ switch p.(type) {
+ case *Unit:
+ s.Units = removePtr(s.Units, p.(*Unit))
+ case *Artifact:
+ s.Artifacts = removePtr(s.Artifacts, p.(*Artifact))
+ }
+}
+
+func (s *State) AddNewArtifact(card string, pos Position, owner *Player) {
+ artifact := NewArtifact(card, s.Map.TileAt(pos), owner)
+ s.Permanents = append(s.Permanents, artifact)
+ s.Artifacts = append(s.Artifacts, artifact)
+}
+
+func (s *State) Fight(p1, p2 Permanent) {
+ p1.Fight(p2)
+ p2.Fight(p1)
+ if p1.IsDestroyed() {
+ s.DestroyPermanent(p1)
+ }
+ if p2.IsDestroyed() {
+ s.DestroyPermanent(p2)
+ }
+}
+
func (s *State) MoveUnit(u *Unit, t *Tile) {
u.move(s, t)
}
+
+func (s *State) EnemyUnits(player *Player) []*Unit {
+ // TODO: support coop / teams
+ enemyUnits := []*Unit{}
+ for _, u := range s.Units {
+ if u.GetController() != player {
+ enemyUnits = append(enemyUnits, u)
+ }
+ }
+ return enemyUnits
+}
diff --git a/go/game/tile.go b/go/game/tile.go
index 50688d83..b8e8c83f 100644
--- a/go/game/tile.go
+++ b/go/game/tile.go
@@ -2,7 +2,6 @@ package game
import (
"log"
- "strconv"
"strings"
)
@@ -62,27 +61,19 @@ var TileNames = map[string]TileType{
type Tile struct {
Position Position
- Permanent *Permanent
+ Permanent Permanent
Type TileType
Raw string
- PlayerId int
}
func INVALID_TILE() Tile {
- return Tile{INVALID_POSITION(), nil, 0, "", -1}
+ return Tile{INVALID_POSITION(), nil, 0, ""}
}
func NewTileFromString(tile string, pos Position) (Tile, error) {
tokens := strings.Split(tile, " ")
tileType := TileNames[strings.ToLower(tokens[0])]
- t := Tile{pos, nil, tileType, tile, -1}
- if tokens[0] == "spawn" {
- playerId, err := strconv.Atoi(tokens[2])
- if err != nil {
- return INVALID_TILE(), err
- }
- t.PlayerId = playerId
- }
+ t := Tile{pos, nil, tileType, tile}
return t, nil
}
diff --git a/go/game/unit.go b/go/game/unit.go
index 4c07d625..b81ea8b3 100644
--- a/go/game/unit.go
+++ b/go/game/unit.go
@@ -1,16 +1,17 @@
package game
import (
+ "fmt"
"golang.org/x/exp/slices"
"log"
"strconv"
"strings"
)
-type UnitStates int
+type UnitState int
const (
- Paralysis UnitStates = iota + 1
+ Paralysis UnitState = iota + 1
Poison
Panic
Rage
@@ -22,6 +23,13 @@ type Attack struct {
Range int
}
+func (a *Attack) String() string {
+ if a.Range == 1 {
+ return fmt.Sprintf("%d", a.Damage)
+ }
+ return fmt.Sprintf("%d range %d", a.Damage, a.Range)
+}
+
func parseAttack(attack string) Attack {
tokens := strings.Split(attack, " ")
a, err := strconv.Atoi(tokens[0])
@@ -40,34 +48,46 @@ func parseAttack(attack string) Attack {
}
type Unit struct {
- Permanent
- Health int
- Movement int
- Attack Attack
- Upkeep int
- // FullActions []FullActions
- // FreeActions []FreeActions
- States []UnitStates
+ PermanentBase
+ Health int
+ Movement int
+ Attack Attack
+ Upkeep int
+ FullActions []*FullActionPrototype
+ FreeActions []*FreeActionPrototype
+ States []UnitState
// Effects []Effects
+ AvailMoveActions int
+ AvailAttackActions int
}
func NewUnit(cardPath string, tile *Tile, owner *Player) *Unit {
card := NewCard(cardPath)
- return &Unit{
- Permanent{card, tile, 0, []*Permanent{}, owner, owner},
+ u := &Unit{
+ NewPermanentBase(card, tile, owner),
card.Values["health"].(int),
card.Values["movement"].(int),
parseAttack(card.Values["attack"].(string)),
card.Values["upkeep"].(int),
- []UnitStates{},
+ []*FullActionPrototype{},
+ []*FreeActionPrototype{},
+ []UnitState{},
+ 1,
+ 1,
}
+
+ tile.Permanent = u
+ return u
}
func (u *Unit) move(s *State, tile *Tile) {
if !slices.Contains(u.MoveRangeTiles(s.Map), tile) {
log.Panicf("Unit %s at %v can not move to %v", u.Card.Name, u.Tile.Position, tile.Position)
}
+ if tile.Permanent != nil {
+ log.Panicf("Unit %s at %v can not move to occupied %v", u.Card.Name, u.Tile.Position, tile.Position)
+ }
u.Tile.Permanent = nil
u.Tile = tile
@@ -84,6 +104,60 @@ func (u *Unit) MoveRangeTiles(m *Map) []*Tile {
return tiles
}
+func (u *Unit) AttackableTiles(m *Map) []*Tile {
+ return TilesInRange(m, u, u.Attack.Range)
+}
+
+func (u *Unit) AttackablePermanents(m *Map) []Permanent {
+ permanents := []Permanent{}
+ for _, t := range u.AttackableTiles(m) {
+ if t.Permanent != nil && t.Permanent.Attackable() {
+ permanents = append(permanents, t.Permanent)
+ }
+ }
+ return permanents
+}
+
func (u *Unit) IsAvailableTile(tile *Tile) bool {
return tile.IsFree()
}
+
+func (u *Unit) Fight(p Permanent) {
+ p.AddDamage(u.Attack.Damage)
+}
+
+func (u *Unit) IsDestroyed() bool {
+ return u.PermanentBase.Damage >= u.Health
+}
+
+func (u *Unit) Attackable() bool {
+ return true
+}
+
+func (u *Unit) HasFullAction() bool {
+ return len(u.FullActions) > 0
+}
+
+func (u *Unit) FullAction() {
+}
+
+func (u *Unit) GetAvailActionPrototypes() []ActionPrototype {
+ prototypes := []ActionPrototype{}
+ if u.AvailMoveActions > 0 {
+ prototypes = append(prototypes, NewMoveActionPrototype(u))
+ }
+
+ if u.AvailAttackActions > 0 {
+ prototypes = append(prototypes, NewAttackActionPrototype(u))
+ }
+
+ for _, p := range u.FullActions {
+ prototypes = append(prototypes, p)
+ }
+
+ for _, p := range u.FreeActions {
+ prototypes = append(prototypes, p)
+ }
+
+ return prototypes
+}