aboutsummaryrefslogtreecommitdiff
path: root/engine/game.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 /engine/game.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 'engine/game.go')
-rw-r--r--engine/game.go180
1 files changed, 180 insertions, 0 deletions
diff --git a/engine/game.go b/engine/game.go
new file mode 100644
index 0000000..c5699d2
--- /dev/null
+++ b/engine/game.go
@@ -0,0 +1,180 @@
+package engine
+
+import (
+ "image/color"
+ "time"
+
+ "age_of_empires/ecs"
+ "age_of_empires/physics"
+ "age_of_empires/selection"
+
+ "github.com/hajimehoshi/ebiten/v2"
+ "github.com/hajimehoshi/ebiten/v2/text/v2"
+
+ "golang.org/x/exp/slog"
+)
+
+type Action string
+
+const (
+ Selecting Action = "selecting"
+ Patrolling Action = "patrolling"
+)
+
+var soilColor = color.RGBA{0x60, 0x40, 0x20, 0xff}
+
+type Game struct {
+ CurrentAction Action
+ Camera Camera
+ GlobalSelection selection.GlobalSelection
+ ResourceAmount int
+ FaceSource *text.GoTextFaceSource
+ UnitBuilder EntityBuilder
+ NowFunc func() time.Time
+ // Components
+ Mask []ecs.Mask
+ // Entities []*Entity
+ Position []physics.Point
+ RelBounds []physics.Rectangle
+ Image []*ebiten.Image
+ Selectable []selection.Selectable
+ Move []physics.Move
+ Order []OrderKind
+ PatrolOrder []PatrolOrder
+ ResourceGatherer []ResourceGatherer
+ ResourceSource []ResourceSource
+ ResourceStorage []ResourceStorage
+ Spawn []Spawn
+}
+
+func (g Game) Now() time.Time {
+ if g.NowFunc != nil {
+ return g.NowFunc()
+ }
+ return time.Now()
+}
+
+func (g *Game) Append(components Components, mask ecs.Mask) ecs.Entity {
+ g.Mask = append(g.Mask, mask)
+ g.Position = append(g.Position, components.Position)
+ g.RelBounds = append(g.RelBounds, components.RelBounds)
+ g.Image = append(g.Image, components.Image)
+ g.Selectable = append(g.Selectable, components.Selectable)
+ g.Move = append(g.Move, components.Move)
+ g.Order = append(g.Order, components.Order)
+ g.ResourceGatherer = append(g.ResourceGatherer, components.ResourceGatherer)
+ g.ResourceSource = append(g.ResourceSource, components.ResourceSource)
+ g.ResourceStorage = append(g.ResourceStorage, components.ResourceStorage)
+ g.Spawn = append(g.Spawn, components.Spawn)
+ return ecs.Entity(len(g.Mask) - 1)
+}
+
+func (g *Game) GetMoveMap() physics.MoveMap {
+ required := ecs.CM_Position
+ blocked := map[physics.Point]bool{}
+ for i, mask := range g.Mask {
+ if mask&required == required {
+ blocked[g.Position[i]] = true
+ }
+ }
+ return physics.MoveMap{Width: WorldWidth, Height: WorldHeight, Blocked: blocked}
+}
+
+func (g *Game) entityAt(position physics.Point) ecs.Entity {
+ required := ecs.CM_Position
+ for i, mask := range g.Mask {
+ if mask&required == required && g.Position[i] == position {
+ return ecs.Entity(i)
+ }
+ }
+ return -1
+}
+
+func getAllStorageDockings(g *Game) []physics.Point {
+ storageDockings := []physics.Point{}
+ required := ecs.CM_Position | ecs.CM_ResourceStorage
+ for i, mask := range g.Mask {
+ if mask&required == required {
+ position := g.Position[i]
+ storageDockings = append(storageDockings, physics.AdjacentPoints(position)...)
+ }
+ }
+ return storageDockings
+}
+
+const (
+ KeySpawnRequest = ebiten.KeyS
+ KeyPatrolRequest = ebiten.KeyQ
+)
+
+func (g *Game) updateSelecting(input Input, moveMap physics.MoveMap) {
+ if input.LeftMouseDown {
+ g.GlobalSelection.Start(input.Cursor)
+ g.GlobalSelection.StartScreen(input.ScreenCursor)
+ }
+ if input.RightMouseUp {
+ destination := input.Cursor.Div(100).Mul(100)
+ slog.Info("destination", slog.String("destination", destination.String()))
+ for _, i := range g.GlobalSelection.Selected {
+ MainAction(g, i, destination, moveMap)
+ }
+ }
+ if input.LeftMouseUp {
+ if g.GlobalSelection.IsActive(input.Cursor) {
+ if g.GlobalSelection.IsArea(input.Cursor) {
+ g.GlobalSelection.SelectMultiple(input.Cursor, g.Mask, g.Selectable, g.Position, g.RelBounds)
+ } else {
+ g.GlobalSelection.SelectSingle(input.Cursor, g.Mask, g.Selectable, g.Position, g.RelBounds)
+ }
+ }
+ }
+ if input.SpawnPressed {
+ required := ecs.CM_Spawn
+ for _, i := range g.GlobalSelection.Selected {
+ if g.Mask[i]&required == required {
+ spawn := &g.Spawn[i]
+ if g.ResourceAmount >= spawn.UnitResourceCost {
+ g.ResourceAmount -= spawn.UnitResourceCost
+ spawn.Requests = append(spawn.Requests, SpawnRequest{Start: g.Now()})
+ }
+ }
+ }
+ }
+}
+
+func (g *Game) UpdateSimulation(input Input) {
+ moveMap := g.GetMoveMap()
+
+ if input.EscapePressed {
+ g.GlobalSelection.Unselect(g.Selectable)
+ g.CurrentAction = Selecting
+ } else if input.PatrolPressed {
+ g.CurrentAction = Patrolling
+ }
+
+ switch g.CurrentAction {
+ case Selecting:
+ g.updateSelecting(input, moveMap)
+ case Patrolling:
+ if input.RightMouseUp {
+ destination := input.Cursor.Div(100).Mul(100)
+ StartPatrolSystem(destination, g.Mask, g.Position, g.Move, g.Order, g.PatrolOrder)
+ g.CurrentAction = Selecting
+ }
+ }
+
+ physics.UpdateMoveSystem(moveMap, g.Mask, g.Move, g.Position)
+ UpdateSpawnSystem(g, g.Now(), g.Mask, g.Spawn, g.Position)
+ UpdateGatherSystem(g, g.Now(), g.GetMoveMap(), g.Mask, g.Order, g.Position, g.Move, g.ResourceGatherer)
+ UpdatePatrolSystem(g.GetMoveMap(), g.Mask, g.Order, g.PatrolOrder, g.Position, g.Move)
+}
+
+func (g *Game) Update() error {
+ g.Camera.Update(1.0 / 60.0)
+ g.UpdateSimulation(CollectInput(&g.Camera))
+ return nil
+}
+
+func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
+ return ScreenWidth, ScreenHeight
+}