aboutsummaryrefslogtreecommitdiff
path: root/integration_test.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-06-06 20:59:12 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-06-06 20:59:12 +0200
commitd9a54f1319cc9dcc2861cecc9edfde14cf039c5b (patch)
tree74df791d6eda0ffe6c09cb32e5ffd76c4849cbac /integration_test.go
parent18592fd9f520a33ab307e496b995c1a7bf284893 (diff)
Add examples catalog with subcommand dispatch
- Extract game engine from package main to engine/ library package - Create examples/ package with registry, Entry type, and BuilderDeps - Register default world as an example (moved from main()) - Add combat example: two teams of units facing each other - Rewrite main.go as thin dispatch: go run . example [name] - Update README with examples catalog one-liner
Diffstat (limited to 'integration_test.go')
-rw-r--r--integration_test.go201
1 files changed, 0 insertions, 201 deletions
diff --git a/integration_test.go b/integration_test.go
deleted file mode 100644
index 7d687fb..0000000
--- a/integration_test.go
+++ /dev/null
@@ -1,201 +0,0 @@
-package main
-
-import (
- "image/color"
- "testing"
- "time"
-
- "age_of_empires/physics"
- "age_of_empires/selection"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestCameraScreenToWorldRoundTrip(t *testing.T) {
- zooms := []float64{0.25, 0.5, 1.0, 2.0, 3.0}
- points := []physics.Point{
- {X: 1000, Y: 1000},
- {X: 0, Y: 0},
- {X: 3200, Y: 2400},
- {X: 1600, Y: 1200},
- }
- for _, zoom := range zooms {
- cam := Camera{X: 1600, Y: 1200, Zoom: zoom}
- for _, p := range points {
- sx, sy := cam.WorldToScreen(p)
- got := cam.ScreenToWorld(int(sx), int(sy))
- assert.Equal(t, p, got, "zoom=%.2f point=%v", zoom, p)
- }
- }
-}
-
-func TestCameraClampsToBounds(t *testing.T) {
- cases := []struct{ x, y float64 }{
- {-99999, -99999},
- {99999, 99999},
- {-99999, 99999},
- {99999, -99999},
- }
- for _, c := range cases {
- cam := Camera{X: c.x, Y: c.y, Zoom: 1.0}
- cam.clampToBounds()
- halfW := (ScreenWidth / 2.0) / cam.Zoom
- halfH := (ScreenHeight / 2.0) / cam.Zoom
- assert.GreaterOrEqual(t, cam.X, halfW, "X=%v should be >= halfW", c.x)
- assert.LessOrEqual(t, cam.X, float64(WorldWidth)-halfW, "X=%v should be <= maxX", c.x)
- assert.GreaterOrEqual(t, cam.Y, halfH, "Y=%v should be >= halfH", c.y)
- assert.LessOrEqual(t, cam.Y, float64(WorldHeight)-halfH, "Y=%v should be <= maxY", c.y)
- }
-}
-
-func TestCameraZoomTowardCursor(t *testing.T) {
- zooms := []struct{ before, after float64 }{
- {1.0, 2.0},
- {1.0, 0.5},
- {2.0, 3.0},
- }
- cursorSX, cursorSY := 400, 300
- for _, z := range zooms {
- cam := Camera{X: 1600, Y: 1200, Zoom: z.before}
- worldBefore := cam.ScreenToWorld(cursorSX, cursorSY)
-
- // replicate the zoom-toward-cursor logic from Camera.Update
- wx0 := float64(cursorSX-ScreenWidth/2)/cam.Zoom + cam.X
- wy0 := float64(cursorSY-ScreenHeight/2)/cam.Zoom + cam.Y
- cam.Zoom = z.after
- wx1 := float64(cursorSX-ScreenWidth/2)/cam.Zoom + cam.X
- wy1 := float64(cursorSY-ScreenHeight/2)/cam.Zoom + cam.Y
- cam.X -= wx1 - wx0
- cam.Y -= wy1 - wy0
-
- worldAfter := cam.ScreenToWorld(cursorSX, cursorSY)
- assert.Equal(t, worldBefore, worldAfter, "zoom %.1f→%.1f: world point under cursor should be stable", z.before, z.after)
- }
-}
-
-func TestGatherLoop(t *testing.T) {
- // Initialize Game
- game := &Game{}
-
- // Town Center (Storage) at -100,0 (different grid cell from unit at 0,0)
- tcBuilder := NewEntityBuilder().
- WithPosition(physics.Point{X: -100, Y: 0}).
- WithResourceStorage()
- _ = game.Append(tcBuilder.Build())
-
- // Mine (Source) at 0, 200
- mineBuilder := NewEntityBuilder().
- WithPosition(physics.Point{X: 0, Y: 200}).
- WithResourceSource(100)
- mineID := game.Append(mineBuilder.Build())
-
- // Unit (Gatherer) at 0, 0
- unitBuilder := NewEntityBuilder().
- WithPosition(physics.Point{X: 0, Y: 0}).
- WithSolid(NewFilledCircleImage(100, color.White)).
- WithSelectable("round", selection.Unit).
- WithMove().
- WithOrder().
- WithResourceGatherer(10)
- game.Append(unitBuilder.Build())
-
- // Set up simulated time
- simTime := time.Now()
- game.NowFunc = func() time.Time { return simTime }
-
- // Click unit to select, then right-click mine — same gesture as in-game
- game.UpdateSimulation(Input{EscapePressed: true})
- game.UpdateSimulation(Input{Cursor: physics.Point{X: 50, Y: 50}, LeftMouseDown: true})
- game.UpdateSimulation(Input{Cursor: physics.Point{X: 50, Y: 50}, LeftMouseUp: true})
- game.UpdateSimulation(Input{Cursor: game.Position[mineID], RightMouseUp: true})
-
- initialResources := game.ResourceAmount
- dt := 16 * time.Millisecond
- maxTicks := 100_000
-
- deposited := false
-
- for i := 0; i < maxTicks; i++ {
- simTime = simTime.Add(dt)
- game.UpdateSimulation(Input{})
-
- if game.ResourceAmount > initialResources {
- deposited = true
- break
- }
- }
-
- if !deposited {
- t.Errorf("Unit failed to gather resources within %d ticks. ResourceAmount: %d", maxTicks, game.ResourceAmount)
- }
-}
-
-func TestTwoEntitiesMovingToSameCellDontOverlap(t *testing.T) {
- // Two units close together both ordered to the same cell should not overlap.
- game := &Game{}
-
- target := physics.Point{X: 500, Y: 0}
- unitImg := NewFilledCircleImage(100, color.White)
-
- unitA := game.Append(NewEntityBuilder().
- WithPosition(physics.Point{X: 200, Y: 0}).
- WithSolid(unitImg).WithSelectable("round", selection.Unit).
- WithMove().WithOrder().Build())
- unitB := game.Append(NewEntityBuilder().
- WithPosition(physics.Point{X: 100, Y: 0}).
- WithSolid(unitImg).WithSelectable("round", selection.Unit).
- WithMove().WithOrder().Build())
-
- // Area-select both units then right-click target — same gesture as in-game
- game.UpdateSimulation(Input{EscapePressed: true})
- game.UpdateSimulation(Input{Cursor: physics.Point{X: 50, Y: -50}, LeftMouseDown: true})
- game.UpdateSimulation(Input{Cursor: physics.Point{X: 350, Y: 50}, LeftMouseUp: true})
- game.UpdateSimulation(Input{Cursor: target, RightMouseUp: true})
-
- // MOVE_SPEED=10, cell=100 → 10 ticks per cell. B detours around A → ~60 ticks.
- for range 60 {
- game.UpdateSimulation(Input{})
- }
-
- assert.False(t, game.Move[unitA].IsActive, "Entity A should have stopped moving")
- assert.False(t, game.Move[unitB].IsActive, "Entity B should have stopped moving")
- 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])
-}