Some checks failed
release / build-release (push) Failing after 27s
- 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>
137 lines
3.9 KiB
Go
137 lines
3.9 KiB
Go
package sim
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
|
|
)
|
|
|
|
func pt(x, y int) level.Point { return level.Point{X: x, Y: y} }
|
|
|
|
// open: 5x5 med vägg runt om, allt annat fritt.
|
|
func open(p level.Point) bool { return p.X >= 1 && p.X <= 5 && p.Y >= 1 && p.Y <= 5 }
|
|
|
|
func TestMoveFourDirections(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
dir Dir
|
|
want level.Point
|
|
}{
|
|
{Up, pt(3, 2)}, {Down, pt(3, 4)}, {Left, pt(2, 3)}, {Right, pt(4, 3)},
|
|
} {
|
|
m := NewMover(pt(3, 3), 4) // 4 rutor/s -> 0.25 s per ruta
|
|
if n := m.Step(0.25, tc.dir, open); n != 1 {
|
|
t.Errorf("%s: arrived=%d, vill ha 1", tc.dir, n)
|
|
}
|
|
if m.Tile != tc.want || m.Moving() {
|
|
t.Errorf("%s: hamnade på %v (moving=%v), vill ha %v", tc.dir, m.Tile, m.Moving(), tc.want)
|
|
}
|
|
if m.Facing != tc.dir {
|
|
t.Errorf("%s: facing=%s", tc.dir, m.Facing)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBlockedByWallTurnsButStays(t *testing.T) {
|
|
m := NewMover(pt(1, 1), 4)
|
|
if n := m.Step(1, Left, open); n != 0 {
|
|
t.Fatalf("gick genom väggen: %d", n)
|
|
}
|
|
if m.Moving() || m.Tile != (pt(1, 1)) {
|
|
t.Fatal("ska stå kvar")
|
|
}
|
|
if m.Facing != Left {
|
|
t.Fatalf("ska vända sig mot väggen, facing=%s", m.Facing)
|
|
}
|
|
}
|
|
|
|
func TestContinuousMovementKeepsPace(t *testing.T) {
|
|
m := NewMover(pt(1, 1), 4)
|
|
if n := m.Step(0.5, Right, open); n != 2 {
|
|
t.Fatalf("arrived=%d, vill ha 2", n)
|
|
}
|
|
if m.Tile != (pt(3, 1)) {
|
|
t.Fatalf("tile=%v", m.Tile)
|
|
}
|
|
// Liten överskjutning ska bli start på nästa ruta, inte tappas bort.
|
|
m = NewMover(pt(1, 1), 4)
|
|
m.Step(0.3, Right, open)
|
|
if m.Tile != (pt(2, 1)) || !m.Moving() || math.Abs(m.Progress-0.2) > 1e-9 {
|
|
t.Fatalf("tile=%v moving=%v progress=%.3f", m.Tile, m.Moving(), m.Progress)
|
|
}
|
|
}
|
|
|
|
func TestStopsAtTileBoundaryWithoutInput(t *testing.T) {
|
|
m := NewMover(pt(1, 1), 4)
|
|
m.Step(0.125, Right, open) // halvvägs
|
|
if !m.Moving() {
|
|
t.Fatal("ska vara på väg")
|
|
}
|
|
m.Step(1, None, open) // släpp tangenten: avsluta rutan, stanna sedan
|
|
if m.Moving() || m.Tile != (pt(2, 1)) {
|
|
t.Fatalf("tile=%v moving=%v", m.Tile, m.Moving())
|
|
}
|
|
}
|
|
|
|
func TestDirectionChangesOnlyAtTileBoundary(t *testing.T) {
|
|
m := NewMover(pt(1, 1), 4)
|
|
m.Step(0.125, Right, open) // halvvägs åt höger
|
|
m.Step(0.125, Down, open) // vill ner: ska först slutföra höger
|
|
if m.Tile != (pt(2, 1)) || m.Moving() {
|
|
t.Fatalf("efter 0.25 s: tile=%v moving=%v", m.Tile, m.Moving())
|
|
}
|
|
m.Step(0.25, Down, open)
|
|
if m.Tile != (pt(2, 2)) {
|
|
t.Fatalf("tile=%v", m.Tile)
|
|
}
|
|
}
|
|
|
|
func TestWorldPosInterpolates(t *testing.T) {
|
|
m := NewMover(pt(1, 1), 4)
|
|
m.Step(0.125, Right, open) // progress 0.5
|
|
x, y := m.WorldPos(16)
|
|
if x != 24 || y != 16 {
|
|
t.Fatalf("pos=(%v,%v), vill ha (24,16)", x, y)
|
|
}
|
|
m.Step(0.125, Right, open)
|
|
if x, _ := m.WorldPos(16); x != 32 {
|
|
t.Fatalf("x=%v", x)
|
|
}
|
|
}
|
|
|
|
func TestHugeDtIsBounded(t *testing.T) {
|
|
m := NewMover(pt(0, 0), 1000)
|
|
if n := m.Step(1000, Right, func(level.Point) bool { return true }); n != maxTilesPerStep {
|
|
t.Fatalf("arrived=%d, vill ha %d", n, maxTilesPerStep)
|
|
}
|
|
}
|
|
|
|
func TestDirHelpers(t *testing.T) {
|
|
for _, d := range []Dir{Up, Down, Left, Right} {
|
|
if d.Opposite().Opposite() != d {
|
|
t.Errorf("%s: dubbel motsats", d)
|
|
}
|
|
dx, dy := d.Delta()
|
|
ox, oy := d.Opposite().Delta()
|
|
if dx != -ox || dy != -oy {
|
|
t.Errorf("%s: delta/motsats stämmer inte", d)
|
|
}
|
|
}
|
|
if dx, dy := None.Delta(); dx != 0 || dy != 0 || None.Opposite() != None {
|
|
t.Error("None ska vara neutral")
|
|
}
|
|
}
|
|
|
|
// TestFrameExactArrival: 6 rutor/s vid 60 TPS = exakt 10 bildrutor per ruta,
|
|
// trots att 0.1 inte går att representera exakt. Viktigt för skriptläget.
|
|
func TestFrameExactArrival(t *testing.T) {
|
|
m := NewMover(pt(1, 1), 6)
|
|
arrived := 0
|
|
for frame := 1; frame <= 40; frame++ {
|
|
arrived += m.Step(1.0/60, Right, func(level.Point) bool { return true })
|
|
if frame%10 == 0 && (arrived != frame/10 || m.Moving()) {
|
|
t.Fatalf("bildruta %d: arrived=%d moving=%v progress=%.17f", frame, arrived, m.Moving(), m.Progress)
|
|
}
|
|
}
|
|
}
|