Files
metro-puzzle-jp/internal/sim/mover.go
claude 4ae7dba291
Some checks failed
release / build-release (push) Failing after 27s
M1: rörelse, kollision, kamera, scener, tileset, skärmdumpar, CI
- internal/sim: rutbaserad Mover (4 riktningar, riktningsbyte vid rutgräns,
  bildrute-exakt ankomst) och Camera (följer, klampar, centrerar små kartor),
  med tester
- internal/game: scener meny -> bana -> resultat, tangentbord + gamepad,
  tileset och spelaranimation, HUD, skriptläge (-script) för deterministiska
  skärmdumpar och tester
- tools/genart genererar placeholder-grafik (tiles.png, player.png);
  tools/shots tar doc/img/*.png som README och wiki visar
- bana 002-shinjuku (33x21) för kamerascroll; level.List/LoadFile/TrainAt
- .gitea/workflows/release.yaml: vet + test + linux/windows-zippar till
  rullande latest på brasse-linux01-runnern (linux-amd64)
- docs: roadmap M1 klar, tester, plan, README med bilder och styrning

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-23 19:16:53 +02:00

84 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}