package sim import "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level" // Mover är något som rör sig ruta för ruta i labyrinten (spelaren, senare // följarna). Rörelsen är mjukt interpolerad mellan rutor men beslut om // riktning fattas bara vid rutgränser, som i klassiska labyrintspel. type Mover struct { Tile level.Point // rutan vi står på / lämnar Target level.Point // rutan vi är på väg till (== Tile när vi står stilla) Progress float64 // 0..1 längs Tile -> Target Facing Dir // senaste riktning (för sprite), aldrig None Speed float64 // rutor per sekund } // NewMover skapar en stillastående Mover på en ruta. func NewMover(start level.Point, speed float64) *Mover { return &Mover{Tile: start, Target: start, Facing: Down, Speed: speed} } // Moving rapporterar om vi är mellan två rutor. func (m *Mover) Moving() bool { return m.Target != m.Tile } // maxTilesPerStep skyddar mot oändliga loopar vid absurt stora dt. const maxTilesPerStep = 16 // Step för rörelsen framåt dt sekunder med önskad riktning want (None = ingen // input). canEnter avgör om en ruta får beträdas. Returnerar antalet rutor // som nåddes under steget (normalt 0 eller 1). // // Regler: står vi stilla och får input startar vi mot grannrutan om den är // passerbar, annars vänder vi oss bara åt det hållet. Är vi på väg fortsätter // vi till rutgränsen; väl där fortsätter vi direkt i önskad riktning om den är // fri (så att hållen tangent ger jämn fart), annars stannar vi. func (m *Mover) Step(dt float64, want Dir, canEnter func(level.Point) bool) int { arrived := 0 remaining := m.Speed * dt // i rutor for i := 0; i < maxTilesPerStep; i++ { if remaining <= eps { return arrived } if !m.Moving() { if want == None { return arrived } m.Facing = want dx, dy := want.Delta() next := level.Point{X: m.Tile.X + dx, Y: m.Tile.Y + dy} if !canEnter(next) { return arrived } m.Target = next m.Progress = 0 } need := 1 - m.Progress if remaining+eps < need { m.Progress += remaining return arrived } // Framme vid rutan. eps gör att 10 × 0.1 räknas som exakt 1 ruta trots // flyttalsavrundning, så att rörelsen blir deterministisk per bildruta. remaining = max(0, remaining-need) m.Tile = m.Target m.Progress = 0 arrived++ } return arrived } // eps är toleransen för flyttalsavrundning i rutor. const eps = 1e-9 // WorldPos returnerar den interpolerade positionen i pixlar (övre vänstra // hörnet av figuren) givet rutstorleken. func (m *Mover) WorldPos(tileSize float64) (x, y float64) { x = float64(m.Tile.X) * tileSize y = float64(m.Tile.Y) * tileSize if m.Moving() { x += float64(m.Target.X-m.Tile.X) * tileSize * m.Progress y += float64(m.Target.Y-m.Tile.Y) * tileSize * m.Progress } return x, y }