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.PathOutside(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 // 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, 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) } 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, 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, 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) } 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) } // 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") } }