diff options
| -rw-r--r-- | entity.go | 13 | ||||
| -rw-r--r-- | gather_order.go | 76 | ||||
| -rw-r--r-- | geometry.go | 9 | ||||
| -rw-r--r-- | main.go | 67 | ||||
| -rw-r--r-- | order.go | 33 | ||||
| -rw-r--r-- | patrol_order.go | 32 | ||||
| -rw-r--r-- | resource.go | 16 |
7 files changed, 193 insertions, 53 deletions
@@ -12,11 +12,14 @@ func C[T any](t T) Component[T] { } type Entity struct { - Position Component[Point] - Image Component[*ebiten.Image] - Selection Component[Selection] - Move Component[Move] - Order Component[Order] + Position Component[Point] + Image Component[*ebiten.Image] + Selection Component[Selection] + Move Component[Move] + Order Component[Order] + ResourceGatherer Component[ResourceGatherer] + ResourceSource Component[ResourceSource] + ResourceStorage Component[ResourceStorage] } func (e Entity) Bounds() Rectangle { diff --git a/gather_order.go b/gather_order.go new file mode 100644 index 0000000..8cb3a7b --- /dev/null +++ b/gather_order.go @@ -0,0 +1,76 @@ +package main + +import ( + "math" + "time" + + "golang.org/x/exp/slog" +) + +type GatherOrder struct { + source *Entity +} + +func (o *GatherOrder) Update(e *Entity, g *Game) { + if e.Position.IsEnabled && e.Move.IsEnabled && e.ResourceGatherer.IsEnabled && e.ResourceGatherer.Value.CurrentTarget != nil { + gatherer := &e.ResourceGatherer.Value + source := gatherer.CurrentTarget.ResourceSource.Value + if gatherer.CurrentVolume == gatherer.MaxCapacity && !e.Move.Value.IsActive { + pointsNextToStorage := []Point{} + for _, entity := range g.Entities { + if entity.Position.IsEnabled && entity.ResourceStorage.IsEnabled { + pointsNextToStorage = append(pointsNextToStorage, AdjacentPoints(entity.Position.Value)...) + } + } + nextToStorage, distance := g.Closest(e.Position.Value, pointsNextToStorage) + if distance == math.MaxInt { + slog.Info("no accessible storage, canceling gather order") + e.Order.Value = nil + return + } + slog.Info("storage target", slog.String("storage", nextToStorage.String()), slog.Int("distance", distance)) + if distance > 1 { + slog.Info("moving to storage", slog.String("storage", nextToStorage.String())) + e.StartMove(nextToStorage, g.getMoveMap()) + } else { + gatherer.CurrentVolume = 0 + // TODO: increment storage + pointsNextToSource := AdjacentPoints(gatherer.CurrentTarget.Position.Value) + nextToSource, distance := g.Closest(e.Position.Value, pointsNextToSource) + if distance == math.MaxInt { + slog.Info("no accessible storage, canceling gather order") + e.Order.Value = nil + return + } + e.StartMove(nextToSource, g.getMoveMap()) + } + } else if Distance(e.Position.Value, gatherer.CurrentTarget.Position.Value) <= 100 { + now := time.Now() + if gatherer.CurrentVolume < gatherer.MaxCapacity && gatherer.LastPickupTime.Add(200*time.Millisecond).Before(now) { + gatherer.LastPickupTime = now + gatherer.CurrentVolume += 1 + source.Remaining -= 1 + slog.Info("gathered", slog.Int("current_volume", gatherer.CurrentVolume)) + } + } + } +} + +func Gather(e *Entity, source *Entity, g *Game) Order { + if e.Selection.IsEnabled && e.Position.IsEnabled && e.Move.IsEnabled && e.ResourceGatherer.IsEnabled && e.Order.IsEnabled { + if e.Selection.Value.IsSelected { + slog.Info("gathering from", slog.String("source", source.Position.Value.String())) + e.Order.Value = &GatherOrder{source: source} + e.ResourceGatherer.Value.CurrentTarget = source + pointsNextToSource := AdjacentPoints(source.Position.Value) + nextToSource, distance := g.Closest(e.Position.Value, pointsNextToSource) + if distance == math.MaxInt { + slog.Info("no accessible storage, canceling gather order") + e.Order.Value = nil + return nil + } + e.StartMove(nextToSource, g.getMoveMap()) + } + } + return &GatherOrder{source: source} +} diff --git a/geometry.go b/geometry.go index 0d90c6f..ba3f643 100644 --- a/geometry.go +++ b/geometry.go @@ -19,3 +19,12 @@ func Length(p Point) float64 { func Normalize(p Point) Point { return p.Div(int(Length(p))) } + +func AdjacentPoints(from Point) []Point { + return []Point{ + from.Add(Point{X: 0, Y: -100}), + from.Add(Point{X: 0, Y: 100}), + from.Add(Point{X: -100, Y: 0}), + from.Add(Point{X: 100, Y: 0}), + } +} @@ -4,6 +4,7 @@ import ( "image" "image/color" _ "image/jpeg" + "math" "os" "github.com/hajimehoshi/ebiten/v2" @@ -42,6 +43,23 @@ func (g *Game) getMoveMap() MoveMap { return MoveMap{Width: 3200, Height: 2400, Blocked: blocked} } +func (g *Game) Closest(from Point, candidates []Point) (Point, int) { + var closest Point + closestDistance := math.MaxInt + moveMap := g.getMoveMap() + for _, dest := range candidates { + path, ok := SearchPath(from, dest, moveMap) + if !ok { + continue + } + if len(path) < closestDistance { + closest = dest + closestDistance = len(path) + } + } + return closest, closestDistance +} + func (g *Game) updateSelecting(cursor Point, moveMap MoveMap) { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { g.Selection.Start = cursor @@ -49,10 +67,21 @@ func (g *Game) updateSelecting(cursor Point, moveMap MoveMap) { } if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonRight) { destination := cursor.Div(100).Mul(100) + var entityAtDestination *Entity + for _, e := range g.Entities { + if e.Position.IsEnabled && e.Position.Value == destination { + entityAtDestination = e + break + } + } slog.Info("destination", slog.String("destination", destination.String())) for _, e := range g.Entities { if e.Selection.IsEnabled && e.Selection.Value.IsSelected { - e.StartMove(destination, moveMap) + if entityAtDestination != nil && entityAtDestination.ResourceSource.IsEnabled { + Gather(e, entityAtDestination, g) + } else { + e.StartMove(destination, moveMap) + } } } } @@ -147,30 +176,38 @@ func main() { ebiten.SetWindowIcon([]image.Image{iconImg}) game := &Game{} ironImage := NewFilledRectangleImage(Point{100, 100}, color.RGBA{0x80, 0x80, 0x80, 0xff}) - ironHaloImage := NewStrokeRectangleImage(Point{110, 110}, SELECTION_HALO_SIZE, color.RGBA{0xff, 0x00, 0x00, 0xff}) ironMine := Entity{ - Position: C(Point{1000, 1000}), - Image: C(ironImage), - Selection: C(Selection{IsSelected: false, Halo: ironHaloImage}), + Position: C(Point{1000, 1000}), + Image: C(ironImage), + ResourceSource: C(ResourceSource{Remaining: 1000}), } game.Entities = append(game.Entities, &ironMine) + storageImage := NewFilledRectangleImage(Point{100, 100}, color.RGBA{0x00, 0x00, 0xff, 0xff}) + storage := Entity{ + Position: C(Point{1000, 2000}), + Image: C(storageImage), + ResourceStorage: C(ResourceStorage{}), + } + game.Entities = append(game.Entities, &storage) var order Order personImage := NewFilledCircleImage(100, color.RGBA{0xff, 0xff, 0xff, 0xff}) personSelectionHalo := NewStrokeCircleImage(110, SELECTION_HALO_SIZE, color.RGBA{0xff, 0x00, 0x00, 0xff}) person1 := Entity{ - Position: C(Point{2000, 2000}), - Image: C(personImage), - Selection: C(Selection{IsSelected: false, Halo: personSelectionHalo}), - Move: C(Move{IsActive: false}), - Order: C(order), + Position: C(Point{2000, 2000}), + Image: C(personImage), + Selection: C(Selection{IsSelected: false, Halo: personSelectionHalo}), + Move: C(Move{IsActive: false}), + Order: C(order), + ResourceGatherer: C(ResourceGatherer{MaxCapacity: 15}), } game.Entities = append(game.Entities, &person1) person2 := Entity{ - Position: C(Point{2200, 2200}), - Image: C(personImage), - Selection: C(Selection{IsSelected: false, Halo: personSelectionHalo}), - Move: C(Move{IsActive: false}), - Order: C(order), + Position: C(Point{2200, 2200}), + Image: C(personImage), + Selection: C(Selection{IsSelected: false, Halo: personSelectionHalo}), + Move: C(Move{IsActive: false}), + Order: C(order), + ResourceGatherer: C(ResourceGatherer{MaxCapacity: 15}), } game.Entities = append(game.Entities, &person2) game.CurrentAction = Selecting @@ -1,31 +1,9 @@ package main -import ( - "golang.org/x/exp/slog" -) - type Order interface { Update(e *Entity, g *Game) } -type PatrolOrder struct { - origin Point - destination Point -} - -func (o *PatrolOrder) Update(e *Entity, g *Game) { - if e.Position.IsEnabled && e.Move.IsEnabled && e.Order.IsEnabled { - if !e.Move.Value.IsActive { - moveMap := g.getMoveMap() - if e.Position.Value == o.destination { - e.StartMove(o.origin, moveMap) - } else { - e.StartMove(o.destination, moveMap) - } - } - } -} - func (e *Entity) UpdateOrder(g *Game) { if e.Order.IsEnabled { if e.Order.Value != nil { @@ -33,14 +11,3 @@ func (e *Entity) UpdateOrder(g *Game) { } } } - -func Patrol(e *Entity, destination Point) Order { - if e.Selection.IsEnabled && e.Position.IsEnabled && e.Move.IsEnabled && e.Order.IsEnabled { - if e.Selection.Value.IsSelected { - origin := e.Position.Value - slog.Info("patrolling between", slog.String("origin", origin.String()), slog.String("destination", destination.String())) - e.Order.Value = &PatrolOrder{origin: origin, destination: destination} - } - } - return &PatrolOrder{origin: e.Position.Value, destination: destination} -} diff --git a/patrol_order.go b/patrol_order.go new file mode 100644 index 0000000..b7201ed --- /dev/null +++ b/patrol_order.go @@ -0,0 +1,32 @@ +package main + +import "golang.org/x/exp/slog" + +type PatrolOrder struct { + origin Point + destination Point +} + +func (o *PatrolOrder) Update(e *Entity, g *Game) { + if e.Position.IsEnabled && e.Move.IsEnabled { + if !e.Move.Value.IsActive { + moveMap := g.getMoveMap() + if e.Position.Value == o.destination { + e.StartMove(o.origin, moveMap) + } else { + e.StartMove(o.destination, moveMap) + } + } + } +} + +func Patrol(e *Entity, destination Point) Order { + if e.Selection.IsEnabled && e.Position.IsEnabled && e.Move.IsEnabled && e.Order.IsEnabled { + if e.Selection.Value.IsSelected { + origin := e.Position.Value + slog.Info("patrolling between", slog.String("origin", origin.String()), slog.String("destination", destination.String())) + e.Order.Value = &PatrolOrder{origin: origin, destination: destination} + } + } + return &PatrolOrder{origin: e.Position.Value, destination: destination} +} diff --git a/resource.go b/resource.go new file mode 100644 index 0000000..1639cb2 --- /dev/null +++ b/resource.go @@ -0,0 +1,16 @@ +package main + +import "time" + +type ResourceSource struct { + Remaining int +} + +type ResourceGatherer struct { + MaxCapacity int + CurrentVolume int + CurrentTarget *Entity + LastPickupTime time.Time +} + +type ResourceStorage struct{} |
