M10b: bussar, fler sidospår och överstimulerande UI
All checks were successful
release / build-release (push) Successful in 1m36s

Ett tåg kan vara en buss (vehicle: bus): samma tidtabell, faser,
bedömning i nätet och ombordstigning, men ett fordon på en väg i stället
för vagnar på räls, hållplatsskylt, "1番" på tavlan, tuta vid inrullning
och dörrsignal före avgång. Nätet har tre busslinjer från Narita. Nya
sidobanor: 001b Flygbussterminalen (biljettbana med tre bussar) och 003b
Harajuku (Yamanote åt rätt eller fel håll runt ringen). Overload-flaggan
ger en rullande reklamticker och maximalt med affischer (Ueno, Tokyo).
Romaji utan makroner (fonten saknar dem). Skärmdumparna som börjar på
bankartan går via titelskärmen. Tester, docs och wiki uppdaterade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 17:33:55 +01:00
parent 87695b668b
commit 0c47b0ad00
35 changed files with 666 additions and 73 deletions

View File

@@ -2,6 +2,9 @@
## Oreleaset
- Bussar: flygbussterminalen (sidobana 001b) med tre bussar på väg, egen
sprite, hållplatsskylt och tuta; sidobanan 003b Harajuku; överstimulerande
UI med reklamticker på Ueno och Tokyo.
- Titelskärm med premissen, språkval första gången, tutorial i fem sidor
(skyltläsning med markering av resmålet), kontextuella tips i första
banan, pausmeny på Esc, Avsluta i menyerna, kortare texter på bankartan.

