From f04d6a88f9dff982d5cdb752ca309f04047e91ef Mon Sep 17 00:00:00 2001 From: claude Date: Sat, 24 Oct 2026 22:12:13 +0200 Subject: [PATCH] =?UTF-8?q?M10c:=20vem=20betalar=20i=20kiosken=20och=20sp?= =?UTF-8?q?=C3=A4rrar=20utan=20=C3=A5terv=C3=A4ndo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Björns tillägg 2026-10-24. Med egna kort och fler i sällskapet frågar kiosken vem som betalar (minigame.Payer: kontant, spelarens kort eller en följares kort; den som inte har råd nekas, Esc avbryter). Avgiften dras nu när man passerar spärren in i det betalda området (level.InsideGates), inte på spärrrutan. Banflaggan oneWayGates: incheckad släpps inte ut igen förrän man åkt; Enter vid spärren kallar på personal (10 s) som låser upp korten; ny inpassering kostar igen. På i 005, 007/007b och 008/008b. Skriptkommandon urge och push. Docs, wiki och skärmdumpar uppdaterade. Co-Authored-By: Claude Fable 5.1 --- README.md | 13 +- doc/img/no-exit.png | Bin 0 -> 23318 bytes doc/img/payer.png | Bin 0 -> 25482 bytes doc/img/staff.png | Bin 0 -> 20841 bytes doc/mal-och-tester.md | 4 + doc/roadmap.md | 39 +++++ doc/spelbeskrivning.md | 9 ++ internal/game/minigame_view.go | 35 +++++ internal/game/scene_level.go | 33 +++++ internal/game/script.go | 23 ++- internal/level/level.go | 27 ++++ internal/level/level_test.go | 26 ++++ internal/minigame/minigame_test.go | 29 ++++ internal/minigame/payer.go | 61 ++++++++ internal/sim/gates_test.go | 141 ++++++++++++++++++ internal/sim/nav_test.go | 2 +- internal/sim/run.go | 147 ++++++++++++++++++- internal/sim/run_test.go | 40 ++--- resources/data/levels/005-ueno.json | 3 +- resources/data/levels/007-akihabara.json | 9 +- resources/data/levels/007b-ueno.json | 43 +++--- resources/data/levels/008-tokyo-hemresa.json | 9 +- resources/data/levels/008b-nippori.json | 9 +- resources/text/en.json | 13 +- resources/text/sv.json | 13 +- tools/shots/main.go | 10 +- 26 files changed, 674 insertions(+), 64 deletions(-) create mode 100644 doc/img/no-exit.png create mode 100644 doc/img/payer.png create mode 100644 doc/img/staff.png create mode 100644 internal/minigame/payer.go create mode 100644 internal/sim/gates_test.go diff --git a/README.md b/README.md index aaa3415..34876f3 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,11 @@ med flera våningar, folk, reklam, sorl, vagnval och ett riktigt linjenät med list- och skyltläge. De två sista resorna har **byten**: man kliver av på en mellanstation och fortsätter i samma omgång, med klockan, plånboken och sällskapet i behåll. Klarar man finalen (Tokyo → Nippori → Narita) visas -**hemresan**: planet lyfter, resan summeras och eftertexterna rullar. Nästa -är M10b (bussar) eller M11 (oändligt läge och New Game+). Se -[doc/roadmap.md](doc/roadmap.md). +**hemresan**: planet lyfter, resan summeras och eftertexterna rullar. Med +egna kort väljer man **vem som betalar** i kiosken, och på de svåraste +stationerna släpper spärren inte ut en som checkat in: man får **kalla på +personal** med Enter vid spärren. Nästa är M11 (oändligt läge och New +Game+). Se [doc/roadmap.md](doc/roadmap.md). ![Bankartan](doc/img/map.png) @@ -76,6 +78,8 @@ labyrintgolv, samma klocka och samma hungriga sällskap. ![Byte i Ueno](doc/img/transfer.png) +![Vem betalar?](doc/img/payer.png) + ![Hemresan](doc/img/ending.png) På de svåraste banorna har alla i sällskapet egna kort som måste ha saldo, @@ -185,7 +189,8 @@ metro-puzzle-jp -version ### Styrning Pilar eller WASD går. Enter väljer, kliver på tåget, använder toaletten, -handlar i kiosken eller startar automaten; i minispelen väljer pilarna och +handlar i kiosken, startar automaten eller kallar på personal vid en spärr +som inte släpper ut en; i minispelen väljer pilarna och Enter bekräftar (håll Enter för att mata in sedlar). H frågar om hjälp vid en skylt, L öppnar ordlistan, T skillträdet, I garderoben, B butiken vid en kiosk, M döljer/visar minikartan, P på kartan lägger kortet i telefonen, N på kartan diff --git a/doc/img/no-exit.png b/doc/img/no-exit.png new file mode 100644 index 0000000000000000000000000000000000000000..6bea212dc8a02154ffb53ac22ec60e9826351af6 GIT binary patch literal 23318 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 dJ;qXd%F6$taD0e0st$gGt&S7 literal 0 HcmV?d00001 diff --git a/doc/img/payer.png b/doc/img/payer.png new file mode 100644 index 0000000000000000000000000000000000000000..2910cd36967f27a852c58cb659fe5ead178b40a2 GIT binary patch literal 25482 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�g1f`rg5(kC#`A{4_?Y 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ℑFg3L6Y? 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

