diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-02-04 18:51:31 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:46:40 +0200 |
| commit | 8c3456f5a120a00f8a4e89065d7d50f2c1b4ad0d (patch) | |
| tree | 6e201152c264184bcd5aad784386654e921c8634 /go/game | |
| parent | 083ec284a9ef286729e544df9340a12760f03ab7 (diff) | |
| download | muhqs-game-8c3456f5a120a00f8a4e89065d7d50f2c1b4ad0d.tar.gz muhqs-game-8c3456f5a120a00f8a4e89065d7d50f2c1b4ad0d.zip | |
improve permanent pile support
* Fix alot of bugs caused by permanents without tiles
* Implement a permanent choice to select the permanents in a pile
* Fix how pile hooks are called
Diffstat (limited to 'go/game')
| -rw-r--r-- | go/game/action.go | 12 | ||||
| -rw-r--r-- | go/game/ai.go | 6 | ||||
| -rw-r--r-- | go/game/cardImplementations.go | 2 | ||||
| -rw-r--r-- | go/game/equipment.go | 3 | ||||
| -rw-r--r-- | go/game/permanent.go | 19 | ||||
| -rw-r--r-- | go/game/state.go | 12 | ||||
| -rw-r--r-- | go/game/targets.go | 33 | ||||
| -rw-r--r-- | go/game/unit.go | 19 | ||||
| -rw-r--r-- | go/game/unit_test.go | 12 |
9 files changed, 92 insertions, 26 deletions
diff --git a/go/game/action.go b/go/game/action.go index 08e42f64..934f5645 100644 --- a/go/game/action.go +++ b/go/game/action.go @@ -129,6 +129,14 @@ type MoveAction struct { const moveActionTargetDesc = "available tile" +func (a *MoveAction) targetTile() *Tile { + if t, ok := a.targets.sel[0].(*Tile); ok { + return t + } + + return a.targets.sel[0].(*Unit).Tile() +} + func NewMoveAction(u *Unit) *MoveAction { a := &MoveAction{ ActionBase{ @@ -140,7 +148,7 @@ func NewMoveAction(u *Unit) *MoveAction { a.targets = newTargets(u.Controller().gameState, []string{moveActionTargetDesc}, a) a.resolveFunc = func(s *State) { - tile := a.targets.sel[0].(*Tile) + tile := a.targetTile() u.move(s, tile) } @@ -161,7 +169,7 @@ func (a *MoveAction) String() string { return fmt.Sprintf("move %s", u.Movement.String()) } - t := a.targets.sel[0].(*Tile) + t := a.targetTile() return fmt.Sprintf("%v: %v -> %v", u.Controller().Name, u.Card().Name, t.Position) } diff --git a/go/game/ai.go b/go/game/ai.go index ac6fb29a..181f6dc1 100644 --- a/go/game/ai.go +++ b/go/game/ai.go @@ -102,7 +102,7 @@ func (m *Map) generateMapGraphFor(u *Unit) *dijkstra.Graph { } func findPathTo(graph *dijkstra.Graph, u *Unit, pos Position) (dijkstra.BestPath, error) { - srcId, err := graph.GetMapping(u.Tile().Position.String()) + srcId, err := graph.GetMapping(u.TileOrContainingPermTile().Position.String()) if err != nil { log.Fatalf("No mapping %s in Graph: %s", u.Tile().Position.String(), err) } @@ -119,6 +119,10 @@ func findPathsToUnits(graph *dijkstra.Graph, m *Map, u *Unit, units []*Unit) []* paths := []*dijkstra.BestPath{} for _, unit := range units { + if unit.tile == nil { + continue + } + var bestPathToUnit *dijkstra.BestPath = nil for _, adjacentTile := range TilesInRangeFromOrigin(m, unit.Tile().Position, 1) { path, err := findPathTo(graph, u, adjacentTile.Position) diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go index b20b46bc..be625211 100644 --- a/go/game/cardImplementations.go +++ b/go/game/cardImplementations.go @@ -56,7 +56,7 @@ func (*swordImplementation) onPile(p Permanent) { } } -func (*swordImplementation) onDrop(p Permanent) { +func (*swordImplementation) onUnpile(p Permanent) { if u, ok := p.(*Unit); ok && !INVALID_ATTACK(u.Attack) { u.Attack.attacks[0] -= 1 } diff --git a/go/game/equipment.go b/go/game/equipment.go index 14e8b317..af06fdb1 100644 --- a/go/game/equipment.go +++ b/go/game/equipment.go @@ -28,11 +28,10 @@ func newEquipment(card *Card, containing interface{}, owner *Player) *Equipment } func (e *Equipment) onDrop(p Permanent) { + e.PermanentBase.onDrop(p) e.Durability-- if e.Durability < 1 { e.Controller().gameState.DestroyPermanent(e) return } - - p.Card().Impl.onDrop(p) } diff --git a/go/game/permanent.go b/go/game/permanent.go index 91aa0cda..67c39e0b 100644 --- a/go/game/permanent.go +++ b/go/game/permanent.go @@ -85,8 +85,12 @@ func (p *PermanentBase) Owner() *Player { } func (p *PermanentBase) String() string { - return fmt.Sprintf("%s's %s@%s", - p.Controller().Name, p.Card().Name, p.Tile().Position.String()) + if p.Tile() != nil { + return fmt.Sprintf("%s's %s@%v", + p.Controller().Name, p.Card().Name, p.Tile().Position) + } + + return fmt.Sprintf("%s's %s", p.Controller().Name, p.Card().Name) } func DealDamage(src, dest Permanent, damage int) { @@ -157,14 +161,17 @@ func (p *PermanentBase) RemovePermanentFromPile(rm Permanent) { 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 (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 AddPermanentToPile(containing, p Permanent) { p.SetContainingPerm(containing) containing.AddPermanentToPile(p) - p.onPile(p) + p.onPile(containing) } func RemovePermanentFromPile(containing, rm Permanent) { diff --git a/go/game/state.go b/go/game/state.go index 45c8bba5..35aeb342 100644 --- a/go/game/state.go +++ b/go/game/state.go @@ -126,16 +126,16 @@ func (s *State) IsValidMove(a *MoveAction) error { } u := a.Source().(*Unit) - tile := a.Targets().sel[0].(*Tile) + tile := relaxedTileTarget(a, a.Targets().sel[0]) if !slices.Contains(u.MoveRangeTiles(s.Map), tile) { - return fmt.Errorf("Unit %s at %s can not move to %s", - u.card.Name, u.tile.Position.String(), tile.Position.String()) + return fmt.Errorf("Unit %s@%v can not move to %s", + u.card.Name, u.tile.Position, tile.Position.String()) } - if tile.Permanent != nil { - return fmt.Errorf("Unit %s at %s can not move to occupied %s", - u.card.Name, u.tile.Position.String(), tile.Position.String()) + if !u.IsAvailableTile(tile) { + return fmt.Errorf("Unit %s@%v can not move to not available tile %v", + u.card.Name, u.tile.Position, tile) } return nil diff --git a/go/game/targets.go b/go/game/targets.go index 0ab61cbc..046c53d6 100644 --- a/go/game/targets.go +++ b/go/game/targets.go @@ -165,6 +165,7 @@ func attackableTargetConstraint(action Action) TargetConstraintFunc { p, _ := t.(Permanent) if !p.Attackable() { err = fmt.Errorf("Permanent %v is not attackable", p) + return } if attackAction, ok := action.(*AttackAction); ok { @@ -215,7 +216,6 @@ func parsePermanentTargetConstraint(desc string, s *State, action Action) []Targ } if strings.Contains(desc, "attackable ") { - log.Println("adding attackable constrained") constraints = append(constraints, attackableTargetConstraint(action)) } @@ -271,6 +271,25 @@ func parseArtifactTargetConstraint(constraint string, s *State, action Action) [ return constraints } +func relaxedTileTarget(action Action, t interface{}) *Tile { + // Strict case + if _, ok := action.(*MoveAction); !ok { + return t.(*Tile) + } + + // Move actions allow to also move to Tile occupied by a crewable permanent + switch t := t.(type) { + case *Tile: + return t + case *Unit: + return t.Tile() + default: + log.Fatalf("Not handled target type %T", t) + } + + return nil +} + var tileTargetConstraint TargetConstraintFunc = func(t interface{}) (err error) { _, ok := t.(*Tile) if !ok { @@ -280,18 +299,20 @@ var tileTargetConstraint TargetConstraintFunc = func(t interface{}) (err error) } func parseTileTargetConstraint(desc string, s *State, action Action) []TargetConstraintFunc { - log.Println("parsing", desc) - constraints := []TargetConstraintFunc{tileTargetConstraint} + constraints := []TargetConstraintFunc{} if moveAction, ok := action.(*MoveAction); ok { u := moveAction.Source().(*Unit) constraints = append(constraints, func(t interface{}) (err error) { - tile := t.(*Tile) + tile := relaxedTileTarget(action, t) if slices.Contains(u.MoveRangeTiles(s.Map), tile) { return nil } return fmt.Errorf("tile %v is not in %v's movement range", tile, u) }) + // Move action allow also unit targets as though they were tiles + } else { + constraints = append(constraints, tileTargetConstraint) } var player *Player @@ -314,7 +335,7 @@ func parseTileTargetConstraint(desc string, s *State, action Action) []TargetCon } constraints = append(constraints, func(t interface{}) (err error) { - tile := t.(*Tile) + tile := relaxedTileTarget(action, t) if slices.Contains(s.AvailableSpawnTiles(player, card), tile) { return nil } @@ -324,7 +345,7 @@ func parseTileTargetConstraint(desc string, s *State, action Action) []TargetCon if strings.Contains(desc, "available") { constraints = append(constraints, func(t interface{}) (err error) { - tile := t.(*Tile) + tile := relaxedTileTarget(action, t) if card == nil { log.Fatalf("Not implemented availability tile constraint for %T", action) diff --git a/go/game/unit.go b/go/game/unit.go index a0d6bfa4..d38d156d 100644 --- a/go/game/unit.go +++ b/go/game/unit.go @@ -146,11 +146,21 @@ func (u *Unit) move(s *State, tile *Tile) { } } +func (u *Unit) TileOrContainingPermTile() *Tile { + tile := u.tile + if tile == nil { + tile = u.containingPerm.Tile() + } + + return tile +} + func (u *Unit) MoveRangeTiles(m *Map) []*Tile { tiles := []*Tile{} // TODO: build graph only containing possible tile in movement range graph := m.generateMapGraphFor(u) - for _, pos := range PositionsInRange(u.tile.Position, u.Movement.Range, false) { + origin := u.TileOrContainingPermTile().Position + for _, pos := range PositionsInRange(origin, u.Movement.Range, false) { tile := m.TileAt(pos) if tile == nil || !u.IsAvailableTile(tile) { continue @@ -171,6 +181,10 @@ func (u *Unit) MoveRangeTiles(m *Map) []*Tile { } func (u *Unit) AttackableTiles(m *Map) []*Tile { + // Units not on the map can not attack + if u.tile == nil { + return nil + } return TilesInRange(m, u, u.Attack.MaxRange()) } @@ -207,7 +221,8 @@ func (u *Unit) IsDestroyed() bool { } func (u *Unit) Attackable() bool { - return true + // Only units on the map are attackable + return u.tile != nil } func (u *Unit) HasFullAction() bool { diff --git a/go/game/unit_test.go b/go/game/unit_test.go index 341fb350..d6b5b079 100644 --- a/go/game/unit_test.go +++ b/go/game/unit_test.go @@ -44,6 +44,18 @@ symbols: } } + if INVALID_ATTACK(f.Attack) { + t.Fatalf("fisher has an invalid attack") + } + + if a.Attackable() { + t.Fatalf("Piled archer is attackable") + } + + if len(a.AttackableTiles(s.Map)) != 0 { + t.Fatalf("Piled archer has attackable tiles") + } + a.move(s, s.Map.TileAt(Position{1, 1})) if len(f.Pile()) != 0 { t.Fatalf("fisher's pile is not empty") |