BIN
doc/img/bus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -142,6 +142,9 @@ att bygga på Linux som på Windows.
| `internal/game` | `TestHighlightDestination` | **M13:** resmålets tecken får markeringsfärg, resten nedtonas, flera träffar, ingen träff |
| `internal/game` | `TestAllSoundsDecode`, `TestMelodyForIsStableAndKnown` | **M12:** alla ljud i `AllSounds` finns, avkodas och är minst 50 ms; melodin per bana är stabil och känd; ljudlagret är nil-säkert |
| `internal/save` | `TestSettingsDefaultsAndNormalize` | **M12:** standardinställningar, klampning av volymer/skala, okänt språk blir sv, saknade inställningar fylls i |
| `internal/level` | `TestBusVehicleAndBoardText` | **M10b:** `vehicle: bus` ger ett fordon och "番" på tavlan, okänt fordon ger fel, tåg är standard |
| `internal/nav` | `TestShippedBusLines` | **M10b:** flygbussen mot Shinjuku är rätt, mot Yokohama ingen trafik för Shinjuku |
| `internal/sim` | `TestBusTerminalFlow` | **M10b:** biljett till Shinjuku, hållplats 1, bussen vinner |
| `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 |
@@ -169,7 +172,7 @@ att bygga på Linux som på Windows.
| ~~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 |
| ~~M13 Titel, tutorial, trygghet~~ ✓ | Markeringen av resmålet i skylttext (`game.TestHighlightDestination`); titel/språkval/paus/tutorial/tips med skärmdumpar; karttexter ryms |
| 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 |
| ~~M10b Bussar och vägar~~ ✓ | Bussar följer tidtabellen, bedöms som tåg och ritas på väg; tavlan skiljer buss från tåg; busslinjerna i nätet; bussterminalen klaras – finns, se tabellen ovan; overload-tickern verifieras med skärmdump |
| ~~M11 Oändligt läge & NG+~~ ✓ | Målsekvens deterministisk per seed, giltiga mål med fälla, NG+ behåller skills men nollställer items, poänglista – finns, se tabellen ovan |
| ~~M12 Ljud & polish~~ ✓ | Alla ljudfiler som refereras finns och går att avkoda, stationsmelodi per bana, inställningar normaliseras – finns, se tabellen ovan; Windows-binär startar (manuellt, kvar) |

View File

@@ -563,7 +563,7 @@ på **brasse-linux01-runnern** (label `linux-amd64`), aldrig på Pi5:n.
- **Kvar till M10b:** bussar och vägrutor, fler sidobanor, överstimulerande
UI som eget svårighetsreglage.
### M10b – Bussar, vägar och fler sidospår
### M10b – Bussar, vägar och fler sidospår ✓ (klar 2026-10-25)
- **Mål:** resten av M10-listan som inte rymdes: buss med vägrutor och
hållplatser (egen fordonssprite, hållplatsskylt, tidtabell som tågen),
@@ -572,6 +572,25 @@ på **brasse-linux01-runnern** (label `linux-amd64`), aldrig på Pi5:n.
avgångstavlor) som kan slås på per bana.
- **Teknik:** vägrutor i banformatet, `Vehicle`-abstraktion i sim som tåg
och buss delar, återanvänd `nav` för busslinjer.
- **Så blev det:** ett tåg kan vara en **buss** (`vehicle: "bus"` på tåget):
samma tidtabell, faser, bedömning i nätet och ombordstigning, men ett
enda fordon (gul buss-sprite) på en **väg** (asfalt med mittlinje) i
stället för tre vagnar på räls, en **hållplatsskylt** på hållplatsen,
"1番" i stället för "1番線" på tavlan, tuta när den svänger in och
dörrsignal före avgång (ingen stationsmelodi). Nätet fick tre busslinjer
från Narita (リムジンバス mot Shinjuku och Yokohama, 空港バス mot Tokyo).
**Sidobanor:** 001b Flygbussterminalen (biljettbana med tre bussar,
parent 001) och 003b Harajuku (Yamanote mot Shinjuku eller fel varv runt
ringen, parent 003). **Överstimulerande UI** (`overload: true` på 005
Ueno och 008 Tokyo): rullande reklamticker i blinkande färger under
HUD:en och maximalt med affischer. Falska avgångstavlor fick vänta.
- **Klar när:** fordon valideras och tavlan skiljer buss från tåg
(`level.TestBusVehicleAndBoardText`), busslinjerna bedöms rätt
(`nav.TestShippedBusLines`), bussterminalen går att klara
(`sim.TestBusTerminalFlow`), busstutan finns (`game.TestAllSoundsDecode`). ✓
- **Kvar till senare:** falska avgångstavlor, bussar i oändligt läge (går
redan: 001b ligger i poolen), hållplatser mitt i en bana (vägrutor som
golv).
### M10c – Vem betalar, och spärrar utan återvändo ✓ (klar 2026-10-24)

View File

@@ -296,7 +296,7 @@ från skills i skill tree, och från unika skills man får av sidouppdrag.
| "skyltar och terminaler" | Terminalen är biljettautomaten: på nätkopplade banor visar den linjetavlan (linjer vid stationen med grannstationer åt båda hållen), läst genom ens japanska som en skylt | Egna terminalrutor kan komma senare |
| "flera stopp och byten" | En resa är en kedja av banfiler (`next` → delbana med `kind: "leg"`). Samma omgång fortsätter på nästa station med klocka, plånbok och sällskap; resmålet i HUD:en visas "via" bytesstationen. Vilken bytesstation som gäller bestämmer banan – spelaren väljer tåg, inte rutt | Fri ruttplanering (välj själv var man byter) är en möjlig utbyggnad |
| "slutar när man ska tillbaka till flygplatsen" | Sista huvudbanan (008) går Tokyo → Nippori → Narita flygplats med Keisei. När den klaras första gången visas hemresan: planet lyfter, resan summeras, eftertexter | Slutet går att se igen via `-ending` |
| Buss | Uppskjuten till M10b (vägrutor, hållplatser, egen sprite) | |
| Buss | Ett tåg med `vehicle: "bus"`: samma tidtabell och bedömning, ett fordon på väg med hållplatsskylt; flygbussterminalen (001b) är en biljettbana med tre bussar | Vägrutor som golv och hållplatser mitt i banan kan komma senare |
| "De ikoniska melodierna" | Egna syntetiserade stationsmelodier i klockklang (fyra stycken, en per station) – riktiga JR-melodier är upphovsrättsskyddade och spelas inte in | Byt filerna i `resources/sound/` om egna inspelningar finns |
| "Oändligt läge … målen är slumpade" | Målen är storyns stationer med lottade resmål (samma labyrint, samma tåg, nytt resmål avgör vilket tåg som är rätt), seedat; sällskapet växer och tidsgränsen krymper per mål; omgången slutar vid första förlust och hamnar i en egen poänglista | Slumpade labyrinter vid körning kan komma senare |
| "New Game+ … behåll alla dina skills" | Skills, XP och språkkunskap behålls; items, stjärnor, pengar och debuffar nollställs; sällskapet är en person större per NG+-varv | |

View File

@@ -32,6 +32,7 @@ var tileOrder = []level.Tile{
level.LaneLeft, level.LaneRight,
level.Wall, level.Wall, level.Wall, // plats 13–15 = affischer (laddas separat som Posters)
level.Kiosk,
level.Wall, level.Wall, level.Wall, // plats 18–20 = buss, väg, hållplatsskylt (laddas separat)
}
const (
@@ -39,6 +40,9 @@ const (
railsIndex = 9
posterIndex = 14
posterCount = 3
busIndex = 18
roadIndex = 19
stopIndex = 20
)
// Assets är allt som laddats från resources/.
@@ -50,6 +54,9 @@ type Assets struct {
Rails *ebiten.Image // rälsruta (horisontell), roteras för vertikala spår
Posters []*ebiten.Image // affischer för visuell röra (ritas över väggrutor)
Plane *ebiten.Image // flygplanet i slutscenen
Bus *ebiten.Image // bussen (ett fordon, inte vagnar)
Road *ebiten.Image // vägruta (horisontell), roteras för vertikala
StopSign *ebiten.Image // hållplatsskylt ovanpå busshållplatsen
Icon image.Image // fönsterikonen (nil om den saknas)
Player [4][3]*ebiten.Image // [riktning: ner, upp, vänster, höger][bildruta: stilla, gå1, gå2]
Text map[string]string
@@ -78,7 +85,7 @@ func LoadAssets() (*Assets, error) {
return nil, fmt.Errorf("tiles.png är %d px bred, behöver minst %d", w, need)
}
for i, t := range tileOrder {
if i == trainIndex || i == railsIndex || (i >= posterIndex && i < posterIndex+posterCount) {
if i == trainIndex || i == railsIndex || (i >= posterIndex && i < posterIndex+posterCount) || i >= busIndex {
continue
}
a.Tiles[t] = sub(i)
@@ -88,6 +95,7 @@ func LoadAssets() (*Assets, error) {
}
a.Train = sub(trainIndex)
a.Rails = sub(railsIndex)
a.Bus, a.Road, a.StopSign = sub(busIndex), sub(roadIndex), sub(stopIndex)
for i := 0; i < posterCount; i++ {
a.Posters = append(a.Posters, sub(posterIndex+i))
}

View File

@@ -48,6 +48,7 @@ const (
SoundLose = "lose"
SoundStaff = "staff"
SoundPlane = "plane"
SoundBusHorn = "bus-horn"
)
// StationMelodies är avgångsmelodierna; varje station har sin (level.Melody
@@ -57,7 +58,7 @@ var StationMelodies = []string{"melody-a", "melody-b", "melody-c", "melody-d"}
// AllSounds är alla klipp som måste finnas i resources/sound.
func AllSounds() []string {
return append([]string{SoundApproach, SoundAnnounce, SoundDoors, SoundChatter, SoundMusicMap, SoundGateBeep, SoundGateDeny,
SoundKiosk, SoundToilet, SoundLearn, SoundConfirm, SoundCancel, SoundTransfer, SoundWin, SoundLose, SoundStaff, SoundPlane},
SoundKiosk, SoundToilet, SoundLearn, SoundConfirm, SoundCancel, SoundTransfer, SoundWin, SoundLose, SoundStaff, SoundPlane, SoundBusHorn},
StationMelodies...)
}

View File

@@ -36,11 +36,11 @@ func (s *LevelScene) drawNPCs(g *Game, screen *ebiten.Image, ox, oy float64) {
func (s *LevelScene) buildPosters() {
s.posters = map[level.Point]int{}
l := s.run.Level
if l.Clutter == 0 {
if s.clutterLevel() == 0 {
return
}
r := s.run.Seed.Derive("posters").New()
chance := 0.12 * float64(l.Clutter)
chance := 0.12 * float64(s.clutterLevel())
for z := 0; z < l.NumFloors(); z++ {
for y := 0; y < l.Height(); y++ {
for x := 0; x < l.Width(); x++ {

View File

@@ -58,6 +58,7 @@ type LevelScene struct {
// Tåg och effekter (M7b).
tracks []trackInfo
rails map[level.Point]bool
roads map[level.Point]bool // vägrutor för bussar (ritas med vägsprite)
lastPhase map[level.Tile]level.Phase
particles []particle
rng *rand.Rand // bara för partiklar (ingen spellogik)
@@ -299,7 +300,7 @@ func (s *LevelScene) Draw(g *Game, screen *ebiten.Image) {
for y := y0; y < y1; y++ {
for x := x0; x < x1; x++ {
p := level.Point{X: x, Y: y, Z: z}
if s.rails[p] {
if s.rails[p] || s.roads[p] {
s.drawRails(g, screen, p, ox, oy)
continue
}
@@ -358,6 +359,7 @@ func (s *LevelScene) Draw(g *Game, screen *ebiten.Image) {
s.drawIndicator(screen, ox, oy)
s.drawHUD(g, screen)
s.drawTicker(g, screen)
s.drawTip(g, screen)
if r := s.run; r.TransferAt >= 0 && r.Time-r.TransferAt < transferBannerSec && r.Mini == nil && r.Outcome == sim.Ongoing {
txt := g.Assets.T("hud.transfer_done")

View File

@@ -69,7 +69,7 @@ func (m *MapScene) nodes() ([]mapNode, map[string][]mapNode) {
}
mains = append(mains, mapNode{l, x, y})
for k, s := range m.sides[l.ID] {
sides[l.ID] = append(sides[l.ID], mapNode{s, x + 12 + k*18, y - 40})
sides[l.ID] = append(sides[l.ID], mapNode{s, x + 12 + k*18, y - 28})
}
}
return mains, sides

53
internal/game/ticker.go Normal file
View File

@@ -0,0 +1,53 @@
package game
import (
"image/color"
"math"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
)
// Överstimulerande UI (level.Overload): en rullande reklamticker i
// blinkande färger under HUD:en, och fler affischer på väggarna. Texten är
// reklamfraser som inte betyder något för spelet – det är poängen.
var adPhrases = []string{"大セール 半額!", "新発売 ラーメン", "免税 お土産", "駅弁 人気No.1", "本日限り 特価", "ポイント2倍", "新宿 ショッピング", "温泉 旅行 キャンペーン"}
const tickerSpeed = 30.0 // px/s
// drawTicker ritar reklamtickern (bara med Overload och när inget annat
// ligger där).
func (s *LevelScene) drawTicker(g *Game, screen *ebiten.Image) {
r := s.run
if !r.Level.Overload || r.Mini != nil || r.Outcome != 0 || s.tips.current != "" {
return
}
a := g.Assets
text := strings.Join(adPhrases, " ★ ") + " ★ "
w := a.Width(text)
if w == 0 {
return
}
y := 26
blink := math.Mod(r.Time, 1.0) < 0.5
bg, fg := color.RGBA{120, 30, 60, 230}, color.RGBA{255, 230, 120, 255}
if blink {
bg, fg = color.RGBA{40, 60, 140, 230}, color.RGBA{255, 255, 255, 255}
}
vector.DrawFilledRect(screen, 0, float32(y), ScreenW, LineH+2, bg, false)
x := ScreenW - int(math.Mod(r.Time*tickerSpeed, float64(w+ScreenW)))
for _, sx := range []int{x, x + w} {
if sx < ScreenW && sx+w > 0 {
a.Draw(screen, text, sx, y+1, fg)
}
}
}
// clutterLevel är den visuella röran: banans clutter, maximal med Overload.
func (s *LevelScene) clutterLevel() int {
if s.run.Level.Overload {
return 3
}
return s.run.Level.Clutter
}

View File

@@ -31,15 +31,21 @@ type trackInfo struct {
platform level.Point // perrongrutan
track level.Point // rälsrutan bortom kanten
horizontal bool // rälsens riktning
bus bool // buss: väg och ett fordon i stället för räls och vagnar
}
func (s *LevelScene) buildTracks() {
s.tracks = nil
s.rails = map[level.Point]bool{}
s.roads = map[level.Point]bool{}
for _, t := range level.PlatformTiles {
for _, p := range s.run.Level.Find(t) {
tt, horizontal := s.run.Level.TrackTile(p)
s.tracks = append(s.tracks, trackInfo{tile: t, platform: p, track: tt, horizontal: horizontal})
bus := false
if tr := s.run.Level.TrainAt(t); tr != nil && tr.IsBus() {
bus = true
}
s.tracks = append(s.tracks, trackInfo{tile: t, platform: p, track: tt, horizontal: horizontal, bus: bus})
out := level.Point{X: tt.X - p.X, Y: tt.Y - p.Y} // riktningen bort från perrongen
for k := -railsReach; k <= railsReach; k++ {
c := tt
@@ -51,12 +57,16 @@ func (s *LevelScene) buildTracks() {
beyond := level.Point{X: c.X + out.X, Y: c.Y + out.Y, Z: c.Z}
// Räls på väggrutor längs spåret, så länge det är vägg (eller kartkant) bortom dem.
if s.run.Level.At(c) == level.Wall && s.run.Level.At(beyond) == level.Wall {
if bus {
s.roads[c] = true
} else {
s.rails[c] = true
}
}
}
}
}
}
// trainOffset är tågets förskjutning i rutor längs rälsen i en fas:
// det kommer in från minus-sidan och lämnar åt plus-sidan.
@@ -87,7 +97,11 @@ func (s *LevelScene) drawRails(g *Game, screen *ebiten.Image, c level.Point, ox,
op.GeoM.Translate(TileSize/2, TileSize/2)
}
op.GeoM.Translate(ox+float64(c.X*TileSize), oy+float64(c.Y*TileSize))
screen.DrawImage(g.Assets.Rails, &op)
img := g.Assets.Rails
if s.roads[c] {
img = g.Assets.Road
}
screen.DrawImage(img, &op)
}
// drawTrains ritar tågen i rätt läge för sin fas.
@@ -98,6 +112,11 @@ func (s *LevelScene) drawTrains(g *Game, screen *ebiten.Image, ox, oy float64) {
continue // tågen på andra våningar syns inte härifrån
}
phase, prog := s.run.PlatformPhase(tr.tile)
if tr.bus { // hållplatsskylten står på hållplatsen
var op ebiten.DrawImageOptions
op.GeoM.Translate(ox+float64(tr.platform.X*TileSize), oy+float64(tr.platform.Y*TileSize))
screen.DrawImage(g.Assets.StopSign, &op)
}
// Strålkastare när tåget är på väg in: ett sken från det håll det kommer.
if phase == level.Approaching {
s.drawHeadlight(screen, tr, prog, ox, oy)
@@ -106,8 +125,12 @@ func (s *LevelScene) drawTrains(g *Game, screen *ebiten.Image, ox, oy float64) {
if !show {
continue
}
for k := 0; k < trainCars; k++ {
d := off + float64(k-trainCars/2)
cars, sprite := trainCars, g.Assets.Train
if tr.bus {
cars, sprite = 1, g.Assets.Bus
}
for k := 0; k < cars; k++ {
d := off + float64(k-cars/2)
var op ebiten.DrawImageOptions
x, y := float64(tr.track.X*TileSize), float64(tr.track.Y*TileSize)
if tr.horizontal {
@@ -119,7 +142,7 @@ func (s *LevelScene) drawTrains(g *Game, screen *ebiten.Image, ox, oy float64) {
op.GeoM.Translate(TileSize/2, TileSize/2)
}
op.GeoM.Translate(ox+math.Round(x), oy+math.Round(y))
screen.DrawImage(g.Assets.Train, &op)
screen.DrawImage(sprite, &op)
}
}
}
@@ -162,14 +185,19 @@ func (s *LevelScene) updateEffects(g *Game) {
s.lastPhase[tr.tile] = phase
dist := math.Hypot(float64(tr.platform.X*TileSize)-px, float64(tr.platform.Y*TileSize)-py) / TileSize
mask := noiseMask(r.Level.Noise)
switch phase {
case level.Approaching: // ankomstjingeln: tåget är på väg
switch {
case tr.bus && phase == level.Arriving: // bussen tutar när den svänger in
g.Audio.Play(SoundBusHorn, volumeAt(dist)*mask)
case tr.bus && phase == level.Closing:
g.Audio.Play(SoundDoors, volumeAt(dist)*mask)
case tr.bus:
case phase == level.Approaching: // ankomstjingeln: tåget är på väg
g.Audio.Play(SoundApproach, volumeAt(dist)*mask)
case level.Arriving: // ding-dong när det rullar in
case phase == level.Arriving: // ding-dong när det rullar in
g.Audio.Play(SoundAnnounce, volumeAt(dist)*mask)
case level.Closing: // stationens avgångsmelodi innan dörrarna stängs
case phase == level.Closing: // stationens avgångsmelodi innan dörrarna stängs
g.Audio.Play(MelodyFor(r.Level), volumeAt(dist)*mask)
case level.Leaving: // dörrsignalen när det går
case phase == level.Leaving: // dörrsignalen när det går
g.Audio.Play(SoundDoors, volumeAt(dist)*mask)
}
}

View File

@@ -194,6 +194,7 @@ type Train struct {
Correct bool `json:"correct"` // tar spelaren till rätt resmål (räknas ut från nätet när banan har station/to)
LineID string `json:"lineId"` // linjen i linjenätet (resources/data/network.json)
Terminus string `json:"terminus"` // en station i färdriktningen (nätets id) – avgör riktningen
Vehicle string `json:"vehicle"` // "" = tåg, "bus" = buss (väg i stället för räls, hållplats i stället för perrong)
Crowded bool `json:"crowded"` // fullt tåg: minispel för att få plats (gäller alla vagnar)
Cars int `json:"cars"` // antal vagnar (0 = DefaultCars)
WomenOnlyCar int `json:"womenOnlyCar"` // 1-baserat vagnnummer för kvinnovagn, 0 = ingen
@@ -269,6 +270,7 @@ type Level struct {
SeatSelection bool `json:"seatSelection"` // biljettköpet fortsätter med sittplatsval efter sällskapets önskemål
OneWayGates bool `json:"oneWayGates"` // spärren släpper inte ut en igen förrän man åkt; personal vid spärren låser upp (tar tid)
Melody string `json:"melody"` // stationens avgångsmelodi: "a".."d" (tomt = vald efter id)
Overload bool `json:"overload"` // överstimulerande UI: reklamticker, blinkande reklam, extra affischer
FareYen int `json:"fareYen"` // vad spärren drar från kortet (0 = standard)
Grid []string `json:"grid,omitempty"` // envåningsbana (kompatibilitet) – blir Floors[0]
Floors []Storey `json:"floors,omitempty"` // flervåningsbana; alla våningar lika stora
@@ -466,8 +468,14 @@ func (l *Level) Validate() error {
if tr.Correct {
correct++
}
if tr.Vehicle != "" && tr.Vehicle != "bus" {
return fmt.Errorf("level %s: tåg %d har okänt fordon %q (tomt eller bus)", l.ID, i, tr.Vehicle)
}
if tr.Cars == 0 {
tr.Cars = DefaultCars
if tr.IsBus() {
tr.Cars = 1
}
}
if tr.Cars < 1 || tr.Cars > MaxCars {
return fmt.Errorf("level %s: tåg %d har %d vagnar (1–%d)", l.ID, i, tr.Cars, MaxCars)
@@ -720,11 +728,18 @@ func (l *Level) Unreachable() []Point {
return out
}
// BoardText är avgångstavlans text på japanska, t.ex. "2番線 成田エクスプレス 新宿方面".
// BoardText är avgångstavlans text på japanska, t.ex. "2番線 成田エクスプレス 新宿方面"
// (för bussar "1番 リムジンバス 新宿方面": hållplatsnummer i stället för spår).
func (tr *Train) BoardText() string {
if tr.IsBus() {
return tr.Track + "番 " + tr.Line.Get("ja") + " " + tr.Destination.Get("ja") + "方面"
}
return tr.Track + "番線 " + tr.Line.Get("ja") + " " + tr.Destination.Get("ja") + "方面"
}
// IsBus rapporterar om fordonet är en buss.
func (tr *Train) IsBus() bool { return tr.Vehicle == "bus" }
// TrackTile är rutan "bortom perrongkanten" där rälsen går: första väggruta
// nedanför, till höger, till vänster eller ovanför perrongen. horizontal
// anger om rälsen (och tågets rörelse) går i sidled.

View File

@@ -838,3 +838,24 @@ func TestToiletSides(t *testing.T) {
}
}
}
func TestBusVehicleAndBoardText(t *testing.T) {
src := `{"id":"bus","name":{"sv":"x"},"difficulty":1,"destination":{"ja":"新宿"},
"grid":["#######","#S.GP.#","#######"],
"trains":[{"platform":"P","track":"1","line":{"ja":"リムジンバス"},"destination":{"ja":"新宿"},"correct":true,"vehicle":"bus"}]}`
l, err := Parse([]byte(src))
if err != nil {
t.Fatal(err)
}
tr := &l.Trains[0]
if !tr.IsBus() || tr.Cars != 1 || tr.BoardText() != "1番 リムジンバス 新宿方面" {
t.Fatalf("buss: cars=%d board=%q", tr.Cars, tr.BoardText())
}
if _, err := Parse([]byte(strings.Replace(src, `"vehicle":"bus"`, `"vehicle":"tram"`, 1))); err == nil {
t.Fatal("okänt fordon ska ge fel")
}
train, _ := Parse([]byte(strings.Replace(src, `,"vehicle":"bus"`, ``, 1)))
if train.Trains[0].IsBus() || train.Trains[0].Cars != DefaultCars || train.Trains[0].BoardText() != "1番線 リムジンバス 新宿方面" {
t.Fatal("utan vehicle är det ett tåg")
}
}

View File

@@ -169,3 +169,20 @@ func TestShippedNetwork(t *testing.T) {
t.Errorf("Marunouchi via city = %v", v)
}
}
func TestShippedBusLines(t *testing.T) {
dir, err := res.Path("data")
if err != nil {
t.Fatal(err)
}
n, err := LoadFile(filepath.Join(dir, "network.json"))
if err != nil {
t.Fatal(err)
}
if v := n.Judge("limousine-shinjuku", "narita-airport", "shinjuku", "shinjuku"); v != Direct {
t.Errorf("flygbussen mot Shinjuku = %v", v)
}
if v := n.Judge("limousine-yokohama", "narita-airport", "yokohama", "shinjuku"); v != NoService {
t.Errorf("flygbussen mot Yokohama för den som ska till Shinjuku = %v", v)
}
}

View File

@@ -126,3 +126,36 @@ func TestTerminalLines(t *testing.T) {
t.Fatal("okopplad bana har ingen terminal")
}
}
// TestBusTerminalFlow: flygbussterminalen är en biljettbana med bussar –
// köp biljett till Shinjuku, gå till hållplats 1 och åk.
func TestBusTerminalFlow(t *testing.T) {
l := loadLevel(t, "001b-flygbussen")
if !l.TicketRequired || !l.Trains[0].IsBus() || l.Trains[0].Cars != 1 {
t.Fatal("001b ska vara en biljettbana med bussar")
}
r := NewRun(l, RunOptions{Dict: loadDict(t), Seed: rng.FromInt(3)})
r.Party.nextEventAt = 1e9
r.NPCs = nil
walk(t, r, pathTo(r, l.Find(level.TicketMachine)[0]))
r.Step(frame, None, true)
tk, ok := r.Mini.(*minigame.Ticket)
if !ok {
t.Fatalf("ingen biljettautomat (kind=%v)", r.MiniKind)
}
for tk.Options[tk.Cursor].Dest != "新宿" {
r.StepMini(frame, minigame.Input{Move: 1})
}
r.StepMini(frame, minigame.Input{Confirm: true})
if r.Wallet.Ticket != "新宿" {
t.Fatalf("biljett=%q", r.Wallet.Ticket)
}
tr := boardAt(t, r, level.Platform)
if !tr.IsBus() || tr.Verdict != nav.Direct {
t.Fatalf("hållplats 1: %v %v", tr.IsBus(), tr.Verdict)
}
ride(t, r)
if r.Outcome != Won {
t.Fatalf("outcome=%v (%s)", r.Outcome, r.LostReason)
}
}

View File

@@ -0,0 +1,154 @@
{
"destination": {
"en": "Shinjuku",
"ja": "新宿",
"romaji": "Shinjuku",
"sv": "Shinjuku"
},
"difficulty": 1,
"fareYen": 0,
"grid": [
"#################################",
"#S#.....#...#..i..#.....#.....#K#",
"#.#.###.#.#.#.#.#.#.###.#.#.#.#.#",
"#.#i..#.#.#...#.#...#...#.#.#...#",
"#.#.#.#.#.#.###.#####.###.#.###.#",
"#.#T#.#...#.....#.#...#.#.#.#.#.#",
"#.###.###########.#.#.#.#.#.#.#.#",
"#...#...#.........#.#.....#.#.#.#",
"###.###.#.#########.#.#####.#.#.#",
"#...#...#.#...#.....#.....#.#...#",
"#.###.###.#.#.#.#########.#.#.###",
"#.#...#.....#...#.....#...#.#...#",
"#.#.###.#####.#.#.#.#.#####.#.#.#",
"#..i#...#...#.#...#.#.#.....#.#.#",
"###.#.###.###.#####.#.#.#####.#.#",
"#...#...#...#.#.......#.#.....#.#",
"#.#####.#.#.#.#.#######.#.#####.#",
"#W#.....#.#.#.#...#...#.#.....#.#",
"###.#####.#.#.###G#.#.#.#####.#G#",
"#pG.........#...#Q#.#...#.....#P#",
"#################################"
],
"id": "001b-flygbussen",
"kind": "side",
"name": {
"en": "Airport bus terminal",
"ja": "空港バスのりば",
"sv": "Flygbussterminalen"
},
"order": 1,
"parent": "001-flygplatsen",
"party": 2,
"rewards": {
"skillPoints": 1
},
"rideStops": 2,
"signs": [
{
"text": {
"en": "Bus terminal. Tickets at the machine. Stop 1 limousine bus for Shinjuku: south-east. Stop 2 limousine bus for Yokohama: south-west. Stop 3 airport bus for Tokyo: south.",
"ja": "バスのりば 切符 → 券売機 1番 リムジンバス 新宿方面 → 南東 2番 リムジンバス 横浜方面 → 南西 3番 空港バス 東京方面 → 南",
"sv": "Bussterminalen. Biljetter köps i automaten. Hållplats 1 flygbuss mot Shinjuku: sydost. Hållplats 2 flygbuss mot Yokohama: sydväst. Hållplats 3 flygplatsbuss mot Tokyo: söderut."
},
"x": 3,
"y": 13,
"z": 0
},
{
"text": {
"en": "\u003c- Stop 2 limousine bus for Yokohama Stop 1 limousine bus for Shinjuku -\u003e",
"ja": "← 2番 リムジンバス 横浜方面 1番 リムジンバス 新宿方面 →",
"sv": "\u003c- Hållplats 2 flygbuss mot Yokohama Hållplats 1 flygbuss mot Shinjuku -\u003e"
},
"x": 3,
"y": 3,
"z": 0
},
{
"text": {
"en": "Stop 1 limousine bus for Shinjuku: the gate in the south-east corner. Ticket required.",
"ja": "1番 リムジンバス 新宿方面 → 南東口 改札 切符",
"sv": "Hållplats 1 flygbuss mot Shinjuku: spärren i sydöstra hörnet. Biljett krävs."
},
"x": 15,
"y": 1,
"z": 0
}
],
"station": "narita-airport",
"ticketRequired": true,
"timeLimitSec": 300,
"to": "shinjuku",
"trains": [
{
"cars": 1,
"destination": {
"en": "Shinjuku",
"ja": "新宿",
"romaji": "Shinjuku"
},
"line": {
"en": "Airport Limousine",
"ja": "リムジンバス",
"romaji": "Limousine Bus"
},
"lineId": "limousine-shinjuku",
"platform": "P",
"schedule": {
"dwellSec": 9,
"intervalSec": 32,
"offsetSec": 10
},
"terminus": "shinjuku",
"track": "1",
"vehicle": "bus"
},
{
"cars": 1,
"destination": {
"en": "Yokohama",
"ja": "横浜",
"romaji": "Yokohama"
},
"line": {
"en": "Airport Limousine",
"ja": "リムジンバス",
"romaji": "Limousine Bus"
},
"lineId": "limousine-yokohama",
"platform": "p",
"schedule": {
"dwellSec": 8,
"intervalSec": 32,
"offsetSec": 20
},
"terminus": "yokohama",
"track": "2",
"vehicle": "bus"
},
{
"cars": 1,
"destination": {
"en": "Tokyo",
"ja": "東京",
"romaji": "Tokyo"
},
"line": {
"en": "Airport Bus",
"ja": "空港バス",
"romaji": "Kuko basu"
},
"lineId": "airport-bus-tokyo",
"platform": "Q",
"schedule": {
"dwellSec": 8,
"intervalSec": 28,
"offsetSec": 4
},
"terminus": "tokyo",
"track": "3",
"vehicle": "bus"
}
]
}

View File

@@ -0,0 +1,131 @@
{
"crowd": 1,
"destination": {
"en": "Shinjuku",
"ja": "新宿",
"romaji": "Shinjuku",
"sv": "Shinjuku"
},
"difficulty": 2,
"fareYen": 160,
"grid": [
"#################################",
"#S..#W..............#.....#...#.#",
"#.#.###.#########.#.#.###.#.#.#.#",
"#.#.....#.........#.#.#K#...#...#",
"#.#######.#.###.###.#.#.#######.#",
"#...#.....#.#...#...#.#...#...#.#",
"#.###.#####.#.###.###.#.###.#.#.#",
"#...#.#.....#T#...#...#.#...#.#.#",
"#.#.#.#.#######.###.###.#.###.#.#",
"#...#.#...#.....#...#.....#...#.#",
"###.#.###.#.#######.#.#####.###.#",
"#..i#...#...#.......#.....#.#...#",
"#.#.#.#.#####.#.#####.###.#.#.###",
"#.#.#...#...#.#.#...#...#.#.#...#",
"#.#.###.#.#.###.#.#.#.#.#.#.###.#",
"#.#...#.#.#...#.#.#...#.#...#..i#",
"#.#####.#.###.#.#.#####.#.###.#.#",
"#.....#...#.#...#.#...#.#.#...#.#",
"#G#.#.#####.#####.#.#.#.#.#.#####",
"#p#.#......i........#.........GP#",
"#################################"
],
"id": "003b-harajuku",
"kind": "side",
"name": {
"en": "Harajuku",
"ja": "原宿駅",
"sv": "Harajuku"
},
"order": 3,
"parent": "003-shibuya",
"party": 2,
"rewards": {
"items": [
"mask_tengu"
]
},
"rideStops": 3,
"signs": [
{
"text": {
"en": "Harajuku Station. Track 1 Yamanote Line for Shinjuku: south-east. Track 2 Yamanote Line for Shibuya: south-west. Same line - the wrong way round the loop takes half an hour.",
"ja": "原宿駅 1番線 山手線 新宿方面 → 南東 2番線 山手線 渋谷方面 → 南西",
"sv": "Harajuku station. Spår 1 Yamanote-linjen mot Shinjuku: sydost. Spår 2 Yamanote-linjen mot Shibuya: sydväst. Samma linje - fel varv runt ringen tar en halvtimme."
},
"x": 3,
"y": 11,
"z": 0
},
{
"text": {
"en": "\u003c- Track 2 Yamanote Line for Shibuya Track 1 Yamanote Line for Shinjuku -\u003e",
"ja": "← 2番線 山手線 渋谷方面 1番線 山手線 新宿方面 →",
"sv": "\u003c- Spår 2 Yamanote-linjen mot Shibuya Spår 1 Yamanote-linjen mot Shinjuku -\u003e"
},
"x": 11,
"y": 19,
"z": 0
},
{
"text": {
"en": "Track 1 Yamanote Line for Shinjuku: the gate in the south-east corner.",
"ja": "1番線 山手線 新宿方面 → 南東口 改札",
"sv": "Spår 1 Yamanote-linjen mot Shinjuku: spärren i sydöstra hörnet."
},
"x": 31,
"y": 15,
"z": 0
}
],
"station": "harajuku",
"timeLimitSec": 240,
"to": "shinjuku",
"trains": [
{
"cars": 4,
"destination": {
"en": "Shinjuku",
"ja": "新宿",
"romaji": "Shinjuku"
},
"line": {
"en": "Yamanote Line",
"ja": "山手線",
"romaji": "Yamanote-sen"
},
"lineId": "yamanote",
"platform": "P",
"schedule": {
"dwellSec": 8,
"intervalSec": 26,
"offsetSec": 8
},
"terminus": "shinjuku",
"track": "1"
},
{
"cars": 4,
"destination": {
"en": "Shibuya",
"ja": "渋谷",
"romaji": "Shibuya"
},
"line": {
"en": "Yamanote Line",
"ja": "山手線",
"romaji": "Yamanote-sen"
},
"lineId": "yamanote",
"platform": "p",
"schedule": {
"dwellSec": 8,
"intervalSec": 26,
"offsetSec": 18
},
"terminus": "shibuya",
"track": "2"
}
]
}

View File

@@ -11,23 +11,23 @@
"fareYen": 200,
"grid": [
"#################################",
"#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###",
"#S.<..<..#..<..<..<K.<..#..<..###",
"#..>..>..#..>..>..>..>..#.i>..###",
"#######<>#<>#<>####<>#<>####<>###",
"#..<..<T.#..<..<..<..#..<..<..###",
"#.i>..>..#..>..>..>..#..>..>..###",
"#<>#############<>#######<>#<>###",
"#..#..<..<..<..<..<..<..#..<..###",
"#..#..>..>..>..>..>..>..#..>..###",
"#<>####<>##########<>#<>#<>######",
"#..<..<..<..#..<..<..#..#..<..###",
"#..>..>..>..#..>..>..#..#..>..###",
"####<>#<>#<>#<>####<>#######<>###",
"#..<..#..#..#..<..#..<..<..<..###",
"#G.>..#..#..#..>..#..>..>..>..###",
"#<G#######<>####<>###############",
"#..#..<..<.i#..<..<..<..<..G..W##",
"#p.#..>..>..#..>..>..>..>.G>.P###",
"#################################",
"#################################",
"#################################"
@@ -65,9 +65,9 @@
},
{
"text": {
"en": "\u003c- Track 9 Keihin-Tohoku Line (Omiya) Track 3 Yamanote Line (Akihabara) -\u003e",
"en": "<- Track 9 Keihin-Tohoku Line (Omiya) Track 3 Yamanote Line (Akihabara) ->",
"ja": "← 9番線 京浜東北線 大宮方面 3番線 山手線 秋葉原方面 →",
"sv": "\u003c- Spår 9 Keihin-Tohoku-linjen (Omiya) Spår 3 Yamanote-linjen (Akihabara) -\u003e"
"sv": "<- Spår 9 Keihin-Tohoku-linjen (Omiya) Spår 3 Yamanote-linjen (Akihabara) ->"
},
"x": 11,
"y": 16,
@@ -135,5 +135,6 @@
"terminus": "omiya",
"track": "9"
}
]
],
"overload": true
}

View File

@@ -100,9 +100,9 @@
},
{
"text": {
"en": "\u003c- Track 5 Yamanote Line for Shinagawa Track 4 Yamanote Line for Ueno -\u003e",
"en": "<- Track 5 Yamanote Line for Shinagawa Track 4 Yamanote Line for Ueno ->",
"ja": "← 5番線 山手線 品川方面 4番線 山手線 上野方面 →",
"sv": "\u003c- Spår 5 Yamanote-linjen mot Shinagawa Spår 4 Yamanote-linjen mot Ueno -\u003e"
"sv": "<- Spår 5 Yamanote-linjen mot Shinagawa Spår 4 Yamanote-linjen mot Ueno ->"
},
"x": 7,
"y": 19,
@@ -211,5 +211,6 @@
"terminus": "takao",
"track": "1"
}
]
],
"overload": true
}

View File

@@ -1004,6 +1004,47 @@
"yokohama",
"ofuna"
]
},
{
"id": "limousine-shinjuku",
"name": {
"ja": "リムジンバス",
"romaji": "Limousine Bus",
"en": "Airport Limousine (Shinjuku)"
},
"stations": [
"narita-airport",
"airport-terminal-2",
"shinjuku"
],
"vehicle": "bus"
},
{
"id": "limousine-yokohama",
"name": {
"ja": "リムジンバス",
"romaji": "Limousine Bus",
"en": "Airport Limousine (Yokohama)"
},
"stations": [
"narita-airport",
"airport-terminal-2",
"yokohama"
],
"vehicle": "bus"
},
{
"id": "airport-bus-tokyo",
"name": {
"ja": "空港バス",
"romaji": "Kuko basu",
"en": "Airport Bus (Tokyo)"
},
"stations": [
"narita-airport",
"tokyo"
],
"vehicle": "bus"
}
]
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -51,7 +51,7 @@
},
"方面": {
"kana": "ほうめん",
"romaji": "hōmen",
"romaji": "homen",
"en": "for",
"sv": "mot",
"tier": 2
@@ -79,14 +79,14 @@
},
"特急": {
"kana": "とっきゅう",
"romaji": "tokkyū",
"romaji": "tokkyu",
"en": "Limited express",
"sv": "Expresståg",
"tier": 3
},
"空港": {
"kana": "くうこう",
"romaji": "kūkō",
"romaji": "kuko",
"en": "Airport",
"sv": "Flygplats",
"tier": 2
@@ -121,21 +121,21 @@
},
"終点": {
"kana": "しゅうてん",
"romaji": "shūten",
"romaji": "shuten",
"en": "Terminus",
"sv": "Slutstation",
"tier": 3
},
"自動券売機": {
"kana": "じどうけんばいき",
"romaji": "jidō kenbaiki",
"romaji": "jido kenbaiki",
"en": "Ticket machine",
"sv": "Biljettautomat",
"tier": 3
},
"チャージ": {
"kana": "チャージ",
"romaji": "chāji",
"romaji": "chaji",
"en": "Top up (IC card)",
"sv": "Fyll på (kort)",
"tier": 2
@@ -158,7 +158,7 @@
},
"東京": {
"kana": "とうきょう",
"romaji": "Tōkyō",
"romaji": "Tokyo",
"en": "Tokyo",
"sv": "Tokyo",
"tier": 1,
@@ -196,7 +196,7 @@
},
"中央線": {
"kana": "ちゅうおうせん",
"romaji": "Chūō-sen",
"romaji": "Chuo-sen",
"en": "Chuo Line",
"sv": "Chuo-linjen",
"tier": 2
@@ -232,7 +232,7 @@
},
"南東": {
"kana": "なんとう",
"romaji": "nantō",
"romaji": "nanto",
"en": "south-east",
"sv": "sydöst",
"tier": 3
@@ -246,7 +246,7 @@
},
"南東口": {
"kana": "なんとうぐち",
"romaji": "nantōguchi",
"romaji": "nantoguchi",
"en": "South-east exit",
"sv": "Sydöstra utgången",
"tier": 3
@@ -281,14 +281,14 @@
},
"到着": {
"kana": "とうちゃく",
"romaji": "tōchaku",
"romaji": "tochaku",
"en": "Arrival",
"sv": "Ankomst",
"tier": 2
},
"注意": {
"kana": "ちゅうい",
"romaji": "chūi",
"romaji": "chui",
"en": "Caution",
"sv": "Varning",
"tier": 2
@@ -316,14 +316,14 @@
},
"エレベーター": {
"kana": "エレベーター",
"romaji": "erebētā",
"romaji": "erebeta",
"en": "Elevator",
"sv": "Hiss",
"tier": 2
},
"エスカレーター": {
"kana": "エスカレーター",
"romaji": "esukarētā",
"romaji": "esukareta",
"en": "Escalator",
"sv": "Rulltrappa",
"tier": 2
@@ -372,14 +372,14 @@
},
"普通": {
"kana": "ふつう",
"romaji": "futsū",
"romaji": "futsu",
"en": "Local",
"sv": "Lokaltåg",
"tier": 2
},
"急行": {
"kana": "きゅうこう",
"romaji": "kyūkō",
"romaji": "kyuko",
"en": "Express",
"sv": "Expresståg",
"tier": 2
@@ -534,7 +534,7 @@
},
"歌舞伎町": {
"kana": "かぶきちょう",
"romaji": "Kabukichō",
"romaji": "Kabukicho",
"en": "Kabukicho",
"sv": "Kabukicho",
"tier": 3,
@@ -542,14 +542,14 @@
},
"東京駅": {
"kana": "とうきょうえき",
"romaji": "Tōkyō-eki",
"romaji": "Tokyo-eki",
"en": "Tokyo Station",
"sv": "Tokyo station",
"tier": 3
},
"東海道線": {
"kana": "とうかいどうせん",
"romaji": "Tōkaidō-sen",
"romaji": "Tokaido-sen",
"en": "Tokaido Line",
"sv": "Tokaido-linjen",
"tier": 3
@@ -598,14 +598,14 @@
},
"京浜東北線": {
"kana": "けいひんとうほくせん",
"romaji": "Keihin-Tōhoku-sen",
"romaji": "Keihin-Tohoku-sen",
"en": "Keihin-Tohoku Line",
"sv": "Keihin-Tohoku-linjen",
"tier": 3
},
"大宮": {
"kana": "おおみや",
"romaji": "Ōmiya",
"romaji": "Omiya",
"en": "Omiya",
"sv": "Omiya",
"tier": 3,
@@ -613,14 +613,14 @@
},
"女性専用車": {
"kana": "じょせいせんようしゃ",
"romaji": "josei sen'yō-sha",
"romaji": "josei sen'yo-sha",
"en": "Women-only car",
"sv": "Kvinnovagn",
"tier": 2
},
"号車": {
"kana": "ごうしゃ",
"romaji": "gōsha",
"romaji": "gosha",
"en": "car no.",
"sv": "vagn nr",
"tier": 2
@@ -641,7 +641,7 @@
},
"通行": {
"kana": "つうこう",
"romaji": "tsūkō",
"romaji": "tsuko",
"en": "passage / keep",
"sv": "gång / håll",
"tier": 3
@@ -1175,5 +1175,26 @@
"en": "transfer",
"sv": "byte",
"tier": 2
},
"リムジンバス": {
"kana": "リムジンバス",
"romaji": "rimujin basu",
"en": "airport limousine bus",
"sv": "flygbuss",
"tier": 3
},
"番": {
"kana": "ばん",
"romaji": "ban",
"en": "number",
"sv": "nummer",
"tier": 2
},
"バスのりば": {
"kana": "バスのりば",
"romaji": "basu noriba",
"en": "bus stop",
"sv": "busshållplats",
"tier": 2
}
}

View File

@@ -213,6 +213,32 @@ var tiles = []func(canvas){
c.rect(11, 5, 2, 2, green)
c.rect(2, 10, 12, 3, metal) // disk
},
func(c canvas) { // buss (ritas på vägrutan bortom hållplatsen när bussen är inne)
c.rect(0, 0, 16, 16, wallDark)
c.rect(1, 2, 14, 11, color.RGBA{240, 200, 60, 255}) // gul kaross
c.rect(1, 2, 14, 1, metalDark)
c.rect(2, 4, 12, 4, screenC) // fönsterrad
c.rect(7, 4, 1, 4, metalDark) // fönsterspröjs
c.rect(1, 9, 14, 2, awningRed) // rand
c.rect(11, 8, 2, 5, metalDark) // dörr
c.rect(3, 13, 3, 2, boxDark) // hjul
c.rect(10, 13, 3, 2, boxDark)
},
func(c canvas) { // väg (horisontell; roteras för vertikala): asfalt med mittlinje och kantsten
c.rect(0, 0, 16, 16, color.RGBA{70, 72, 80, 255})
c.rect(0, 0, 16, 1, wallMid)
c.rect(0, 15, 16, 1, metal) // trottoarkant
for x := 0; x < 16; x += 6 {
c.rect(x, 7, 3, 1, color.RGBA{230, 220, 150, 255}) // streckad mittlinje
}
},
func(c canvas) { // hållplatsskylt: stolpe med bussymbol (ritas över hållplatsrutan)
c.rect(7, 4, 2, 12, metalDark)
c.rect(3, 1, 10, 6, color.RGBA{240, 200, 60, 255})
c.rect(4, 2, 8, 3, boxDark)
c.rect(5, 3, 2, 1, white)
c.rect(9, 3, 2, 1, white)
},
}
// player ritar en figur som tittar åt facing (0 ner, 1 upp, 2 vänster, 3 höger)

View File

@@ -70,6 +70,7 @@ var (
staffTone = []note{{a4, 0.18}, {0, 0.08}, {a4, 0.18}, {0, 0.12}, {e5, 0.3}}
gateBeep = []note{{a6, 0.09}}
gateDeny = []note{{e4, 0.12}, {0, 0.04}, {e4, 0.12}, {0, 0.04}, {e4, 0.14}}
busHorn = []note{{a4, 0.22}, {f4, 0.3}}
)
// render syntetiserar toner med sinus + överton, attack/release-envelope.
@@ -264,6 +265,7 @@ func main() {
"lose": render(loseSting, 0.01, 0.08, 0.2),
"staff": square(staffTone),
"plane": noiseSweep(3.0, 0.03, 0.4, func(t float64) float64 { return math.Sin(math.Pi * t / 3.0) }),
"bus-horn": square(busHorn),
"chatter": chatter(6),
"music-map": mapMusic(),
}

View File

@@ -124,6 +124,18 @@ func buildShots(levelsDir string) error {
if err != nil {
return err
}
l1b, err := level.LoadFile(filepath.Join(levelsDir, "001b-flygbussen.json"))
if err != nil {
return err
}
toMachine1b, err := moves(l1b, l1b.SpawnPoint(), l1b.Find(level.TicketMachine)[0])
if err != nil {
return err
}
toStop1b, err := moves(l1b, l1b.Find(level.TicketMachine)[0], l1b.CorrectPlatform())
if err != nil {
return err
}
l7, err := level.LoadFile(filepath.Join(levelsDir, "007-akihabara.json"))
if err != nil {
return err
@@ -146,7 +158,7 @@ func buildShots(levelsDir string) error {
// delvisa översättningen syns; hjälpen lär sedan in nästa ord.
known := []string{"-know", "改札,番線,方面", "-seed", "7"}
shots = []shot{
{"", "wait 10; shot $OUT/map.png; press l; wait 5; press down; press down; press down; wait 5; shot $OUT/words.png; press l; wait 3; press t; wait 5; press down; wait 3; shot $OUT/skills.png; press t; wait 3; press i; wait 5; shot $OUT/items.png; quit",
{"", "wait 5; press enter; wait 10; shot $OUT/map.png; press l; wait 5; press down; press down; press down; wait 5; shot $OUT/words.png; press l; wait 3; press t; wait 5; press down; wait 3; shot $OUT/skills.png; press t; wait 3; press i; wait 5; shot $OUT/items.png; quit",
[]string{"-know", "改札,出口,トイレ", "-points", "3", "-save", "$DEMO"}},
{"001-flygplatsen", toKiosk + "; press b; wait 5; press down; wait 3; shot $OUT/shop.png; quit", []string{"-seed", "7", "-save", "$DEMO"}},
{"001-flygplatsen", "move right 4; move down 2; wait 5; shot $OUT/level-001.png; quit", nil},
@@ -162,13 +174,14 @@ func buildShots(levelsDir string) error {
{"006-ikebukuro", "move right 2; wait 5; shot $OUT/nav-list.png; quit", []string{"-seed", "7", "-mode", "list"}},
{"006-ikebukuro", toMachine6 + "; wait 5; shot $OUT/terminal.png; " + "quit", []string{"-seed", "7", "-mode", "signs", "-know", "山手線,番線,方面,新宿,池袋,埼京線"}},
{"006-ikebukuro", toTrap6 + "; waittrain; wait 5; press enter; wait 100; pick calm; press enter; wait 60; shot $OUT/ride-real.png; waitover; wait 5; shot $OUT/too-far.png; quit", []string{"-seed", "7", "-mode", "signs"}},
{"", "wait 10; press n; wait 5; shot $OUT/map-signs.png; quit", []string{"-save", "$DEMO"}},
{"", "wait 5; press enter; wait 10; press n; wait 5; shot $OUT/map-signs.png; quit", []string{"-save", "$DEMO"}},
{"007-akihabara", toPlatform7 + "; waittrain; wait 5; press enter; wait 100; pick calm; press enter; waitstop; press enter; wait 40; shot $OUT/transfer.png; quit",
[]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"}},
{"", "wait 10; shot $OUT/endless.png; quit", []string{"-endless", "-seed", "7", "-mode", "list", "-save", "$DEMO"}},
{"", "wait 10; press s; wait 5; press down; wait 3; shot $OUT/settings.png; quit", []string{"-save", "$DEMO"}},
{"001b-flygbussen", toMachine1b + "; press enter; pick 新宿; press enter; wait 5; " + toStop1b + "; waittrain; wait 10; shot $OUT/bus.png; quit", []string{"-seed", "7", "-know", "新宿,方面"}},
{"", "wait 5; press enter; wait 10; press s; wait 5; press down; wait 3; shot $OUT/settings.png; quit", []string{"-save", "$DEMO"}},
{"", "wait 10; shot $OUT/lang.png; press enter; wait 30; shot $OUT/title.png; press down; press enter; wait 5; press right; wait 5; shot $OUT/tutorial.png; quit", []string{"-save", "$FRESH"}},
{"001-flygplatsen", "wait 40; shot $OUT/tips.png; " + toSign + "; wait 5; shot $OUT/tips-sign.png; press esc; wait 5; shot $OUT/pause.png; quit", []string{"-seed", "7", "-save", "$FRESH"}},
{"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"}},