6o4$&ZLx>_O3|Z08dW1)7h?-a!b#L z{Ht}P^R^z~qmk&>fgXNp?hd{0NXmCwqb~Nt`M0hBY0I{&H8{I@_e{Q*a{WTmE}X!| z*lUJu9?ybf9=(Is!=@L1sa^UtO*>uT{mU3XDtpl3cGm~zKw2qbuL0II?niym=d;i= zdoAMOrlB4kF84rO{=yGAwCh|&X*AII*Dktu>rCZ|A8iOJSZ=1)>VVoW=d7N zjosEz-`1cu$2jO}2&ddUeT?ng8Kz^LNNTw#}_IwkztnXbI;4u3_EnDN2Q7@$Bh%3~;#PF(9TFDM$l&czyaMeDpRIS~==HLuIh~58I zydasEFPjM=0si_q!w+?xdve~rF z{q%}LER>MT?n`19OM|{oBeFjx8U>*!AlL?P=D~k_an^<`Hbz^zA1|B0HVHXiS$oHi z2E%}UHDZW0jD-saZt`}O=UB?QlR=_JD?OKI{!w&B;#lI>^jmTy=8Kt>G;UT~=QRBu zgIc{15@W7~BW=4cmt=RF*@w=NS;Cl3tFE6lr2mSX0j^RmL0g4LS}Pi-s7C5=8y7Fmts=IX>%PMCIRbSFAFtEmbakL z#ZPm&V?XX?m(=SOn3#o?*8Q$~gAeLJ?_8Th>$1QFZBMeId8KW^4(>L=`5XQp1v48@ z|5BB%-E|Qc?o7Hj+|x_TqD zcHX0Vw4--a)CYDP;FluqvuVUXKM1|3fL`=|>J{j|-yPhs8*7{6rEDxAHx;V8ZI<;R z5~u>TdG&=CMXX+7-f(TPT_wGOZJ9Bm=P{&rj66lu$BmgNSigRLP%hJ+J6#F7*4tc! z?eBUy!VS&smM0U#d`{cO8PygMz327GvS zJYDCn2L#q4szvMs&l`7Jj?;2D)tm}vMf4vA^4itQ&i8uR$+{4eSI`2E5_=g@ba{RL z_{!&3w3iX4DoR+NV4@@L`a@D`Yfh#gtlRixQ&= 0 && r.Time-r.NoExitAt < deniedShowSec: + return a.wrapLine(plainLine(fmt.Sprintf(a.T("hud.no_exit"), sim.StaffSec), colBad), maxW), colBad, false + case r.NeedsStaff(): + return []line{plainLine(fmt.Sprintf(a.T("hud.staff_prompt"), sim.StaffSec), colWarn)}, colWarn, false + case lvl.OneWayGates && r.StaffUnlocked: + return []line{plainLine(a.T("hud.unlocked_prompt"), colGood)}, colGood, false + } case level.Toilet: if r.Facility.Active { return []line{plainLine(a.T("hud.toilet_busy"), colDim)}, colGood, true diff --git a/internal/game/script.go b/internal/game/script.go index 5a20c52..3476f0a 100644 --- a/internal/game/script.go +++ b/internal/game/script.go @@ -88,11 +88,20 @@ func parseScript(src string) (*script, error) { return nil, fmt.Errorf("script: okänd riktning %q", f[1]) } st.n, _ = strconv.Atoi(f[2]) - case "shot", "pick", "waitphase": + case "shot", "pick", "waitphase", "urge": if len(f) != 2 { return nil, fmt.Errorf("script: '%s TEXT'", f[0]) } st.arg = f[1] + case "push": // håll en riktning N bildrutor oavsett om spelaren kommer någonstans (t.ex. mot en spärr) + if len(f) != 3 { + return nil, fmt.Errorf("script: 'push DIR N'") + } + st.dir = parseDir(f[1]) + if st.dir == sim.None { + return nil, fmt.Errorf("script: okänd riktning %q", f[1]) + } + st.n, _ = strconv.Atoi(f[2]) case "quit", "waittrain", "waitstop", "waitover": default: return nil, fmt.Errorf("script: okänt kommando %q", f[0]) @@ -128,6 +137,7 @@ type trainWaiter interface { RideAtDest() bool LevelOver() bool MiniPick(text string) bool + ForceUrge(name string) bool TrackPhaseName() string } @@ -201,6 +211,17 @@ func (s *script) tick(g *Game, frame *ebiten.Image) error { if tw, ok := g.scene.(trainWaiter); !ok || tw.LevelOver() || s.counter > 60*120 { s.advance() } + case "urge": + if tw, ok := g.scene.(trainWaiter); ok { + tw.ForceUrge(st.arg) + } + s.advance() + case "push": + virtual.held = st.dir + s.counter++ + if s.counter >= st.n { + s.advance() + } case "waitphase": s.counter++ if tw, ok := g.scene.(trainWaiter); !ok || tw.TrackPhaseName() == st.arg || s.counter > 60*60 { diff --git a/internal/level/level.go b/internal/level/level.go index 79edee7..93f3f64 100644 --- a/internal/level/level.go +++ b/internal/level/level.go @@ -267,6 +267,7 @@ type Level struct { MaxMissedTrains int `json:"maxMissedTrains"` // >0: fler missade tåg än så förlorar banan IndividualCards bool `json:"individualCards"` // alla i sällskapet har egna kort (och biljetter) som spärren kräver 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) 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 @@ -618,6 +619,32 @@ func (l *Level) Solvable() bool { return false } +// InsideGates är rutorna innanför spärrarna: allt som nås från perrongerna +// utan att passera en spärr. Används av spärrar utan återvändo. +func (l *Level) InsideGates() map[Point]bool { + seen := map[Point]bool{} + var queue []Point + for _, t := range PlatformTiles { + for _, p := range l.Find(t) { + seen[p] = true + queue = append(queue, p) + } + } + for len(queue) > 0 { + p := queue[0] + queue = queue[1:] + for _, n := range l.Neighbors(p) { + t := l.At(n) + if seen[n] || !t.Passable() || t == Gate { + continue + } + seen[n] = true + queue = append(queue, n) + } + } + return seen +} + // 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 1b3f130..2e10387 100644 --- a/internal/level/level_test.go +++ b/internal/level/level_test.go @@ -732,3 +732,29 @@ func TestStoryChains(t *testing.T) { t.Errorf("giltig kedja: %v", err) } } + +func TestOneWayGatesAndInside(t *testing.T) { + src := `{"id":"ow","name":{"sv":"x"},"difficulty":1,"destination":{"sv":"x"},"oneWayGates":true, + "grid":["#########","#S.G.P.G#","#########"], + "trains":[{"platform":"P","track":"1","line":{"en":"A"},"destination":{"en":"B"},"correct":true}]}` + l, err := Parse([]byte(src)) + if err != nil { + t.Fatal(err) + } + if !l.OneWayGates { + t.Fatal("flaggan ska läsas") + } + in := l.InsideGates() + want := map[Point]bool{{X: 4, Y: 1}: true, {X: 5, Y: 1}: true, {X: 6, Y: 1}: true} + if len(in) != len(want) { + t.Fatalf("innanför spärrarna: %v", in) + } + for p := range want { + if !in[p] { + t.Errorf("%v ska vara innanför", p) + } + } + if in[Point{X: 3, Y: 1}] || in[Point{X: 1, Y: 1}] { + t.Fatal("spärren och starten är inte innanför") + } +} diff --git a/internal/minigame/minigame_test.go b/internal/minigame/minigame_test.go index 50595dd..7dfeda3 100644 --- a/internal/minigame/minigame_test.go +++ b/internal/minigame/minigame_test.go @@ -409,3 +409,32 @@ func TestRideCrowdLetPass(t *testing.T) { } } } + +func TestPayerPicksSourceAndRefusesPoor(t *testing.T) { + opts := []PayOption{{Cash: true, Yen: 100}, {Name: "Du", Yen: 50}, {Name: "Anna", Member: 1, Yen: 400}, {Name: "Ville", Member: 2, Yen: 0}} + g := NewPayer(300, opts) + if g.Cursor != 2 { + t.Fatalf("markören ska stå på första som har råd, står på %d", g.Cursor) + } + g.Update(Input{MoveX: 1}, 0) // Ville: har inte råd + g.Update(Input{Confirm: true}, 0) + if g.Done() || !g.Refused { + t.Fatal("den som inte har råd ska nekas utan att köpet avslutas") + } + g.Update(Input{MoveX: -1}, 0) + if g.Refused { + t.Fatal("att flytta markören nollställer nekandet") + } + g.Update(Input{Confirm: true}, 0) + if !g.Done() || !g.Success() || g.Chosen == nil || g.Chosen.Name != "Anna" || g.Chosen.Member != 1 { + t.Fatalf("Anna ska betala: %+v", g.Chosen) + } + h := NewPayer(300, opts) + h.Update(Input{Cancel: true}, 0) + if !h.Done() || h.Success() || h.Chosen != nil { + t.Fatal("Esc avbryter utan köp") + } + if NewPayer(1000, opts).Cursor != 0 { + t.Fatal("utan någon som har råd står markören först") + } +} diff --git a/internal/minigame/payer.go b/internal/minigame/payer.go new file mode 100644 index 0000000..5ca9e6d --- /dev/null +++ b/internal/minigame/payer.go @@ -0,0 +1,61 @@ +package minigame + +// Payer är frågan "vem betalar?" vid kiosken när alla i sällskapet har egna +// kort: kontant, spelarens kort eller en följares kort. Pilarna väljer, +// Enter betalar (bara om det räcker), Esc avbryter köpet. Poängen är att +// kunna vara strategisk så att alla har kvar pengar till spärrarna. +type PayOption struct { + Name string // vem (tom för kontant) + Cash bool // kontanter i plånboken + Member int // 0 = spelarens kort, annars följarens index i sällskapet + Yen int // tillgängligt belopp +} + +// Payer är väljaren. +type Payer struct { + base + Price int + Options []PayOption + Cursor int + Chosen *PayOption // vald källa när det lyckats + Refused bool // försökte betala med något som inte räcker +} + +// NewPayer skapar väljaren med markören på första källan som har råd. +func NewPayer(price int, options []PayOption) *Payer { + g := &Payer{Price: price, Options: append([]PayOption(nil), options...)} + for i, o := range g.Options { + if o.Yen >= price { + g.Cursor = i + break + } + } + return g +} + +// CanAfford rapporterar om källan i räcker till priset. +func (g *Payer) CanAfford(i int) bool { return g.Options[i].Yen >= g.Price } + +// Update flyttar markören och tar emot betala/avbryt. +func (g *Payer) Update(in Input, dt float64) { + if g.done || len(g.Options) == 0 { + return + } + if in.Cancel { + g.done = true + return + } + if mv := in.MoveX + in.Move; mv != 0 { + g.Cursor = wrap(g.Cursor+mv, len(g.Options)) + g.Refused = false + } + if in.Confirm { + if !g.CanAfford(g.Cursor) { + g.Refused = true + return + } + o := g.Options[g.Cursor] + g.Chosen = &o + g.done, g.success = true, true + } +} diff --git a/internal/sim/gates_test.go b/internal/sim/gates_test.go new file mode 100644 index 0000000..b75bcd7 --- /dev/null +++ b/internal/sim/gates_test.go @@ -0,0 +1,141 @@ +package sim + +import ( + "testing" + + "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level" + "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/minigame" + "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/rng" +) + +// 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.PathCanEnter(p) && (!r.Level.At(p).IsPlatform() || p == goal) + }) +} + +// stepUntilStill tar ett steg i riktningen och låter rörelsen bli klar. +func stepUntilStill(r *Run, d Dir) { + r.Step(frame, d, false) + for i := 0; i < 120 && r.Player.Moving(); i++ { + r.Step(frame, None, false) + } +} + +// TestKioskAsksWhoPays: med egna kort och fler i sällskapet frågar kiosken +// vem som betalar; den valda följarens kort dras, inte spelarens. +func TestKioskAsksWhoPays(t *testing.T) { + l := loadLevel(t, "007-akihabara") + if !l.IndividualCards || l.Party < 3 { + t.Fatal("007 ska ha egna kort och sällskap") + } + r := NewRun(l, RunOptions{Seed: rng.FromInt(7)}) + r.NPCs = nil + r.Party.nextEventAt = 1e9 + ville := r.Party.Members[2] + ville.Meters.Drink, ville.Urge = 5, Thirsty + walk(t, r, pathTo(r, l.Find(level.Kiosk)[0])) + card, villeCard := r.Wallet.Card, ville.Card + r.Step(frame, None, true) + g, ok := r.Mini.(*minigame.Payer) + if !ok || r.MiniKind != MiniPayer || len(g.Options) != r.Party.Size()+1 || g.Price != KioskPriceYen { + t.Fatalf("kiosken ska fråga vem som betalar: kind=%v", r.MiniKind) + } + if !g.Options[0].Cash || g.Options[1].Member != 0 || g.Options[3].Name != ville.Name { + t.Fatalf("källorna: %+v", g.Options) + } + for g.Cursor != 3 { + r.StepMini(frame, minigame.Input{MoveX: 1}) + } + r.StepMini(frame, minigame.Input{Confirm: true}) + if r.Mini != nil || ville.Card != villeCard-KioskPriceYen || r.Wallet.Card != card || ville.Meters.Drink != 100 || r.Served != 1 { + t.Fatalf("Villes kort ska betala: ville=%d (var %d) kort=%d served=%d", ville.Card, villeCard, r.Wallet.Card, r.Served) + } + // Ingen som behöver något: ingen fråga. Avbrutet köp: inget dras. + r.Step(frame, None, true) + if r.Mini != nil { + t.Fatal("utan behov ska kiosken inte fråga") + } + anna := r.Party.Members[1] + anna.Meters.Food, anna.Urge, anna.Card = 5, Hungry, 0 + r.Step(frame, None, true) + g = r.Mini.(*minigame.Payer) + for g.Cursor != 2 { // Anna, utan pengar + r.StepMini(frame, minigame.Input{MoveX: 1}) + } + r.StepMini(frame, minigame.Input{Confirm: true}) + if r.Mini == nil || !g.Refused { + t.Fatal("den som inte har råd nekas") + } + r.StepMini(frame, minigame.Input{Cancel: true}) + if r.Mini != nil || anna.Meters.Food > 10 || r.Served != 1 { + t.Fatal("avbrutet köp ska inte dra något") + } + // Kontant betalar. + cash := r.Wallet.Cash + r.Step(frame, None, true) + r.StepMini(frame, minigame.Input{Confirm: true}) // markören står på kontant (Anna har inte råd, kontant står först) + if r.Wallet.Cash != cash-KioskPriceYen || anna.Meters.Food != 100 { + t.Fatalf("kontant: cash=%d (var %d) food=%.0f", r.Wallet.Cash, cash, anna.Meters.Food) + } +} + +// TestOneWayGatesNeedStaff: i Ueno släpper spärren inte ut en som checkat +// in; Enter vid spärren kallar på personal, sedan får man gå ut, och går man +// in igen dras avgiften igen. +func TestOneWayGatesNeedStaff(t *testing.T) { + l := loadLevel(t, "005-ueno") + if !l.OneWayGates { + t.Fatal("005 ska ha spärrar utan återvändo") + } + r := NewRun(l, RunOptions{Seed: rng.FromInt(5)}) + r.NPCs = nil + r.Party.nextEventAt = 1e9 + plat := l.CorrectPlatform() + start := r.Wallet.Card + walk(t, r, pathTo(r, plat)) + if !r.CheckedIn() || !r.NeedsStaff() || r.GatePasses != 1 || r.Wallet.Card != start-l.FareYen { + 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 + 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 + 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) + } + r.Step(frame, None, true) // kalla på personal + if !r.Staff.Active { + t.Fatal("Enter vid spärren ska kalla på personal") + } + for i := 0; i < int(StaffSec*60)+5 && r.Staff.Active; i++ { + r.Step(frame, None, false) + } + 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 { + 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 + 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 + 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) + } + // Utan flaggan är det fritt fram. + l1 := loadLevel(t, "001-flygplatsen") + r1 := NewRun(l1, RunOptions{Seed: rng.FromInt(1)}) + walk(t, r1, l1.Path(r1.Player.Tile, l1.CorrectPlatform(), r1.PathCanEnter)) + if r1.CheckedIn() || r1.NeedsStaff() { + t.Fatal("utan oneWayGates ska ingen vara incheckad") + } +} diff --git a/internal/sim/nav_test.go b/internal/sim/nav_test.go index 57e8c4d..b8b1001 100644 --- a/internal/sim/nav_test.go +++ b/internal/sim/nav_test.go @@ -17,7 +17,7 @@ func boardAt(t *testing.T, r *Run, tile level.Tile) *level.Train { r.NPCs = nil // folket ska inte stå i vägen för testboten r.Party.nextEventAt = 1e9 l := r.Level - path := l.Path(r.Player.Tile, l.Find(tile)[0], r.PathCanEnter) + path := pathTo(r, l.Find(tile)[0]) if path == nil { t.Fatalf("ingen väg till perrong %c", tile) } diff --git a/internal/sim/run.go b/internal/sim/run.go index 5272eeb..fa96418 100644 --- a/internal/sim/run.go +++ b/internal/sim/run.go @@ -47,6 +47,16 @@ type Run struct { timeLimit int // resans tidsgräns (första delbanans), 0 = ingen maxMissed int // resans gräns för missade tåg (första delbanans) + // Spärrar utan återvändo (level.OneWayGates): incheckad släpps inte ut + // förrän personalen låst upp korten. + inside map[level.Point]bool // rutorna innanför spärrarna + checkedIn bool + gateFromIn bool // spärrrutan man står på nåddes inifrån (på väg ut) + StaffUnlocked bool + Staff StaffState + StaffCalls int + NoExitAt float64 // när spärren senast vägrade släppa ut (< 0 = aldrig) + // Språk: ordboken, spelarens kunskap (delas mellan banor) och hjälpen. Dict *lang.Dictionary Know *lang.Knowledge @@ -129,6 +139,7 @@ const ( const ( FlashWomenOnly = "hud.women_only" FlashTransfer = "hud.transfer_done" + FlashStaff = "hud.staff_unlocked" FlashEarly = "hud.early_board" FlashBump = "hud.bump" ) @@ -177,6 +188,7 @@ const ( MiniCrowd MiniRide MiniCarPick + MiniPayer ) // Förlustorsaker (textnycklar). @@ -208,6 +220,18 @@ type FacilityState struct { Count int } +// StaffState är ett pågående personalanrop vid spärren (tar tid, avbryts +// om man går). +type StaffState struct { + Active bool + At level.Point + Remaining float64 + Total float64 +} + +// StaffSec är hur länge man väntar på personalen vid spärren. +const StaffSec = 10.0 + // RunOptions styr en banomgång. type RunOptions struct { Speed float64 // rutor per sekund (0 = DefaultSpeed) @@ -280,6 +304,8 @@ func NewRun(l *level.Level, o RunOptions) *Run { timeLimit: l.TimeLimitSec, maxMissed: l.MaxMissedTrains, TransferAt: -1, + inside: l.InsideGates(), + NoExitAt: -1, Party: NewParty(l.Party, l.SpawnPoint(), o.Seed.Derive("party")), baseSpeed: o.Speed, NoticeAt: -1, @@ -375,6 +401,8 @@ func (r *Run) startLeg(l *level.Level) { r.Mini, r.MiniKind, r.PendingTrain, r.CarChosen = nil, MiniNone, nil, nil r.Help, r.Facility = HelpState{}, FacilityState{} r.DeniedAt, r.BumpedAt, r.WrongLane = -1, -1, false + r.inside = l.InsideGates() + r.checkedIn, r.StaffUnlocked, r.gateFromIn, r.Staff = false, false, false, StaffState{} r.parSec += parFor(l) r.TransferAt = r.Time r.flash(FlashTransfer) @@ -418,6 +446,10 @@ func (r *Run) CanEnter(p level.Point) bool { r.DeniedAt = r.Time return false } + if r.NeedsStaff() && r.inGate() && r.gateFromIn && t != level.Gate && !r.inside[p] { + r.NoExitAt = r.Time // incheckad: spärren släpper inte ut en + return false + } for _, n := range r.NPCs { if n.Occupies(p) { r.BumpedAt = r.Time @@ -427,6 +459,48 @@ func (r *Run) CanEnter(p level.Point) bool { return true } +// NeedsStaff rapporterar om man är incheckad i en spärr utan återvändo och +// behöver personalen för att komma ut. +func (r *Run) NeedsStaff() bool { return r.Level.OneWayGates && r.checkedIn && !r.StaffUnlocked } + +// CheckedIn rapporterar om spelaren har checkat in i spärren (och inte åkt). +func (r *Run) CheckedIn() bool { return r.checkedIn } + +// callStaff trycker på knappen vid spärren: efter StaffSec låser personalen +// upp korten så att man får gå ut. +func (r *Run) callStaff() { + if !r.NeedsStaff() || r.Staff.Active { + return + } + r.Staff = StaffState{Active: true, At: r.Player.Tile, Remaining: StaffSec, Total: StaffSec} +} + +func (r *Run) stepStaff(dt float64) { + if !r.Staff.Active { + return + } + if r.Player.Moving() || r.Player.Tile != r.Staff.At { + r.Staff = StaffState{} + return + } + r.Staff.Remaining -= dt + if r.Staff.Remaining > 0 { + return + } + r.Staff = StaffState{} + r.StaffUnlocked = true + r.StaffCalls++ + r.flash(FlashStaff) +} + +// StaffProgress är 0..1 för ett pågående personalanrop. +func (r *Run) StaffProgress() float64 { + if !r.Staff.Active || r.Staff.Total <= 0 { + return 0 + } + return 1 - r.Staff.Remaining/r.Staff.Total +} + func (r *Run) canPassGate() bool { if r.opts.FreeGates { return true // JR Pass: genväg genom alla spärrar @@ -538,11 +612,21 @@ func (r *Run) Step(dt float64, want Dir, board bool) { } r.stepHelp(dt) r.stepFacility(dt) + r.stepStaff(dt) r.stepReading() r.stepNPCs(dt) r.checkLimits() if r.Player.Tile != before && r.inGate() && !wasGate { + r.gateFromIn = r.inside[before] // på väg ut (eller bara på genomresa utifrån) + } + // Avgiften dras när man passerar spärren in i det betalda området, inte + // när man står på spärrrutan: spärrar som ligger i en gång kan passeras + // utifrån utan att det kostar. + if r.Player.Tile != before && r.inside[r.Player.Tile] && !r.inside[before] { r.GatePasses++ + if r.Level.OneWayGates { + r.checkedIn = true // incheckad: spärren släpper inte ut en förrän man åkt + } if !r.Level.TicketRequired && !r.opts.FreeGates { r.Wallet.PayCard(r.Level.FareYen) if r.Level.IndividualCards { @@ -558,8 +642,13 @@ func (r *Run) Step(dt float64, want Dir, board bool) { } } } + if r.Level.OneWayGates && r.Player.Tile != before && wasGate && r.gateFromIn && !r.inside[r.Player.Tile] && !r.inGate() { + r.checkedIn, r.StaffUnlocked = false, false // ute igen (efter personalens upplåsning); ny inpassering kostar igen + } if board && !r.Player.Moving() { switch r.Level.At(r.Player.Tile) { + case level.Gate: + r.callStaff() case level.Toilet: r.useToilet() case level.Kiosk: @@ -791,6 +880,10 @@ func (r *Run) StepMini(dt float64, in minigame.Input) { r.Party.Members[g.Member].Card += g.Amount r.TopUps++ } + case *minigame.Payer: + if g.Success() && g.Chosen != nil { + r.purchaseAtKiosk(r.payFrom(*g.Chosen)) + } case *minigame.Ticket: if !g.Success() || g.Chosen == nil { break @@ -882,10 +975,58 @@ func (r *Run) stepFacility(dt float64) { r.Facility = FacilityState{} } -// buyAtKiosk köper mat och dryck åt alla som behöver, betalat med kortet. -// Med liten seedad otur blir maten dålig: matförgiftning efter banan. +// buyAtKiosk köper mat och dryck åt alla som behöver. Med egna kort och +// fler i sällskapet frågar kiosken först vem som betalar (minigame.Payer), +// annars dras det från spelarens kort. func (r *Run) buyAtKiosk() { - n := r.Party.BuyAtKiosk(r.Wallet.PayCard) + needs := r.Party.NeedsKiosk() + if len(needs) == 0 { + return + } + if r.Level.IndividualCards && r.Party.Size() > 1 { + r.Mini, r.MiniKind = minigame.NewPayer(KioskPriceYen*len(needs), r.payOptions()), MiniPayer + return + } + r.purchaseAtKiosk(r.Wallet.PayCard) +} + +// payOptions är källorna man kan betala kiosken med: kontant, spelarens +// kort och varje följares kort. +func (r *Run) payOptions() []minigame.PayOption { + card := r.Wallet.Card + if !r.Wallet.CardUsable() { + card = 0 + } + opts := []minigame.PayOption{{Cash: true, Yen: r.Wallet.Cash}, {Name: r.Party.Members[0].Name, Member: 0, Yen: card}} + for i, m := range r.Party.Followers() { + opts = append(opts, minigame.PayOption{Name: m.Name, Member: i + 1, Yen: m.Card}) + } + return opts +} + +// payFrom ger betalfunktionen för en vald källa. +func (r *Run) payFrom(o minigame.PayOption) func(int) bool { + switch { + case o.Cash: + return r.Wallet.PayCash + case o.Member == 0: + return r.Wallet.PayCard + } + m := r.Party.Members[o.Member] + return func(yen int) bool { + if m.Card < yen { + return false + } + m.Card -= yen + r.Wallet.Spent += yen + return true + } +} + +// purchaseAtKiosk genomför köpet med en betalfunktion. Med liten seedad +// otur blir maten dålig: matförgiftning efter banan. +func (r *Run) purchaseAtKiosk(pay func(int) bool) { + n := r.Party.BuyAtKiosk(pay) if n == 0 { return } diff --git a/internal/sim/run_test.go b/internal/sim/run_test.go index 5f16f20..5d43141 100644 --- a/internal/sim/run_test.go +++ b/internal/sim/run_test.go @@ -155,6 +155,25 @@ func walk(t *testing.T, r *Run, path []level.Point) { } } +// throughGate kapar en väg vid första rutan innanför spärrarna – där dras +// avgiften (inte på spärrrutan). +func throughGate(l *level.Level, path []level.Point) []level.Point { + in := l.InsideGates() + for i, p := range path { + if in[p] { + return path[:i+1] + } + } + if len(path) > 0 { // vägen slutar på spärren: kliv in på rutan innanför + for _, n := range l.Neighbors(path[len(path)-1]) { + if in[n] { + return append(path, n) + } + } + } + return path +} + // waitForTrain står still tills tåget vid perrongen är inne. func waitForTrain(t *testing.T, r *Run) *level.Train { t.Helper() @@ -698,12 +717,7 @@ func TestPhoneSpeedRescanAndBattery(t *testing.T) { } gate := l.Find(level.Gate)[0] path := l.Path(r.Player.Tile, gate, r.PathCanEnter) - for i, p := range path { // stanna på den första spärrutan – där sker skanningen - if l.At(p) == level.Gate { - path = path[:i+1] - break - } - } + path = throughGate(l, path) // in i det betalda området: där dras avgiften och sker skanningen walk(t, r, path) rescans[r.Rescans]++ if r.Rescans == 1 { @@ -873,12 +887,7 @@ func TestIndividualCardsAtGateAndMachine(t *testing.T) { // Spärren drar från allas kort. before := r.Wallet.Spent path := l1.Path(r.Player.Tile, gate, r.PathCanEnter) - for i, p := range path { - if l1.At(p) == level.Gate { - path = path[:i+1] - break - } - } + path = throughGate(l1, path) // in i det betalda området: där dras avgiften och sker skanningen walk(t, r, path) if anna.Card != 1000-l1.FareYen || r.Wallet.Card != StartCardYen-l1.FareYen || r.Wallet.Spent != before+2*l1.FareYen { t.Fatalf("efter spärren: anna=%d egen=%d spent=%d", anna.Card, r.Wallet.Card, r.Wallet.Spent) @@ -1039,12 +1048,7 @@ func TestRunOptionsFromProgression(t *testing.T) { r.Step(frame, None, false) } path := l.Path(r.Player.Tile, gate, r.PathCanEnter) - for i, p := range path { - if l.At(p) == level.Gate { - path = path[:i+1] - break - } - } + path = throughGate(l, path) // in i det betalda området: där dras avgiften och sker skanningen walk(t, r, path) if r.Wallet.Spent != 0 || r.GatePasses != 1 { t.Fatalf("gratis spärr: spent=%d passes=%d", r.Wallet.Spent, r.GatePasses) diff --git a/resources/data/levels/005-ueno.json b/resources/data/levels/005-ueno.json index 0f02b1f..caf975c 100644 --- a/resources/data/levels/005-ueno.json +++ b/resources/data/levels/005-ueno.json @@ -134,5 +134,6 @@ } ], "station": "ueno", - "to": "akihabara" + "to": "akihabara", + "oneWayGates": true } diff --git a/resources/data/levels/007-akihabara.json b/resources/data/levels/007-akihabara.json index 5bb2739..ba1e5f7 100644 --- a/resources/data/levels/007-akihabara.json +++ b/resources/data/levels/007-akihabara.json @@ -64,9 +64,9 @@ }, { "text": { - "en": "\u003c- Track 3 Hibiya Line for Naka-Meguro Track 4 Hibiya Line for Kita-Senju -\u003e", + "en": "<- Track 3 Hibiya Line for Naka-Meguro Track 4 Hibiya Line for Kita-Senju ->", "ja": "← 3番線 日比谷線 中目黒方面 4番線 日比谷線 北千住方面 →", - "sv": "\u003c- Spår 3 Hibiya-linjen mot Naka-Meguro Spår 4 Hibiya-linjen mot Kita-Senju -\u003e" + "sv": "<- Spår 3 Hibiya-linjen mot Naka-Meguro Spår 4 Hibiya-linjen mot Kita-Senju ->" }, "x": 9, "y": 11, @@ -175,5 +175,6 @@ "terminus": "tokyo", "track": "1" } - ] -} \ No newline at end of file + ], + "oneWayGates": true +} diff --git a/resources/data/levels/007b-ueno.json b/resources/data/levels/007b-ueno.json index 0d4778c..9a8d269 100644 --- a/resources/data/levels/007b-ueno.json +++ b/resources/data/levels/007b-ueno.json @@ -11,23 +11,23 @@ "fareYen": 170, "grid": [ "#################################", - "#S.#..\u003c..\u003c..\u003c..\u003c..\u003c..\u003c..\u003c..\u003c..###", - "#..#..\u003e..\u003e..\u003e..\u003e..\u003e..\u003e..\u003e..\u003e..###", - "#\u003c\u003e################\u003c\u003e#######\u003c\u003e###", - "#..#..\u003ci.\u003c..\u003c..\u003c..#..#..\u003c..#..###", - "#..#..\u003e..\u003e..\u003e..\u003eW.#..#..\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..\u003c..#..#..#..\u003c..#..\u003c..###", - "#..#..\u003e..\u003e..#..#..#..\u003e..#..\u003e..###", - "#\u003c\u003e#\u003c\u003e#######\u003c\u003e#\u003c\u003e#\u003c\u003e#\u003c\u003e####\u003c\u003e###", - "#..#K.\u003c..\u003c..#..#..\u003c.i#.T\u003c..\u003c..###", - "#..#i.\u003e..\u003e..#..#..\u003e..#..\u003e..\u003e..###", - "#\u003c\u003e#\u003c\u003e####\u003c\u003e#\u003c\u003e#\u003c\u003e##########\u003c\u003e###", - "#G.\u003c..\u003c..\u003c..#..\u003cG.\u003c..\u003c..\u003c..\u003c.G###", - "#pG\u003e..\u003e..\u003e..#..GQG\u003e..\u003e..\u003e..\u003eGP###", + "#S.#..<..<..<..<..<..<..<..<..###", + "#..#..>..>..>..>..>..>..>..>..###", + "#<>################<>#######<>###", + "#..#....>..>..>W.#..#..>..#..###", + "#<>#<>##########<>#<>#<>####<>###", + "#..<..#..<..<..#..#..#..<..<..###", + "#..>..#..>..>..#..#..#..>..>..###", + "#######<>#<>#<>#<>#<>#<>#########", + "#..#..<..<..#..#..#..<..#..<..###", + "#..#..>..>..#..#..#..>..#..>..###", + "#<>#<>#######<>#<>#<>#<>####<>###", + "#..#K.<..<..#..#..<.i#.T<..<..###", + "#..#i.>..>..#..#..>..#..>..>..###", + "#<>#<>####<>#<>#<>##########<>###", + "#G.<..<..<..#....>..>..#..GQG>..>..>..>GP###", "#################################", "#################################", "#################################" @@ -56,9 +56,9 @@ }, { "text": { - "en": "\u003c- Track 2 Ginza Line for Shibuya Track 1 Ginza Line for Asakusa -\u003e", + "en": "<- Track 2 Ginza Line for Shibuya Track 1 Ginza Line for Asakusa ->", "ja": "← 2番線 銀座線 渋谷方面 1番線 銀座線 浅草方面 →", - "sv": "\u003c- Spår 2 Ginza-linjen mot Shibuya Spår 1 Ginza-linjen mot Asakusa -\u003e" + "sv": "<- Spår 2 Ginza-linjen mot Shibuya Spår 1 Ginza-linjen mot Asakusa ->" }, "x": 20, "y": 13, @@ -144,5 +144,6 @@ "terminus": "kita-senju", "track": "3" } - ] -} \ No newline at end of file + ], + "oneWayGates": true +} diff --git a/resources/data/levels/008-tokyo-hemresa.json b/resources/data/levels/008-tokyo-hemresa.json index 38fe937..084e3df 100644 --- a/resources/data/levels/008-tokyo-hemresa.json +++ b/resources/data/levels/008-tokyo-hemresa.json @@ -99,9 +99,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, @@ -210,5 +210,6 @@ "terminus": "takao", "track": "1" } - ] -} \ No newline at end of file + ], + "oneWayGates": true +} diff --git a/resources/data/levels/008b-nippori.json b/resources/data/levels/008b-nippori.json index e34a47d..6794b52 100644 --- a/resources/data/levels/008b-nippori.json +++ b/resources/data/levels/008b-nippori.json @@ -57,9 +57,9 @@ }, { "text": { - "en": "\u003c- Track 2 Keisei Line for Keisei-Ueno Track 1 Keisei Line for Narita Airport -\u003e", + "en": "<- Track 2 Keisei Line for Keisei-Ueno Track 1 Keisei Line for Narita Airport ->", "ja": "← 2番線 京成本線 京成上野方面 1番線 京成本線 成田空港方面 →", - "sv": "\u003c- Spår 2 Keisei-linjen mot Keisei-Ueno Spår 1 Keisei-linjen mot Narita flygplats -\u003e" + "sv": "<- Spår 2 Keisei-linjen mot Keisei-Ueno Spår 1 Keisei-linjen mot Narita flygplats ->" }, "x": 13, "y": 15, @@ -146,5 +146,6 @@ "terminus": "ueno", "track": "11" } - ] -} \ No newline at end of file + ], + "oneWayGates": true +} diff --git a/resources/text/en.json b/resources/text/en.json index 26d17b4..4bfea50 100644 --- a/resources/text/en.json +++ b/resources/text/en.json @@ -223,5 +223,16 @@ "ending.none": "none", "ending.story_done": "Story complete! Endless mode and New Game+ are up next.", "ending.hint": "Enter: continue Esc: level map", - "ending.credits": "Metro Puzzle JP\n\nIdea and game design\nBjörn\n\nCode and pixels\nClaude (Anthropic)\n\nEngine\nEbitengine\n\nFont\nPixelMplus (M+ FONT LICENSE)\n\nThe trains, the signs and the chatter\nTokyo\n\nThanks for riding!\nまたね" + "ending.credits": "Metro Puzzle JP\n\nIdea and game design\nBjörn\n\nCode and pixels\nClaude (Anthropic)\n\nEngine\nEbitengine\n\nFont\nPixelMplus (M+ FONT LICENSE)\n\nThe trains, the signs and the chatter\nTokyo\n\nThanks for riding!\nまたね", + "mini.payer.title": "Who pays?", + "mini.payer.price": "To pay: %d¥ (food and drink for everyone who needs it)", + "mini.payer.cash": "Cash", + "mini.payer.card": "card", + "mini.payer.short": "Not enough - pick someone else.", + "mini.payer.hint": "Arrows: choose Enter: pay Esc: cancel", + "hud.no_exit": "The gate will not let you out until you have travelled. Enter here calls the staff (%.0f s).", + "hud.staff_prompt": "Enter: call the staff to get out (%.0f s)", + "hud.staff_busy": "Staff on the way...", + "hud.staff_unlocked": "The staff unlocked the cards - you can leave.", + "hud.unlocked_prompt": "Cards unlocked: you can leave. Entering again costs again." } diff --git a/resources/text/sv.json b/resources/text/sv.json index cab3156..57acfc0 100644 --- a/resources/text/sv.json +++ b/resources/text/sv.json @@ -223,5 +223,16 @@ "ending.none": "inga", "ending.story_done": "Storyn är klar! Oändligt läge och New Game+ kommer härnäst.", "ending.hint": "Enter: vidare Esc: bankartan", - "ending.credits": "Metro Puzzle JP\n\nIdé och speldesign\nBjörn\n\nKod och pixlar\nClaude (Anthropic)\n\nMotor\nEbitengine\n\nTypsnitt\nPixelMplus (M+ FONT LICENSE)\n\nTågen, skyltarna och sorlet\nTokyo\n\nTack för resan!\nまたね" + "ending.credits": "Metro Puzzle JP\n\nIdé och speldesign\nBjörn\n\nKod och pixlar\nClaude (Anthropic)\n\nMotor\nEbitengine\n\nTypsnitt\nPixelMplus (M+ FONT LICENSE)\n\nTågen, skyltarna och sorlet\nTokyo\n\nTack för resan!\nまたね", + "mini.payer.title": "Vem betalar?", + "mini.payer.price": "Att betala: %d¥ (mat och dryck åt alla som behöver)", + "mini.payer.cash": "Kontant", + "mini.payer.card": "kort", + "mini.payer.short": "Det räcker inte - välj någon annan.", + "mini.payer.hint": "Pilar: välj Enter: betala Esc: avbryt", + "hud.no_exit": "Spärren släpper inte ut dig förrän du åkt. Enter här kallar på personal (%.0f s).", + "hud.staff_prompt": "Enter: kalla på personal för att komma ut (%.0f s)", + "hud.staff_busy": "Personalen kommer...", + "hud.staff_unlocked": "Personalen låste upp korten - du kan gå ut.", + "hud.unlocked_prompt": "Korten är upplåsta: du kan gå ut. Ny inpassering kostar igen." } diff --git a/tools/shots/main.go b/tools/shots/main.go index 01f3a38..5b51364 100644 --- a/tools/shots/main.go +++ b/tools/shots/main.go @@ -130,6 +130,10 @@ func buildShots(levelsDir string) error { if err != nil { return err } + toKiosk7, err := moves(l7, l7.SpawnPoint(), l7.Find(level.Kiosk)[0]) + 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"} @@ -154,13 +158,17 @@ func buildShots(levelsDir string) error { {"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"}}, + {"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"}}, } return nil } // moves gör om kortaste vägen mellan två rutor till "move DIR N"-steg. func moves(l *level.Level, from, to level.Point) (string, error) { - path := l.Path(from, to, l.CanEnter) + path := l.Path(from, to, func(p level.Point) bool { // andra perronger är inga genvägar (spärrar utan återvändo) + return l.CanEnter(p) && (!l.At(p).IsPlatform() || p == to) + }) if path == nil { return "", fmt.Errorf("ingen väg %v -> %v i %s", from, to, l.ID) }