From 3aab76ead19850ff1e2aae08ffe8c1948a4fb4af Mon Sep 17 00:00:00 2001 From: Eugene Blikh Date: Tue, 28 Apr 2026 03:10:27 +0300 Subject: [PATCH] crew-movement: wire input, tick, and render Add drawCrew to render.go (interpolated circle + selection ring + initial label). Extend Game with crew slice, walkable-tile map, selectedCrew, and dt-based tick; handle left-click for crew selection and BFS-path dispatch. --- main.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- render.go | 23 +++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 7d86e990e5d09da1af41eb85e0784164ed11bc82..45d0c18732f76f042fac01f7c32469f3757cb614 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,55 @@ package main import ( "image/color" + "time" "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" ) // Game holds the top-level state for the Ebitengine run loop. type Game struct { - ship Ship + ship Ship + walk map[[2]int]bool + crew []Crew + selectedCrew int + lastTime time.Time } -// Update advances simulation; nothing to do in this milestone. +// Update advances simulation by dt seconds and handles input. func (g *Game) Update() error { + if g.lastTime.IsZero() { + g.lastTime = time.Now() + return nil + } + dt := time.Since(g.lastTime).Seconds() + g.lastTime = time.Now() + if dt > 0.1 { + dt = 0.1 + } + + for i := range g.crew { + updateCrew(&g.crew[i], dt) + } + + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + cx, cy := ebiten.CursorPosition() + tx, ty := cx/TilePx, cy/TilePx + + for i := range g.crew { + if g.crew[i].TileX == tx && g.crew[i].TileY == ty { + g.selectedCrew = i + return nil + } + } + + if g.selectedCrew >= 0 && g.walk[[2]int{tx, ty}] { + from := [2]int{g.crew[g.selectedCrew].TileX, g.crew[g.selectedCrew].TileY} + g.crew[g.selectedCrew].Path = bfsPath(g.walk, from, [2]int{tx, ty}) + g.crew[g.selectedCrew].MoveT = 0 + } + } + return nil } @@ -20,6 +58,9 @@ func (g *Game) Update() error { func (g *Game) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{10, 15, 25, 255}) drawShip(screen, g.ship) + for i, c := range g.crew { + drawCrew(screen, c, i == g.selectedCrew) + } } // Layout fixes the virtual resolution; the window scales to fit. @@ -28,9 +69,12 @@ func (g *Game) Layout(_, _ int) (int, int) { } func main() { + ship := NewPlayerShip() + walk := walkableTiles(ship) + crew := NewStartingCrew() ebiten.SetWindowSize(1280, 720) ebiten.SetWindowTitle("ftl-shape") - if err := ebiten.RunGame(&Game{ship: NewPlayerShip()}); err != nil { + if err := ebiten.RunGame(&Game{ship: ship, walk: walk, crew: crew, selectedCrew: -1}); err != nil { panic(err) } } diff --git a/render.go b/render.go index a81cbcdd5b4fd5e613d2e4fe2faf2140dfc53e04..878c778e6f2382ec4484a1807d63310503f3cb4b 100644 --- a/render.go +++ b/render.go @@ -64,3 +64,26 @@ func drawRoom(screen *ebiten.Image, r Room) { // Label: a few pixels inside the top-left corner. ebitenutil.DebugPrintAt(screen, r.Name, int(x)+3, int(y)+2) } + +const CrewRadius = 6 + +var white = color.RGBA{255, 255, 255, 255} + +func drawCrew(screen *ebiten.Image, c Crew, selected bool) { + var fx, fy float64 + if len(c.Path) > 0 { + fx = float64(c.TileX) + (float64(c.Path[0][0])-float64(c.TileX))*c.MoveT + fy = float64(c.TileY) + (float64(c.Path[0][1])-float64(c.TileY))*c.MoveT + } else { + fx = float64(c.TileX) + fy = float64(c.TileY) + } + px := float32(fx*TilePx + TilePx/2) + py := float32(fy*TilePx + TilePx/2) + + vector.DrawFilledCircle(screen, px, py, CrewRadius, c.Color, true) + if selected { + vector.StrokeCircle(screen, px, py, CrewRadius+1, 1, white, true) + } + ebitenutil.DebugPrintAt(screen, string(c.Initial), int(px)-2, int(py)-5) +}