diff --git a/doc/img/cars-alight.png b/doc/img/cars-alight.png index 17eaae8..fd958d0 100644 Binary files a/doc/img/cars-alight.png and b/doc/img/cars-alight.png differ diff --git a/doc/img/cars.png b/doc/img/cars.png index 6b5deb1..4ebc881 100644 Binary files a/doc/img/cars.png and b/doc/img/cars.png differ diff --git a/doc/img/crowd.png b/doc/img/crowd.png index 40a0862..01764ed 100644 Binary files a/doc/img/crowd.png and b/doc/img/crowd.png differ diff --git a/doc/img/floors-1f.png b/doc/img/floors-1f.png index 9d68c3f..9cb7291 100644 Binary files a/doc/img/floors-1f.png and b/doc/img/floors-1f.png differ diff --git a/doc/img/level-002.png b/doc/img/level-002.png index be6719f..d543ae3 100644 Binary files a/doc/img/level-002.png and b/doc/img/level-002.png differ diff --git a/doc/img/map-signs.png b/doc/img/map-signs.png index b9aa000..04aade5 100644 Binary files a/doc/img/map-signs.png and b/doc/img/map-signs.png differ diff --git a/doc/img/map.png b/doc/img/map.png index 528d5b7..0828339 100644 Binary files a/doc/img/map.png and b/doc/img/map.png differ diff --git a/doc/img/nav-list.png b/doc/img/nav-list.png index 18508d5..8d52e3e 100644 Binary files a/doc/img/nav-list.png and b/doc/img/nav-list.png differ diff --git a/doc/img/no-exit.png b/doc/img/no-exit.png index 6bea212..bc7c2c8 100644 Binary files a/doc/img/no-exit.png and b/doc/img/no-exit.png differ diff --git a/doc/img/payer.png b/doc/img/payer.png index 2910cd3..2b8eca2 100644 Binary files a/doc/img/payer.png and b/doc/img/payer.png differ diff --git a/doc/img/ride-real.png b/doc/img/ride-real.png index f9f9b64..3634f33 100644 Binary files a/doc/img/ride-real.png and b/doc/img/ride-real.png differ diff --git a/doc/img/staff.png b/doc/img/staff.png index 8e5ee4d..c6950dd 100644 Binary files a/doc/img/staff.png and b/doc/img/staff.png differ diff --git a/doc/img/terminal.png b/doc/img/terminal.png index 4a5ff0d..0b5b0e6 100644 Binary files a/doc/img/terminal.png and b/doc/img/terminal.png differ diff --git a/doc/img/ticket.png b/doc/img/ticket.png index c2fae58..223c34c 100644 Binary files a/doc/img/ticket.png and b/doc/img/ticket.png differ diff --git a/doc/img/too-far.png b/doc/img/too-far.png index 17a48d5..e0be13a 100644 Binary files a/doc/img/too-far.png and b/doc/img/too-far.png differ diff --git a/doc/img/transfer.png b/doc/img/transfer.png index 78561a7..209f872 100644 Binary files a/doc/img/transfer.png and b/doc/img/transfer.png differ diff --git a/doc/mal-och-tester.md b/doc/mal-och-tester.md index 80d48d5..fbb328e 100644 --- a/doc/mal-och-tester.md +++ b/doc/mal-och-tester.md @@ -135,6 +135,8 @@ att bygga på Linux som på Windows. | `internal/minigame` | `TestPayerPicksSourceAndRefusesPoor` | **M10c:** markören på första som har råd, nekande utan avslut, avbrott utan köp | | `internal/sim` | `TestKioskAsksWhoPays`, `TestOneWayGatesNeedStaff` | **M10c:** kiosken frågar vem som betalar med egna kort, vald följares kort dras, kontant fungerar, avbrott drar inget; avgift vid inpassering i betalt område, spärren släpper inte ut incheckad, personal låser upp efter 10 s, ny inpassering kostar igen | | `internal/level` | `TestOneWayGatesAndInside` | **M10c:** `oneWayGates` läses, `InsideGates` = rutorna innanför spärrarna | +| `internal/level` | `TestPathOutsideAvoidsOtherPaidAreas` + regel i `TestAllShippedLevels` | **M10d:** vägar som undviker andra perrongers betalda områden; varje perrong nås utan att gå genom ett annat betalt område | +| `internal/level` | `TestToiletSides` | **M10d:** 005 och 008b har toaletter bara innanför, 007b på båda sidor, 007 och 008 bara utanför; innanför bara på banor med spärrar utan återvändo | | `internal/sim` | `TestJourneyWithTransfer`, `TestJourneyWrongTrainOnSecondLeg`, `TestChainLevelsLoad` | **M10:** bytet fortsätter omgången på nästa station med klocka, plånbok, sällskap och par-summa; vinst först på sista delbanan; fel håll på bytesstationen förlorar; delbanor laddas enskilt | | `internal/minigame` | `TestCarPickAlightingAndChoice`, `TestCrowdLevelNarrowsZone`, `TestRideCrowdLetPass` | Folk av först (för tidigt räknas), seedad trängsel med minst en lugn vagn, val; trängsel smalnar zonen; resan: kortare tid per station, passagerare som vill förbi, blockerade räknas | | `internal/sim` | `TestNPCsWanderStayOnFloorAndBlockPlayer`, `TestAlightingNPCsSpawnAndLeave` | Folk rör sig på golv, seedat, blockerar spelaren; avstigande spawnar när dörrarna öppnas och lämnar via utgången | @@ -159,6 +161,7 @@ att bygga på Linux som på Windows. | ~~M9 Navigeringslägen~~ ✓ | Nätets bedömningar (rätt/fel håll/för långt/ingen trafik), 1–4 rätta tåg, riktiga stationer på resan, fel-håll- och för-långt-förlust, skyltbonus, terminal – finns, se tabellen ovan | | ~~M10 Story-innehåll~~ ✓ | Kedjorna valideras, byten hänger ihop, storyn börjar och slutar på flygplatsen, bytet för med sig tid/plånbok/sällskap – finns, se tabellen ovan; `tools/levelcheck` som manuell kontroll | | ~~M10c Vem betalar, spärrar utan återvändo~~ ✓ | Väljaren betalar från rätt källa och nekar den utan pengar; spärren håller kvar, personalen låser upp, ny inpassering kostar – finns, se tabellen ovan | +| ~~M10d Toaletter innanför/utanför~~ ✓ | Toaletterna ligger på rätt sida av spärrarna per bana – finns, se tabellen ovan | | M10b Bussar och vägar | Bussar följer tidtabell och vägrutor; hållplatser bedöms som perronger; överstimulerande UI-reglaget påverkar bara presentationen | | M11 Oändligt läge & NG+ | Målsekvens deterministisk per seed; NG+ behåller skills men nollställer items | | M12 Ljud & polish | Alla ljudfiler som refereras finns och går att avkoda; Windows-binär startar (manuellt) | diff --git a/doc/roadmap.md b/doc/roadmap.md index 22fad79..67eeb19 100644 --- a/doc/roadmap.md +++ b/doc/roadmap.md @@ -612,6 +612,40 @@ på **brasse-linux01-runnern** (label `linux-amd64`), aldrig på Pi5:n. - **Kvar till senare:** ett kontor (egen ruta) som alternativ till knappen vid spärren; personal som också kan hjälpa med tomt kort. +### M10d – Toaletter innanför och utanför spärrarna ✓ (klar 2026-10-24) + +- **Mål:** Björns tillägg 2026-10-24: toaletter finns ibland bara + innanför spärrarna, ibland bara utanför, ibland på båda sidor – så att + man måste tänka på *när* sällskapet går på toa, särskilt där spärren inte + släpper ut en (M10c). +- **Teknik:** generatorn får ett **betalt område** (`paidDepth`: hur många + steg från perrongen spärrarna sätts) och `toilets: inside|outside|both` + som fäster en toalettnisch i en vägg innanför spärrarna och/eller lägger + en i en återvändsgränd utanför. `tools/levelcheck` rapporterar var + toaletterna ligger. Banorna 005 Ueno (bara innanför), 007b Ueno (båda) + och 008b Nippori (bara innanför) görs om; 007 och 008 har bara utanför. +- **Så blev det:** generatorn räknar ett betalt område med BFS från + perrongerna (`paidDepth` steg) och sätter spärrar på alla öppna rutor + precis utanför; `toilets: inside` fäster en toalettnisch i en vägg intill + en betald ruta (där rutan bortom också är vägg, så inget nytt öppnas), + `both` lägger dessutom en i en återvändsgränd utanför, automat och kiosk + ligger alltid utanför. 005 Ueno (betalt område 12 rutor, toalett bara + innanför), 007b Ueno (22 rutor, båda) och 008b Nippori (16 rutor, bara + innanför) är omgjorda; 007 och 008 har bara utanför. `tools/levelcheck` + rapporterar betalt område och toaletter per sida. **Perrongerna är nu + återvändsgränder:** generatorn behåller bara passagen mot starten in till + perrongens cell (rum i den breda labyrinten), murar igen de andra och + reparerar labyrinten med nya passager där den annars delas; det betalda + området krymper automatiskt om det skulle bli en genomfart. Regeln "varje + perrong nås utan att gå genom en annan perrongs betalda område" testas + (`level.PathOutside`, i `TestAllShippedLevels` och levelcheck) – annars + skulle spärrar utan återvändo göra fällperrongerna onåbara. Alla + genererade banor utom 003 (som redan klarade regeln) gjordes om. +- **Klar när:** banorna har toaletter på rätt sida (`level.TestToiletSides`), + spärrtestet fungerar med ett större betalt område + (`sim.TestOneWayGatesNeedStaff` går vägen till första spärren i stället + för ett fast steg), och levelcheck är grön. ✓ + ### M11 – Oändligt läge och New Game+ - Slumpade mål från seed, poäng per mål, egen poänglista. NG+ behåller skills. diff --git a/doc/spelbeskrivning.md b/doc/spelbeskrivning.md index 2ca0015..7aabb27 100644 --- a/doc/spelbeskrivning.md +++ b/doc/spelbeskrivning.md @@ -122,6 +122,10 @@ otydligt står tolkningen i [§15](#15-tolkningar-av-originalet). - Folk måste gå på toa, köpa dricka och kan bli distraherade. - **Ju fler i lämmeltåget, desto svårare** blir det och desto oftare händer specialhändelser (dricka, mat, toa m.m.). +- **Toaletter innanför och utanför** (tillägg 2026-10-24): toaletter kan + ibland finnas **bara innanför** spärrarna, ibland **bara utanför** och + ibland **på båda sidor**. Tillsammans med spärrar utan återvändo (§8) + avgör det om man bör gå på toa före eller efter spärren. ## 6. Japanska, skyltar och hjälp diff --git a/internal/level/level.go b/internal/level/level.go index 93f3f64..289f741 100644 --- a/internal/level/level.go +++ b/internal/level/level.go @@ -645,6 +645,32 @@ func (l *Level) InsideGates() map[Point]bool { return seen } +// PathOutside är Path som inte går genom betalda områden (innanför +// spärrarna) utom dem som start eller mål ligger i – så att en bot inte +// checkar in vid fel perrong på vägen (spärrar utan återvändo). +func (l *Level) PathOutside(from, to Point, canEnter func(Point) bool) []Point { + inside := l.InsideGates() + allowed := map[Point]bool{} + for _, seed := range []Point{from, to} { + if !inside[seed] || allowed[seed] { + continue + } + allowed[seed] = true + queue := []Point{seed} + for len(queue) > 0 { + p := queue[0] + queue = queue[1:] + for _, n := range l.Neighbors(p) { + if inside[n] && !allowed[n] { + allowed[n] = true + queue = append(queue, n) + } + } + } + } + return l.Path(from, to, func(p Point) bool { return canEnter(p) && (!inside[p] || allowed[p]) }) +} + // UngatedPlatforms listar perronger som går att nå från start utan att // passera någon spärr. Speldesignen kräver att alla perronger ligger // innanför spärrar, så listan ska vara tom för skeppade banor. diff --git a/internal/level/level_test.go b/internal/level/level_test.go index 2e10387..aa1db37 100644 --- a/internal/level/level_test.go +++ b/internal/level/level_test.go @@ -138,6 +138,12 @@ func TestAllShippedLevels(t *testing.T) { 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 _, tr := range l.Trains { // varje perrong ska nås utan att gå genom en annan perrongs betalda område + pt := l.Find(Tile(tr.Platform[0]))[0] + if l.PathOutside(l.SpawnPoint(), pt, l.CanEnter) == nil { + t.Errorf("perrong %s nås bara genom ett annat betalt område", tr.Platform) + } + } for i, tr := range l.Trains { if tr.Track == "" { t.Errorf("tåg %d saknar spårnummer", i) @@ -733,6 +739,43 @@ func TestStoryChains(t *testing.T) { } } +func TestPathOutsideAvoidsOtherPaidAreas(t *testing.T) { + // Två perronger med betalda områden; den raka vägen till Q går genom + // P:s område, PathOutside tar omvägen via korridoren ovanför. + src := `{"id":"po","name":{"sv":"x"},"difficulty":1,"destination":{"sv":"x"},"oneWayGates":true, + "grid":["###########","#S........#","#.#######.#","#G#######G#","#.G.P.G.Q.#","###########"], + "trains":[{"platform":"P","track":"1","line":{"en":"A"},"destination":{"en":"B"},"correct":true}, + {"platform":"Q","track":"2","line":{"en":"A"},"destination":{"en":"C"}}]}` + l, err := Parse([]byte(src)) + if err != nil { + t.Fatal(err) + } + q := l.Find(Platform2)[0] + direct := l.Path(l.SpawnPoint(), q, l.CanEnter) + outside := l.PathOutside(l.SpawnPoint(), q, l.CanEnter) + if direct == nil || outside == nil { + t.Fatal("vägar saknas") + } + p := l.Find(Platform)[0] + pRegion := map[Point]bool{{X: 3, Y: 4}: true, p: true, {X: 5, Y: 4}: true} + through := false + for _, pt := range direct { + through = through || pRegion[pt] + } + if !through { + t.Fatal("den raka vägen borde gå genom P:s område – annars testar vi inget") + } + for _, pt := range outside { + if pRegion[pt] { + t.Fatalf("PathOutside går genom P:s område vid %v", pt) + } + } + // Från P (inne) och ut till starten: det egna området får användas. + if back := l.PathOutside(p, l.SpawnPoint(), l.CanEnter); back == nil { + t.Fatal("vägen ut från P ska finnas") + } +} + func TestOneWayGatesAndInside(t *testing.T) { src := `{"id":"ow","name":{"sv":"x"},"difficulty":1,"destination":{"sv":"x"},"oneWayGates":true, "grid":["#########","#S.G.P.G#","#########"], @@ -758,3 +801,40 @@ func TestOneWayGatesAndInside(t *testing.T) { t.Fatal("spärren och starten är inte innanför") } } + +// TestToiletSides: toaletterna ligger på den sida av spärrarna som banan +// är gjord för – bara innanför, bara utanför eller båda. +func TestToiletSides(t *testing.T) { + dir, err := res.Path("data", "levels") + if err != nil { + t.Fatal(err) + } + want := map[string][2]bool{ // id -> {innanför, utanför} + "005-ueno": {true, false}, + "007-akihabara": {false, true}, + "007b-ueno": {true, true}, + "008-tokyo-hemresa": {false, true}, + "008b-nippori": {true, false}, + } + for id, w := range want { + l, err := LoadFile(filepath.Join(dir, id+".json")) + if err != nil { + t.Fatal(err) + } + inside := l.InsideGates() + in, out := false, false + for _, p := range l.Find(Toilet) { + if inside[p] { + in = true + } else { + out = true + } + } + if in != w[0] || out != w[1] { + t.Errorf("%s: toaletter innanför=%v utanför=%v, vill ha %v/%v", id, in, out, w[0], w[1]) + } + if in && !l.OneWayGates { + t.Errorf("%s: toaletter innanför är bara intressant med spärrar utan återvändo", id) + } + } +} diff --git a/internal/sim/gates_test.go b/internal/sim/gates_test.go index b75bcd7..a6567be 100644 --- a/internal/sim/gates_test.go +++ b/internal/sim/gates_test.go @@ -11,7 +11,7 @@ import ( // pathTo är testbotens väg till goal: som PathCanEnter, men andra perronger // används inte som genvägar (på spärrar utan återvändo skulle man fastna). func pathTo(r *Run, goal level.Point) []level.Point { - return r.Level.Path(r.Player.Tile, goal, func(p level.Point) bool { + return r.Level.PathOutside(r.Player.Tile, goal, func(p level.Point) bool { return r.PathCanEnter(p) && (!r.Level.At(p).IsPlatform() || p == goal) }) } @@ -100,12 +100,22 @@ func TestOneWayGatesNeedStaff(t *testing.T) { t.Fatalf("på perrongen ska man vara incheckad och ha betalat en gång: checkedIn=%v passes=%d card=%d", r.CheckedIn(), r.GatePasses, r.Wallet.Card) } card := r.Wallet.Card - stepUntilStill(r, Left) // tillbaka på spärren: kostar inget + // Tillbaka mot utgången: vägen till starten, kapad vid första spärren. + back := l.PathOutside(plat, l.SpawnPoint(), r.PathCanEnter) + gi := 0 + for gi < len(back) && l.At(back[gi]) != level.Gate { + gi++ + } + if gi >= len(back)-1 { + t.Fatal("ingen spärr på vägen ut") + } + walk(t, r, back[:gi+1]) // inifrån och ut på spärren: kostar inget if !r.inGate() || r.GatePasses != 1 || r.Wallet.Card != card { t.Fatalf("spärren igen: inGate=%v passes=%d card=%d", r.inGate(), r.GatePasses, r.Wallet.Card) } - gate := r.Player.Tile - stepUntilStill(r, Left) // ut? nej + gate, outside := r.Player.Tile, back[gi+1] + out := dirTo(gate, outside) + stepUntilStill(r, out) // ut? nej if r.Player.Tile != gate || r.NoExitAt < 0 { t.Fatalf("spärren ska inte släppa ut: tile=%v noExitAt=%.1f", r.Player.Tile, r.NoExitAt) } @@ -119,15 +129,15 @@ func TestOneWayGatesNeedStaff(t *testing.T) { if r.Staff.Active || !r.StaffUnlocked || r.StaffCalls != 1 || r.FlashKey != FlashStaff { t.Fatalf("personalen ska ha låst upp: active=%v unlocked=%v calls=%d", r.Staff.Active, r.StaffUnlocked, r.StaffCalls) } - stepUntilStill(r, Left) - if r.Player.Tile == gate || r.CheckedIn() || r.StaffUnlocked { + stepUntilStill(r, out) + if r.Player.Tile != outside || r.CheckedIn() || r.StaffUnlocked { t.Fatalf("ute igen: tile=%v checkedIn=%v unlocked=%v", r.Player.Tile, r.CheckedIn(), r.StaffUnlocked) } - stepUntilStill(r, Right) // på spärren utifrån: inget dras än + stepUntilStill(r, dirTo(outside, gate)) // på spärren utifrån: inget dras än if !r.inGate() || r.CheckedIn() || r.GatePasses != 1 { t.Fatalf("spärren utifrån: inGate=%v checkedIn=%v passes=%d", r.inGate(), r.CheckedIn(), r.GatePasses) } - stepUntilStill(r, Right) // in igen: kostar igen + walk(t, r, pathTo(r, plat)) // in igen: kostar igen if r.Player.Tile != plat || !r.CheckedIn() || r.GatePasses != 2 || r.Wallet.Card != card-l.FareYen { t.Fatalf("ny inpassering: tile=%v checkedIn=%v passes=%d card=%d (var %d)", r.Player.Tile, r.CheckedIn(), r.GatePasses, r.Wallet.Card, card) } diff --git a/resources/data/levels/002-shinjuku.json b/resources/data/levels/002-shinjuku.json index 1da786e..86fd3c7 100644 --- a/resources/data/levels/002-shinjuku.json +++ b/resources/data/levels/002-shinjuku.json @@ -1,40 +1,19 @@ { - "id": "002-shinjuku", - "kind": "main", - "order": 2, - "name": { - "en": "Shinjuku Station", - "ja": "新宿駅", - "sv": "Shinjuku station" - }, - "difficulty": 2, - "party": 3, - "ticketRequired": true, - "seatSelection": true, - "rideStops": 4, "destination": { "en": "Shibuya", "ja": "渋谷", "romaji": "Shibuya", "sv": "Shibuya" }, - "timeLimitSec": 420, - "maxMissedTrains": 2, + "difficulty": 2, "fareYen": 200, - "shop": [ - "cap_red", - "water_bottle", - "mask_surgical", - "map_shinjuku", - "sneakers" - ], "grid": [ "#################################", "#S#..i..#K#...........#.........#", "#.#.#.#.#.#.#######.#.#########.#", "#.#W#.#.#.#.#.....#.#...........#", "#.###.#.#.#.#####.#.###########.#", - "#.#...#.#..i......#.#.......#...#", + "#.#...#.#.........#.#.......#...#", "#.#.###.###########.#.#.###.#.###", "#.....#.......#...#.#.#.........#", "#####.#######.#.#.#.#.#########.#", @@ -46,11 +25,66 @@ "#.#.###.###.#.###.#.#.#######.#.#", "#...#T#.#..i#.#.#...#.....#...#.#", "#####.#.#.#.#.#.#####.#####.###.#", - "#.....#.#.#.#.......#.......#...#", + "#.....#.#.#.#..i....#.......#...#", "#G#.#.#.#.#########.#########.#G#", - "#pG.#...#.....................#P#", + "#p#.#...#.....................#P#", "#################################" ], + "id": "002-shinjuku", + "kind": "main", + "maxMissedTrains": 2, + "name": { + "en": "Shinjuku Station", + "ja": "新宿駅", + "sv": "Shinjuku station" + }, + "order": 2, + "party": 3, + "rideStops": 4, + "seatSelection": true, + "shop": [ + "cap_red", + "water_bottle", + "mask_surgical", + "map_shinjuku", + "sneakers" + ], + "signs": [ + { + "text": { + "en": "Shinjuku Station. Track 14 Yamanote Line for Shibuya: far bottom right. Track 8 Chuo Line for Tokyo: far bottom left.", + "ja": "新宿駅 14番線 山手線 渋谷方面 → 南東 8番線 中央線 東京方面 → 南西", + "sv": "Shinjuku station. Spår 14 Yamanote-linjen mot Shibuya: längst ner till höger. Spår 8 Chuo-linjen mot Tokyo: längst ner till vänster." + }, + "x": 5, + "y": 1, + "z": 0 + }, + { + "text": { + "en": "\u003c- Track 8 Chuo Line (Tokyo) Track 14 Yamanote Line (Shibuya) -\u003e", + "ja": "← 8番線 中央線 東京方面 14番線 山手線 渋谷方面 →", + "sv": "\u003c- Spår 8 Chuo-linjen (Tokyo) Spår 14 Yamanote-linjen (Shibuya) -\u003e" + }, + "x": 11, + "y": 15, + "z": 0 + }, + { + "text": { + "en": "Track 14 Yamanote Line for Shibuya: the gate in the south-east corner.", + "ja": "14番線 山手線 渋谷方面 → 南東口 改札", + "sv": "Spår 14 Yamanote-linjen mot Shibuya: spärren i sydöstra hörnet." + }, + "x": 15, + "y": 17, + "z": 0 + } + ], + "station": "shinjuku", + "ticketRequired": true, + "timeLimitSec": 420, + "to": "shibuya", "trains": [ { "crowded": true, @@ -64,15 +98,15 @@ "ja": "山手線", "romaji": "Yamanote-sen" }, + "lineId": "yamanote", "platform": "P", "schedule": { "dwellSec": 8, "intervalSec": 20, "offsetSec": 3 }, - "track": "14", - "lineId": "yamanote", - "terminus": "shibuya" + "terminus": "shibuya", + "track": "14" }, { "destination": { @@ -85,46 +119,15 @@ "ja": "中央線", "romaji": "Chuo-sen" }, + "lineId": "chuo", "platform": "p", "schedule": { "dwellSec": 8, "intervalSec": 25, "offsetSec": 10 }, - "track": "8", - "lineId": "chuo", - "terminus": "tokyo" + "terminus": "tokyo", + "track": "8" } - ], - "signs": [ - { - "text": { - "en": "Shinjuku Station. Track 14 Yamanote Line for Shibuya: far bottom right. Track 8 Chuo Line for Tokyo: far bottom left.", - "ja": "新宿駅 14番線 山手線 渋谷方面 → 南東 8番線 中央線 東京方面 → 南西", - "sv": "Shinjuku station. Spår 14 Yamanote-linjen mot Shibuya: längst ner till höger. Spår 8 Chuo-linjen mot Tokyo: längst ner till vänster." - }, - "x": 5, - "y": 1 - }, - { - "text": { - "en": "<- Track 8 Chuo Line (Tokyo) Track 14 Yamanote Line (Shibuya) ->", - "ja": "← 8番線 中央線 東京方面 14番線 山手線 渋谷方面 →", - "sv": "<- Spår 8 Chuo-linjen (Tokyo) Spår 14 Yamanote-linjen (Shibuya) ->" - }, - "x": 11, - "y": 15 - }, - { - "text": { - "en": "Track 14 Yamanote Line for Shibuya: the gate in the south-east corner.", - "ja": "14番線 山手線 渋谷方面 → 南東口 改札", - "sv": "Spår 14 Yamanote-linjen mot Shibuya: spärren i sydöstra hörnet." - }, - "x": 11, - "y": 5 - } - ], - "station": "shinjuku", - "to": "shibuya" -} + ] +} \ No newline at end of file diff --git a/resources/data/levels/002b-kabukicho.json b/resources/data/levels/002b-kabukicho.json index ac19d38..1fd55ab 100644 --- a/resources/data/levels/002b-kabukicho.json +++ b/resources/data/levels/002b-kabukicho.json @@ -9,25 +9,25 @@ "fareYen": 200, "grid": [ "#################################", - "#S....#....K#.....#.............#", + "#S..........#.....#.............#", "#.###.#.#####.###.#.#.#########.#", - "#...#.#.........#.#.#.....#.....#", + "#...#.#......i.W#.#.#.....#K....#", "#.###.###########.#.#####.#######", - "#...#.#.....#...#.#.....#.#.....#", + "#...#.#.....#...#.#i...T#.#.....#", "#.#.#.#.###.#.#.#.#.#####.#.###.#", - "#.#...#.#T....#.#.#.#...#.....#.#", + "#.#...#.#.....#.#.#.#...#..i..#.#", "#.#.###.#######.#.#.#.#.#####.#.#", - "#.#....i......#.#...#.#.....#.#.#", + "#.#...........#.#...#.#.....#.#.#", "#.#####.#####.#.#####.#####.#.#.#", - "#.....#....W#.#.....#.#...#.#.#.#", + "#.....#.....#.#.....#.#...#.#.#.#", "#####.###.###.#####.#.#.#.#.###.#", "#...#.#...#...#...#.#.#.#...#...#", "#.#.#.###.#.###.#.#.#.#.#####.###", "#.#.#...#.#.#.....#.#.#...#.....#", "#.#.###.#.#.#.#####.#.###.#.#.#.#", - "#.#i..#.#.#.#.#...#...#...#...#.#", - "#G#.#.#.###.#.#.#.#####.#######G#", - "#pG.#.......#...#......i......GP#", + "#.#...#.#.#.#.#...#...#...#...#.#", + "###.#.#.###.#.#.#.#####.#########", + "#pG.#.......#...#.............GP#", "#################################" ], "id": "002b-kabukicho", @@ -54,8 +54,9 @@ "ja": "歌舞伎町 1番線 山手線 新宿方面 → 南東 3番線 中央線 東京方面", "sv": "Sidospår: Kabukicho. Tillbaka till Shinjuku med Yamanote-linjen, spår 1 i sydost. Chuo-linjen går åt fel håll." }, - "x": 7, - "y": 9 + "x": 13, + "y": 3, + "z": 0 }, { "text": { @@ -63,8 +64,9 @@ "ja": "← 3番線 中央線 東京方面 1番線 山手線 新宿方面 →", "sv": "\u003c- Spår 3 Chuo-linjen (Tokyo) Spår 1 Yamanote-linjen (Shinjuku) -\u003e" }, - "x": 3, - "y": 17 + "x": 19, + "y": 5, + "z": 0 }, { "text": { @@ -72,8 +74,9 @@ "ja": "1番線 山手線 新宿方面 → 南東口 改札", "sv": "Spår 1 Yamanote-linjen: spärren i sydöstra hörnet." }, - "x": 23, - "y": 19 + "x": 27, + "y": 7, + "z": 0 } ], "timeLimitSec": 240, diff --git a/resources/data/levels/004-tokyo.json b/resources/data/levels/004-tokyo.json index 7618707..d697fb0 100644 --- a/resources/data/levels/004-tokyo.json +++ b/resources/data/levels/004-tokyo.json @@ -54,7 +54,7 @@ "#...#.....#.........#.#...#.....#", "###.#.#.###.###########.#.#.###.#", "#...#.#...#.....#.......#.#...#.#", - "#G###.###.#####.#.#.#####.###.#G#", + "#####.###.#####.#.#.#####.###.#G#", "#pG...D.#...........#.........#P#", "#################################" ], @@ -92,9 +92,9 @@ }, { "text": { - "en": "<- Track 7 Tokaido Line (Shinagawa) Track 4 Yamanote Line (Ueno) ->", + "en": "\u003c- Track 7 Tokaido Line (Shinagawa) Track 4 Yamanote Line (Ueno) -\u003e", "ja": "← 7番線 東海道線 品川方面 4番線 山手線 上野方面 →", - "sv": "<- Spår 7 Tokaido-linjen (Shinagawa) Spår 4 Yamanote-linjen (Ueno) ->" + "sv": "\u003c- Spår 7 Tokaido-linjen (Shinagawa) Spår 4 Yamanote-linjen (Ueno) -\u003e" }, "x": 3, "y": 3, @@ -111,8 +111,10 @@ "z": 1 } ], + "station": "tokyo", "ticketRequired": false, "timeLimitSec": 420, + "to": "ueno", "trains": [ { "crowded": true, @@ -126,15 +128,15 @@ "ja": "山手線", "romaji": "Yamanote-sen" }, + "lineId": "yamanote", "platform": "P", "schedule": { "dwellSec": 8, "intervalSec": 24, "offsetSec": 5 }, - "track": "4", - "lineId": "yamanote", - "terminus": "ueno" + "terminus": "ueno", + "track": "4" }, { "destination": { @@ -147,17 +149,15 @@ "ja": "東海道線", "romaji": "Tokaido-sen" }, + "lineId": "tokaido", "platform": "p", "schedule": { "dwellSec": 8, "intervalSec": 28, "offsetSec": 11 }, - "track": "7", - "lineId": "tokaido", - "terminus": "shinagawa" + "terminus": "shinagawa", + "track": "7" } - ], - "station": "tokyo", - "to": "ueno" -} + ] +} \ No newline at end of file diff --git a/resources/data/levels/005-ueno.json b/resources/data/levels/005-ueno.json index caf975c..acc279e 100644 --- a/resources/data/levels/005-ueno.json +++ b/resources/data/levels/005-ueno.json @@ -11,23 +11,23 @@ "fareYen": 200, "grid": [ "#################################", - "#S.<..<..#..<..<..<..<..#..<..###", - "#..>..>..#..>..>..>..>..#..>..###", - "#######<>#<>#<>####<>#<>####<>###", - "#..<..<..#.W<..<..<..#..<..<..###", - "#.i>..>..#..>..>..>..#..>K.>..###", - "#<>#############<>#######<>#<>###", - "#..#..<..<..<.i<..<..<..#..<..###", - "#..#..>..>..>..>..>..>..#.T>i.###", - "#<>####<>##########<>#<>#<>######", - "#..<..<..<..#..<..<..#..#..<..###", - "#..>..>..>..#..>..>..#..#..>..###", - "####<>#<>#<>#<>####<>#######<>###", - "#..<..#..#..#..<..#..<..<..<..###", - "#..>..#..#..#..>..#..>..>..>..###", - "#<>#######<>####<>###############", - "#G.<..<..<..#..<..<..<..<..<.G###", - "#pG>..>..>..#..>..>..>..>..>GP###", + "#S.\u003c..\u003c..#..\u003c..\u003c..\u003cK.\u003c..#..\u003c..###", + "#..\u003e..\u003e..#..\u003e..\u003e..\u003e..\u003e..#.i\u003e..###", + "#######\u003c\u003e#\u003c\u003e#\u003c\u003e####\u003c\u003e#\u003c\u003e####\u003c\u003e###", + "#..\u003c..\u003cT.#..\u003c..\u003c..\u003c..#..\u003c..\u003c..###", + "#.i\u003e..\u003e..#..\u003e..\u003e..\u003e..#..\u003e..\u003e..###", + "#\u003c\u003e#############\u003c\u003e#######\u003c\u003e#\u003c\u003e###", + "#..#..\u003c..\u003c..\u003c..\u003c..\u003c..\u003c..#..\u003c..###", + "#..#..\u003e..\u003e..\u003e..\u003e..\u003e..\u003e..#..\u003e..###", + "#\u003c\u003e####\u003c\u003e##########\u003c\u003e#\u003c\u003e#\u003c\u003e######", + "#..\u003c..\u003c..\u003c..#..\u003c..\u003c..#..#..\u003c..###", + "#..\u003e..\u003e..\u003e..#..\u003e..\u003e..#..#..\u003e..###", + "####\u003c\u003e#\u003c\u003e#\u003c\u003e#\u003c\u003e####\u003c\u003e#######\u003c\u003e###", + "#..\u003c..#..#..#..\u003c..#..\u003c..\u003c..\u003c..###", + "#G.\u003e..#..#..#..\u003e..#..\u003e..\u003e..\u003e..###", + "#\u003cG#######\u003c\u003e####\u003c\u003e###############", + "#..#..\u003c..\u003c.i#..\u003c..\u003c..\u003c..\u003c..G..W##", + "#p.#..\u003e..\u003e..#..\u003e..\u003e..\u003e..\u003e.G\u003e.P###", "#################################", "#################################", "#################################" @@ -41,6 +41,7 @@ "sv": "Ueno station" }, "noise": 2, + "oneWayGates": true, "order": 5, "party": 3, "rideStops": 5, @@ -64,12 +65,12 @@ }, { "text": { - "en": "<- Track 9 Keihin-Tohoku Line (Omiya) Track 3 Yamanote Line (Akihabara) ->", + "en": "\u003c- Track 9 Keihin-Tohoku Line (Omiya) Track 3 Yamanote Line (Akihabara) -\u003e", "ja": "← 9番線 京浜東北線 大宮方面 3番線 山手線 秋葉原方面 →", - "sv": "<- Spår 9 Keihin-Tohoku-linjen (Omiya) Spår 3 Yamanote-linjen (Akihabara) ->" + "sv": "\u003c- Spår 9 Keihin-Tohoku-linjen (Omiya) Spår 3 Yamanote-linjen (Akihabara) -\u003e" }, - "x": 14, - "y": 7, + "x": 11, + "y": 16, "z": 0 }, { @@ -78,12 +79,14 @@ "ja": "3番線 1号車 女性専用車 ラッシュ 改札 → 南東口", "sv": "Spår 3: vagn 1 är kvinnovagn under rusningen (de första 2,5 minuterna). Köa vid markeringarna och låt folk kliva av först." }, - "x": 28, - "y": 8, + "x": 26, + "y": 2, "z": 0 } ], + "station": "ueno", "timeLimitSec": 420, + "to": "akihabara", "trains": [ { "cars": 4, @@ -97,18 +100,18 @@ "ja": "山手線", "romaji": "Yamanote-sen" }, + "lineId": "yamanote", "platform": "P", "schedule": { "dwellSec": 9, "intervalSec": 26, "offsetSec": 6 }, + "terminus": "tokyo", "track": "3", "womenOnlyCar": 1, "womenOnlyFrom": 0, - "womenOnlyTo": 150, - "lineId": "yamanote", - "terminus": "tokyo" + "womenOnlyTo": 150 }, { "cars": 3, @@ -122,18 +125,15 @@ "ja": "京浜東北線", "romaji": "Keihin-Tohoku-sen" }, + "lineId": "keihin-tohoku", "platform": "p", "schedule": { "dwellSec": 8, "intervalSec": 30, "offsetSec": 13 }, - "track": "9", - "lineId": "keihin-tohoku", - "terminus": "omiya" + "terminus": "omiya", + "track": "9" } - ], - "station": "ueno", - "to": "akihabara", - "oneWayGates": true -} + ] +} \ No newline at end of file diff --git a/resources/data/levels/006-ikebukuro.json b/resources/data/levels/006-ikebukuro.json index 84b8b72..1369ac5 100644 --- a/resources/data/levels/006-ikebukuro.json +++ b/resources/data/levels/006-ikebukuro.json @@ -22,14 +22,14 @@ "#...#.....#......i#...#.....#.#.#", "#.###############.###.#.#####.#.#", "#.#.........#..i..#...#.......#.#", - "#.#.#.#####.#.#.###.#.#####.###.#", + "#.#.#.#####.#.#.###.#.#.###.###.#", "#...#.....#...#.....#.....#.#...#", - "#.#######.#.###########.#.###.#.#", + "#.#######.#.#####.#####.#.###.#.#", "#.#.....#...#.....#.....#...#.#.#", - "#.#.###.#####.###.#.#######.#.###", + "#.#.#.#.#####.###.#.#######.#.###", "#...#.#...#...#...#...#...#.#...#", - "#####.###G#.###.#####.#.###.###G#", - "#pG.....GqG.#...GQG...#.......GP#", + "#####.###G#.###.#####.#.###.#####", + "#pG.....#q#.#...#QG...#.......GP#", "#################################" ], "id": "006-ikebukuro", diff --git a/resources/data/levels/007-akihabara.json b/resources/data/levels/007-akihabara.json index ba1e5f7..1216455 100644 --- a/resources/data/levels/007-akihabara.json +++ b/resources/data/levels/007-akihabara.json @@ -17,7 +17,7 @@ "#.#####.#.#.#.#.#.#.#.#.#.#.###.#", "#i..#.....#...#.#.#.#.#.#.#.#...#", "#.#.#.#########.#.#.#.#.#.#.#.###", - "#.#...#.......#.#...#..i#.#.#...#", + "#.#...#.......#.#...#...#.#.#...#", "#.#####.#####.#.#######.#.#.#####", "#.......#T#...#.......#.#K#.#...#", "#########.#.#####.###.#.###.#.#.#", @@ -27,9 +27,9 @@ "#.#.#####.#.###.#####.#######.#.#", "#.#.....#.#.#...#...#.........#.#", "#.#####.#.#.#.###.#.###.#######.#", - "#.......#.#.#.#...#...#.#.......#", - "#G###.###G#.#.#.#####.###.#####G#", - "#pG.....Gq#.....#QG.......#...GP#", + "#i......#.#.#.#...#...#.#.......#", + "#G###.#####.#.#.#####.###.#####G#", + "#p#.....Gq#.....#QG...........#P#", "#################################" ], "id": "007-akihabara", @@ -43,6 +43,7 @@ }, "next": "007b-ueno", "noise": 1, + "oneWayGates": true, "order": 7, "party": 4, "rideStops": 3, @@ -64,9 +65,9 @@ }, { "text": { - "en": "<- Track 3 Hibiya Line for Naka-Meguro Track 4 Hibiya Line for Kita-Senju ->", + "en": "\u003c- Track 3 Hibiya Line for Naka-Meguro Track 4 Hibiya Line for Kita-Senju -\u003e", "ja": "← 3番線 日比谷線 中目黒方面 4番線 日比谷線 北千住方面 →", - "sv": "<- Spår 3 Hibiya-linjen mot Naka-Meguro Spår 4 Hibiya-linjen mot Kita-Senju ->" + "sv": "\u003c- Spår 3 Hibiya-linjen mot Naka-Meguro Spår 4 Hibiya-linjen mot Kita-Senju -\u003e" }, "x": 9, "y": 11, @@ -78,8 +79,8 @@ "ja": "上野 → 乗り換え 銀座線 浅草方面", "sv": "Ueno: byte till Ginza-linjen mot Asakusa." }, - "x": 23, - "y": 7, + "x": 1, + "y": 17, "z": 0 } ], @@ -175,6 +176,5 @@ "terminus": "tokyo", "track": "1" } - ], - "oneWayGates": true -} + ] +} \ No newline at end of file diff --git a/resources/data/levels/007b-ueno.json b/resources/data/levels/007b-ueno.json index 9a8d269..5b98a97 100644 --- a/resources/data/levels/007b-ueno.json +++ b/resources/data/levels/007b-ueno.json @@ -11,24 +11,24 @@ "fareYen": 170, "grid": [ "#################################", - "#S.#..<..<..<..<..<..<..<..<..###", - "#..#..>..>..>..>..>..>..>..>..###", - "#<>################<>#######<>###", - "#..#....>..>..>W.#..#..>..#..###", - "#<>#<>##########<>#<>#<>####<>###", - "#..<..#..<..<..#..#..#..<..<..###", - "#..>..#..>..>..#..#..#..>..>..###", - "#######<>#<>#<>#<>#<>#<>#########", - "#..#..<..<..#..#..#..<..#..<..###", - "#..#..>..>..#..#..#..>..#..>..###", - "#<>#<>#######<>#<>#<>#<>####<>###", - "#..#K.<..<..#..#..<.i#.T<..<..###", - "#..#i.>..>..#..#..>..#..>..>..###", - "#<>#<>####<>#<>#<>##########<>###", - "#G.<..<..<..#....>..>..#..GQG>..>..>..>GP###", - "#################################", + "#S.#.i\u003c..\u003c..\u003c..\u003c..\u003c..\u003c..\u003c..\u003c..###", + "#..#..\u003e..\u003e..\u003e..\u003e..\u003e..\u003e..\u003e..\u003e..###", + "#\u003c\u003e################\u003c\u003e#######\u003c\u003e###", + "#..#..\u003c..\u003c..\u003c..\u003c..#..#..\u003c..#..###", + "#..#..\u003e..\u003e..\u003e..\u003e..#..#..\u003e..#..###", + "#\u003c\u003e#\u003c\u003e##########\u003c\u003e#\u003c\u003e#\u003c\u003e####\u003c\u003e###", + "#..\u003c..#..\u003c..\u003c..#..#..#..\u003c..\u003c..###", + "#..\u003e..#..\u003e..\u003e..#..#..#..\u003e..\u003e..###", + "####\u003c\u003e#\u003c\u003e#\u003c\u003e#\u003c\u003e#\u003c\u003e#\u003c\u003e#\u003c\u003e#########", + "#..\u003c..\u003ci.\u003cT.#..#..#..\u003c..#..\u003c..###", + "#..\u003e..\u003e.K\u003e..#..#..#..\u003e..#..\u003e..###", + "#\u003c\u003e#\u003c\u003e#######\u003c\u003e#\u003c\u003e#\u003c\u003e#\u003c\u003e####\u003c\u003e###", + "#..#..\u003c..\u003c..#..#..\u003c.i#..\u003c..\u003c..###", + "#..#..\u003e..\u003e..#..#..\u003e..#..\u003e..\u003e..###", + "####\u003c\u003e####\u003c\u003e#\u003c\u003e#G\u003e##########\u003c\u003e###", + "#.G\u003cW.\u003c..\u003c..#..#.G#..\u003c..\u003c..\u003c.G###", + "#p.G..\u003e..\u003e..#..#Q.#..\u003e..\u003e..\u003eGP###", + "##W##############################", "#################################", "#################################" ], @@ -41,6 +41,7 @@ "sv": "Ueno station" }, "noise": 2, + "oneWayGates": true, "party": 4, "rideStops": 3, "signs": [ @@ -51,14 +52,14 @@ "sv": "Ueno station. Spår 1 Ginza-linjen mot Asakusa: sydost. Spår 2 Ginza-linjen mot Shibuya: sydväst. Spår 3 Hibiya-linjen mot Kita-Senju: söderut." }, "x": 7, - "y": 4, + "y": 10, "z": 0 }, { "text": { - "en": "<- Track 2 Ginza Line for Shibuya Track 1 Ginza Line for Asakusa ->", + "en": "\u003c- Track 2 Ginza Line for Shibuya Track 1 Ginza Line for Asakusa -\u003e", "ja": "← 2番線 銀座線 渋谷方面 1番線 銀座線 浅草方面 →", - "sv": "<- Spår 2 Ginza-linjen mot Shibuya Spår 1 Ginza-linjen mot Asakusa ->" + "sv": "\u003c- Spår 2 Ginza-linjen mot Shibuya Spår 1 Ginza-linjen mot Asakusa -\u003e" }, "x": 20, "y": 13, @@ -70,8 +71,8 @@ "ja": "1番線 銀座線 浅草方面 → 南東口 改札", "sv": "Spår 1 Ginza-linjen mot Asakusa: spärren i sydöstra hörnet." }, - "x": 4, - "y": 14, + "x": 5, + "y": 1, "z": 0 } ], @@ -144,6 +145,5 @@ "terminus": "kita-senju", "track": "3" } - ], - "oneWayGates": true -} + ] +} \ No newline at end of file diff --git a/resources/data/levels/008-tokyo-hemresa.json b/resources/data/levels/008-tokyo-hemresa.json index 084e3df..aee5a07 100644 --- a/resources/data/levels/008-tokyo-hemresa.json +++ b/resources/data/levels/008-tokyo-hemresa.json @@ -55,9 +55,9 @@ "#.#######.#######.#.###.#.#.###.#", "#.....#..........i..#...#...#...#", "#####.###.###########.#######.###", - "#...#...#.#...........#.....#...#", - "#G#.###.###.###########.#######G#", - "#p#....iGqG.#...GQG...........GP#", + "#...#...#.#.................#...#", + "#G#.###.###.#.#########.#######G#", + "#p#....iGq#.#...#QG...........#P#", "#################################" ], "name": "1F" @@ -74,6 +74,7 @@ }, "next": "008b-nippori", "noise": 2, + "oneWayGates": true, "order": 8, "party": 4, "rewards": { @@ -99,9 +100,9 @@ }, { "text": { - "en": "<- Track 5 Yamanote Line for Shinagawa Track 4 Yamanote Line for Ueno ->", + "en": "\u003c- Track 5 Yamanote Line for Shinagawa Track 4 Yamanote Line for Ueno -\u003e", "ja": "← 5番線 山手線 品川方面 4番線 山手線 上野方面 →", - "sv": "<- Spår 5 Yamanote-linjen mot Shinagawa Spår 4 Yamanote-linjen mot Ueno ->" + "sv": "\u003c- Spår 5 Yamanote-linjen mot Shinagawa Spår 4 Yamanote-linjen mot Ueno -\u003e" }, "x": 7, "y": 19, @@ -210,6 +211,5 @@ "terminus": "takao", "track": "1" } - ], - "oneWayGates": true -} + ] +} \ No newline at end of file diff --git a/resources/data/levels/008b-nippori.json b/resources/data/levels/008b-nippori.json index 6794b52..b30fcb8 100644 --- a/resources/data/levels/008b-nippori.json +++ b/resources/data/levels/008b-nippori.json @@ -11,25 +11,25 @@ "fareYen": 500, "grid": [ "#################################", - "#S#W....#.....#.....#...#.......#", + "#S#.....#.....#.....#...#.......#", "#.#####.#.###.#.#.#.#.#.#.#.#.#.#", "#.....#.#...#...#.....#...#.#...#", "#####.#.###.###############.#####", - "#K#...#...#...#.......#...#.....#", + "#T#...#...#...#.......#...#.....#", "#.#.#####.###.#.#.###.#.#.#####.#", - "#.#.#...#.....#.#....i#.#...#...#", + "#.#.#...#.....#.#.....#.#...#...#", "#.#.#.#.###.###.#####.#.###.#.#.#", - "#.#...#...#...#.....#...#.#...#.#", - "#.#######.#######.#.#####.#####.#", - "#...#...#.#.....#.#.#.#.......#.#", + "#.#..i#...#...#.....#...#K#...#.#", + "#.###.###.#######.#.#####.#####.#", + "#...#...#.#.....#.#.#.#..i....#.#", "#.#.###.#.#.###.#.#.#.#.#####.#.#", - "#.#.....#...#T#i..#.#...#.....#.#", + "#.#.....#...#.#...#.....#.....#.#", "#.#.#########.#.###.#####.#####.#", - "#.#.#.....#..i..#...#.....#.....#", + "#.#.#.....#.....#...#.....#.....#", "#.###.#.#.#.#####.###.###.#.#####", - "#.....#.#.#.......#...#...#.....#", - "#G#####.#.#########.#.#########G#", - "#pG...#.........GQG.#.........GP#", + "#i....#.#.#.......#...#...#...G.#", + "#G#####.#.#########G#.########W.#", + "#p#.............#Q..#.........#P#", "#################################" ], "id": "008b-nippori", @@ -41,6 +41,7 @@ "sv": "Nippori station" }, "noise": 1, + "oneWayGates": true, "party": 4, "rideStops": 4, "seatSelection": true, @@ -51,18 +52,18 @@ "ja": "日暮里駅 1番線 京成本線 成田空港方面 → 南東 2番線 京成本線 京成上野方面 → 南西 11番線 山手線 上野方面 → 南", "sv": "Nippori station. Spår 1 Keisei-linjen mot Narita flygplats: sydost. Spår 2 Keisei-linjen mot Keisei-Ueno: sydväst. Spår 11 Yamanote-linjen mot Ueno: söderut. Biljett krävs." }, - "x": 15, - "y": 13, + "x": 5, + "y": 9, "z": 0 }, { "text": { - "en": "<- Track 2 Keisei Line for Keisei-Ueno Track 1 Keisei Line for Narita Airport ->", + "en": "\u003c- Track 2 Keisei Line for Keisei-Ueno Track 1 Keisei Line for Narita Airport -\u003e", "ja": "← 2番線 京成本線 京成上野方面 1番線 京成本線 成田空港方面 →", - "sv": "<- Spår 2 Keisei-linjen mot Keisei-Ueno Spår 1 Keisei-linjen mot Narita flygplats ->" + "sv": "\u003c- Spår 2 Keisei-linjen mot Keisei-Ueno Spår 1 Keisei-linjen mot Narita flygplats -\u003e" }, - "x": 13, - "y": 15, + "x": 1, + "y": 17, "z": 0 }, { @@ -71,8 +72,8 @@ "ja": "成田空港 → 1番線 切符 → 券売機", "sv": "Narita flygplats: spår 1. Biljetter i automaten." }, - "x": 21, - "y": 7, + "x": 25, + "y": 11, "z": 0 } ], @@ -146,6 +147,5 @@ "terminus": "ueno", "track": "11" } - ], - "oneWayGates": true -} + ] +} \ No newline at end of file diff --git a/tools/levelcheck/main.go b/tools/levelcheck/main.go index 7b4f88a..d85c054 100644 --- a/tools/levelcheck/main.go +++ b/tools/levelcheck/main.go @@ -87,6 +87,10 @@ func report(l *level.Level, all []*level.Level, out string) (problems int) { for _, tr := range l.Trains { pts := l.Find(level.Tile(tr.Platform[0])) dist := len(l.Path(l.SpawnPoint(), pts[0], l.CanEnter)) - 1 + if l.PathOutside(l.SpawnPoint(), pts[0], l.CanEnter) == nil { + fmt.Printf(" FEL: perrong %s nås bara genom ett annat betalt område\n", tr.Platform) + problems++ + } v := "correct=" + fmt.Sprint(tr.Correct) if tr.Verdict != nav.Unjudged { v = tr.Verdict.String() @@ -100,7 +104,16 @@ func report(l *level.Level, all []*level.Level, out string) (problems int) { } fmt.Printf(" resa: %s\n", strings.Join(ids, " → ")) } - fmt.Printf(" skyltar %d, utforskbart %d rutor\n", len(l.Signs), len(reach)) + inside := l.InsideGates() + tin, tout := 0, 0 + for _, p := range l.Find(level.Toilet) { + if inside[p] { + tin++ + } else { + tout++ + } + } + fmt.Printf(" skyltar %d, utforskbart %d rutor, betalt område %d rutor, toaletter innanför %d / utanför %d\n", len(l.Signs), len(reach), len(inside), tin, tout) for z := 0; z < l.NumFloors(); z++ { name := fmt.Sprintf("%s/%s-%d.png", out, l.ID, z) fail(savePNG(name, draw(l, z, reach))) diff --git a/tools/shots/main.go b/tools/shots/main.go index 5b51364..e63dd2d 100644 --- a/tools/shots/main.go +++ b/tools/shots/main.go @@ -134,6 +134,12 @@ func buildShots(levelsDir string) error { if err != nil { return err } + // Från perrongen i Ueno tillbaka till första spärren, och riktningen ut + // genom den (som spärren utan återvändo ska vägra). + toGateBack, pushOut, err := backToGate(l5, l5.CorrectPlatform()) + if err != nil { + return err + } // Skyltbilderna tas med några kända ord så att markeringen och den // delvisa översättningen syns; hjälpen lär sedan in nästa ord. known := []string{"-know", "改札,番線,方面", "-seed", "7"} @@ -159,14 +165,42 @@ func buildShots(levelsDir string) error { []string{"-seed", "7", "-mode", "list", "-know", "上野,番線,方面,乗り換え"}}, {"", "wait 150; shot $OUT/ending.png; press enter; wait 5; shot $OUT/ending-summary.png; press enter; wait 250; shot $OUT/credits.png; quit", []string{"-ending", "-save", "$DEMO"}}, {"007-akihabara", toKiosk7 + "; urge Ville; press enter; wait 5; press right; wait 3; shot $OUT/payer.png; quit", []string{"-seed", "7"}}, - {"005-ueno", toPlatform5 + "; move left 1; wait 5; push left 30; wait 3; shot $OUT/no-exit.png; press enter; wait 150; shot $OUT/staff.png; quit", []string{"-seed", "7"}}, + {"005-ueno", toPlatform5 + "; " + toGateBack + "; wait 5; push " + pushOut + " 30; wait 3; shot $OUT/no-exit.png; press enter; wait 150; shot $OUT/staff.png; quit", []string{"-seed", "7"}}, } return nil } // moves gör om kortaste vägen mellan två rutor till "move DIR N"-steg. +// backToGate ger stegen från perrongen till första spärren på vägen ut och +// riktningen vidare ut genom den. +func backToGate(l *level.Level, from level.Point) (string, string, error) { + path := l.PathOutside(from, l.SpawnPoint(), l.CanEnter) + gi := 0 + for gi < len(path) && l.At(path[gi]) != level.Gate { + gi++ + } + if gi >= len(path)-1 { + return "", "", fmt.Errorf("ingen spärr på vägen ut från %v i %s", from, l.ID) + } + m, err := moves(l, from, path[gi]) + if err != nil { + return "", "", err + } + g, o := path[gi], path[gi+1] + dir := "right" + switch { + case o.X < g.X: + dir = "left" + case o.Y < g.Y: + dir = "up" + case o.Y > g.Y: + dir = "down" + } + return m, dir, nil +} + func moves(l *level.Level, from, to level.Point) (string, error) { - path := l.Path(from, to, func(p level.Point) bool { // andra perronger är inga genvägar (spärrar utan återvändo) + path := l.PathOutside(from, to, func(p level.Point) bool { // andra perronger och deras betalda områden är inga genvägar return l.CanEnter(p) && (!l.At(p).IsPlatform() || p == to) }) if path == nil {