aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--integration_test.go37
-rw-r--r--physics/move.go49
3 files changed, 82 insertions, 7 deletions
diff --git a/README.md b/README.md
index 7a24235..07f4bf7 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,8 @@
## Bugs
-- [ ] stops moving if someone settles at destination
+- [x] stops moving if someone settles at destination
+- [ ] spawner spawns 2 units instead of 1
## Dependencies
diff --git a/integration_test.go b/integration_test.go
index 23d106b..7d687fb 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -162,3 +162,40 @@ func TestTwoEntitiesMovingToSameCellDontOverlap(t *testing.T) {
assert.NotEqual(t, game.Position[unitA], game.Position[unitB],
"Entity A and B should NOT overlap: A at %s, B at %s", game.Position[unitA], game.Position[unitB])
}
+
+func TestUnitDoesNotStopWhenDestinationOccupied(t *testing.T) {
+ // Unit B is far away, unit A is close. Both ordered to the same cell.
+ // A arrives first and settles. B should repath to an adjacent free cell
+ // rather than stopping mid-journey.
+ game := &Game{}
+ game.CurrentAction = Selecting
+ unitImg := NewFilledCircleImage(100, color.White)
+
+ target := physics.Point{X: 500, Y: 0}
+
+ unitA := game.Append(NewEntityBuilder().
+ WithPosition(physics.Point{X: 400, Y: 0}).
+ WithSolid(unitImg).WithSelectable("round", selection.Unit).
+ WithMove().WithOrder().Build())
+ unitB := game.Append(NewEntityBuilder().
+ WithPosition(physics.Point{X: 0, Y: 0}).
+ WithSolid(unitImg).WithSelectable("round", selection.Unit).
+ WithMove().WithOrder().Build())
+
+ game.UpdateSimulation(Input{EscapePressed: true})
+ game.UpdateSimulation(Input{Cursor: physics.Point{X: 50, Y: -50}, LeftMouseDown: true})
+ game.UpdateSimulation(Input{Cursor: physics.Point{X: 450, Y: 50}, LeftMouseUp: true})
+ game.UpdateSimulation(Input{Cursor: target, RightMouseUp: true})
+
+ // Run long enough for both units to settle (B travels ~5 cells)
+ for range 100 {
+ game.UpdateSimulation(Input{})
+ }
+
+ assert.False(t, game.Move[unitA].IsActive, "unit A should have stopped")
+ assert.False(t, game.Move[unitB].IsActive, "unit B should have stopped near target, not mid-journey")
+ assert.NotEqual(t, game.Position[unitA], game.Position[unitB], "units should not overlap")
+ // B should be adjacent to target, not stuck far away
+ assert.LessOrEqual(t, physics.Distance(game.Position[unitB], target), 150.0,
+ "unit B should be adjacent to target, got %s", game.Position[unitB])
+}
diff --git a/physics/move.go b/physics/move.go
index 78b97b7..d59fd05 100644
--- a/physics/move.go
+++ b/physics/move.go
@@ -144,6 +144,20 @@ func (s *PathSearch) search() (Path, bool) {
return Path{}, false
}
+func findNearestFreeAdjacent(from Point, blocked Point, moveMap MoveMap) (Point, bool) {
+ var free []Point
+ for _, p := range AdjacentPoints(blocked) {
+ if !moveMap.Blocked[p] {
+ free = append(free, p)
+ }
+ }
+ if len(free) == 0 {
+ return Point{}, false
+ }
+ dest, dist := Closest(from, free, moveMap)
+ return dest, dist != math.MaxInt
+}
+
func NewMove(origin Point, destination Point, moveMap MoveMap) Move {
current := Point{
X: origin.X / 100 * 100,
@@ -192,12 +206,24 @@ func UpdateMoveSystem(moveMap MoveMap, masks []ecs.Mask, moves []Move, positions
*position = moveToward(*position, next)
continue
}
- // About to snap to next cell — check it's still free
- if moveMap.Blocked[next] {
+ // About to snap to next cell — check it's still free (exclude own cell)
+ currentCell := Point{X: position.X / 100 * 100, Y: position.Y / 100 * 100}
+ if moveMap.Blocked[next] && next != currentCell {
slog.Debug("cell blocked on arrival, repathing", slog.String("next", next.String()))
- path, ok := SearchPath(*position, move.Destination, moveMap)
+ destination := move.Destination
+ if moveMap.Blocked[destination] {
+ newDest, ok := findNearestFreeAdjacent(*position, destination, moveMap)
+ if !ok {
+ slog.Info("no accessible cell near destination", slog.String("destination", destination.String()))
+ move.IsActive = false
+ continue
+ }
+ destination = newDest
+ move.Destination = newDest
+ }
+ path, ok := SearchPath(*position, destination, moveMap)
if !ok {
- slog.Info("no path found", slog.String("destination", move.Destination.String()))
+ slog.Info("no path found", slog.String("destination", destination.String()))
move.IsActive = false
continue
}
@@ -219,9 +245,20 @@ func UpdateMoveSystem(moveMap MoveMap, masks []ecs.Mask, moves []Move, positions
continue
}
// Here maybe the destination has to change
- path, ok := SearchPath(next, move.Destination, moveMap)
+ destination := move.Destination
+ if moveMap.Blocked[destination] && destination != next {
+ newDest, ok := findNearestFreeAdjacent(next, destination, moveMap)
+ if !ok {
+ slog.Info("no accessible cell near destination", slog.String("destination", destination.String()))
+ move.IsActive = false
+ continue
+ }
+ destination = newDest
+ move.Destination = newDest
+ }
+ path, ok := SearchPath(next, destination, moveMap)
if !ok {
- slog.Info("no path found", slog.String("destination", move.Destination.String()))
+ slog.Info("no path found", slog.String("destination", destination.String()))
move.IsActive = false
continue
}