M2: tåg med tidtabell, spärrar med kort, skyltar, vinst/förlust
All checks were successful
release / build-release (push) Successful in 1m35s
All checks were successful
release / build-release (push) Successful in 1m35s
- level: Schedule (ankomst/intervall/uppehåll) med Status(t), spårnummer, avgift, skylttexter per i-ruta, Path (BFS) och UngatedPlatforms – regeln att perronger bara nås via spärrar testas för alla banor - sim.Run: klocka, tågkort (saldo dras vid spärr, tomt kort stoppas), ombordstigning med Enter när tåget är inne, vinst/förlust; headless bot-test klarar bana 001 och förlorar på fel tåg - game: perronginfo (avgår om / nästa tåg om), skyltruta, nekad spärr, tågvagn som ritas när tåget är inne, inforutan flyttas upp när spelaren står nära nederkanten; resultat med spärrpassager och saldo - bana 001 ombyggd så spärrarna är flaskhalsar; bana 002 omgenererad med spärrar runt perrongerna och skylttexter - tools/shots räknar ut vägar med BFS; skriptkommandot waittrain - docs/README/wiki uppdaterade med nya bilder Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package level
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -69,15 +70,20 @@ func TestOutsideGridIsWall(t *testing.T) {
|
||||
|
||||
func TestValidationErrors(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"ojämn rad": strings.Replace(sample, `"#.#.#.#"`, `"#.#.#.##"`, 1),
|
||||
"okänt tecken": strings.Replace(sample, `"#.#.#.#"`, `"#.#.#X#"`, 1),
|
||||
"två starter": strings.Replace(sample, `"#S..#P#"`, `"#S.S#P#"`, 1),
|
||||
"ingen start": strings.Replace(sample, `"#S..#P#"`, `"#...#P#"`, 1),
|
||||
"ingen perrong": strings.Replace(sample, `"#S..#P#"`, `"#S..#.#"`, 1),
|
||||
"inget rätt tåg": strings.Replace(sample, `"correct": true`, `"correct": false`, 1),
|
||||
"tåg vid fel perrong": strings.Replace(sample, `"platform": "P"`, `"platform": "p"`, 1),
|
||||
"saknar id": strings.Replace(sample, `"id": "test"`, `"id": ""`, 1),
|
||||
"trasig json": "{",
|
||||
"ojämn rad": strings.Replace(sample, `"#.#.#.#"`, `"#.#.#.##"`, 1),
|
||||
"okänt tecken": strings.Replace(sample, `"#.#.#.#"`, `"#.#.#X#"`, 1),
|
||||
"två starter": strings.Replace(sample, `"#S..#P#"`, `"#S.S#P#"`, 1),
|
||||
"ingen start": strings.Replace(sample, `"#S..#P#"`, `"#...#P#"`, 1),
|
||||
"ingen perrong": strings.Replace(sample, `"#S..#P#"`, `"#S..#.#"`, 1),
|
||||
"inget rätt tåg": strings.Replace(sample, `"correct": true`, `"correct": false`, 1),
|
||||
"tåg vid fel perrong": strings.Replace(sample, `"platform": "P"`, `"platform": "p"`, 1),
|
||||
"dwell >= interval": strings.Replace(sample, `"correct": true`, `"correct": true, "schedule": {"intervalSec": 10, "dwellSec": 10}`, 1),
|
||||
"negativ offset": strings.Replace(sample, `"correct": true`, `"correct": true, "schedule": {"offsetSec": -1}`, 1),
|
||||
"skylt utanför i-ruta": strings.Replace(sample, `"trains"`, `"signs": [{"x": 1, "y": 1, "text": {"sv": "x"}}], "trains"`, 1),
|
||||
"i-ruta utan skylt": strings.Replace(sample, `"#S..#P#"`, `"#Si.#P#"`, 1),
|
||||
"skylt utan text": strings.Replace(strings.Replace(sample, `"#S..#P#"`, `"#Si.#P#"`, 1), `"trains"`, `"signs": [{"x": 2, "y": 1, "text": {}}], "trains"`, 1),
|
||||
"saknar id": strings.Replace(sample, `"id": "test"`, `"id": ""`, 1),
|
||||
"trasig json": "{",
|
||||
}
|
||||
for name, src := range cases {
|
||||
if _, err := Parse([]byte(src)); err == nil {
|
||||
@@ -133,6 +139,19 @@ func TestAllShippedLevels(t *testing.T) {
|
||||
if u := l.Unreachable(); len(u) > 0 {
|
||||
t.Errorf("onåbara interaktiva rutor: %v", u)
|
||||
}
|
||||
if u := l.UngatedPlatforms(); len(u) > 0 {
|
||||
t.Errorf("perronger som nås utan att passera en spärr: %v – spärrarna måste vara flaskhalsar", u)
|
||||
}
|
||||
for i, tr := range l.Trains {
|
||||
if tr.Track == "" {
|
||||
t.Errorf("tåg %d saknar spårnummer", i)
|
||||
}
|
||||
}
|
||||
for _, sg := range l.Signs {
|
||||
if sg.Text["sv"] == "" || sg.Text["en"] == "" {
|
||||
t.Errorf("skylt (%d,%d) saknar sv/en-text", sg.X, sg.Y)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -169,3 +188,80 @@ func TestListAndLoadFile(t *testing.T) {
|
||||
t.Fatal("saknad fil ska ge fel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduleStatus(t *testing.T) {
|
||||
s := Schedule{OffsetSec: 5, IntervalSec: 30, DwellSec: 10}
|
||||
cases := []struct {
|
||||
t float64
|
||||
at bool
|
||||
secs float64
|
||||
}{
|
||||
{0, false, 5}, {5, true, 10}, {14.9, true, 0.1}, {15, false, 20}, {34.9, false, 0.1}, {35, true, 10}, {70, true, 5}, {80, false, 15},
|
||||
}
|
||||
for _, c := range cases {
|
||||
at, secs := s.Status(c.t)
|
||||
if at != c.at || math.Abs(secs-c.secs) > 1e-9 {
|
||||
t.Errorf("t=%v: at=%v secs=%.2f, vill ha at=%v secs=%.2f", c.t, at, secs, c.at, c.secs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduleAndFareDefaults(t *testing.T) {
|
||||
l := mustParse(t, sample)
|
||||
sc := l.Trains[0].Schedule
|
||||
if sc.IntervalSec != DefaultIntervalSec || sc.DwellSec != DefaultDwellSec || sc.OffsetSec != 0 {
|
||||
t.Fatalf("standardtidtabell saknas: %+v", sc)
|
||||
}
|
||||
if l.FareYen != DefaultFareYen {
|
||||
t.Fatalf("standardavgift saknas: %d", l.FareYen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignAt(t *testing.T) {
|
||||
src := strings.Replace(strings.Replace(sample, `"#S..#P#"`, `"#Si.#P#"`, 1),
|
||||
`"trains"`, `"signs": [{"x": 2, "y": 1, "text": {"sv": "Hej"}}], "trains"`, 1)
|
||||
l := mustParse(t, src)
|
||||
if sg := l.SignAt(Point{2, 1}); sg == nil || sg.Text.Get("sv") != "Hej" {
|
||||
t.Fatalf("SignAt = %v", sg)
|
||||
}
|
||||
if l.SignAt(Point{1, 1}) != nil {
|
||||
t.Fatal("ingen skylt på startrutan")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPath(t *testing.T) {
|
||||
l := mustParse(t, sample)
|
||||
path := l.Path(l.SpawnPoint(), Point{5, 1}, l.CanEnter)
|
||||
// S(1,1) -> (1,2) -> (1,3) -> (2,3) -> (3,3) -> (4,3) -> (5,3) -> (5,2) -> (5,1)
|
||||
if len(path) != 9 || path[0] != (Point{1, 1}) || path[len(path)-1] != (Point{5, 1}) {
|
||||
t.Fatalf("path = %v", path)
|
||||
}
|
||||
for i := 1; i < len(path); i++ {
|
||||
dx, dy := path[i].X-path[i-1].X, path[i].Y-path[i-1].Y
|
||||
if dx*dx+dy*dy != 1 {
|
||||
t.Fatalf("steg %d är inte en grannruta: %v -> %v", i, path[i-1], path[i])
|
||||
}
|
||||
}
|
||||
if l.Path(l.SpawnPoint(), Point{0, 0}, l.CanEnter) != nil {
|
||||
t.Fatal("väg in i en vägg ska vara nil")
|
||||
}
|
||||
if p := l.Path(l.SpawnPoint(), l.SpawnPoint(), l.CanEnter); len(p) != 1 {
|
||||
t.Fatalf("väg till sig själv = %v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUngatedPlatforms(t *testing.T) {
|
||||
// Perrong utan spärr framför -> rapporteras.
|
||||
l := mustParse(t, sample)
|
||||
if u := l.UngatedPlatforms(); len(u) != 1 || u[0] != (Point{5, 1}) {
|
||||
t.Fatalf("UngatedPlatforms = %v, vill ha [(5,1)]", u)
|
||||
}
|
||||
// Spärr i enda passagen -> inget rapporteras, men banan är fortfarande lösbar.
|
||||
l = mustParse(t, strings.Replace(sample, `"#.#...#"`, `"#.#.G.#"`, 1))
|
||||
if u := l.UngatedPlatforms(); len(u) != 0 {
|
||||
t.Fatalf("UngatedPlatforms = %v, vill ha inga", u)
|
||||
}
|
||||
if !l.Solvable() {
|
||||
t.Fatal("banan ska vara lösbar med spärren passerbar")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user