Files
metro-puzzle-jp/internal/sim/legs_test.go
claude 4ae6a1d24c
All checks were successful
release / build-release (push) Successful in 1m37s
M10: byten, hemresan och levelcheck
Resor med byten: en bana kan ha next som pekar på en delbana (kind leg,
syns inte på bankartan). När man klivit av rätt tåg fortsätter samma
omgång på nästa station med klocka, plånbok, sällskap och räknare;
par-tiden adderas, dimma och folk är nya, en Byte!-banner visas och
HUD:en visar resmålet via bytesstationen. Tidsgräns och missade tåg
gäller hela resan. level.ValidateChains kräver sammanhängande kedjor.
Nya resor: 007 Akihabara -> Ueno -> Asakusa och finalen 008 Tokyo (två
våningar) -> Nippori -> Narita flygplats. Slutscen när sista huvudbanan
klaras första gången: planet lyfter, resan summeras, eftertexter;
Progress.StoryCleared sparas; flaggan -ending. tools/levelcheck ritar
banorna och rapporterar problem. HUD-nivån kortas när mätarna tar plats;
poängraden radbryts. Skärmdumpar och docs uppdaterade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 13:37:48 +02:00

102 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sim
import (
"path/filepath"
"testing"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/res"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/rng"
)
// loadChain laddar alla banor och ger resan som börjar med id.
func loadChain(t *testing.T, id string) []*level.Level {
t.Helper()
dir, err := res.Path("data", "levels")
if err != nil {
t.Fatal(err)
}
all, err := level.LoadAll(dir)
if err != nil {
t.Fatal(err)
}
c := level.ChainOf(all, id)
if len(c) < 2 {
t.Fatalf("%s är ingen resa med byten: %v", id, c)
}
return c
}
// TestJourneyWithTransfer: bana 007 fortsätter på Ueno efter första tåget –
// klockan, plånboken och sällskapet följer med, par-tiden adderas, och
// resan vinns först när andra tåget nått Asakusa.
func TestJourneyWithTransfer(t *testing.T) {
c := loadChain(t, "007-akihabara")
head, leg := c[0], c[1]
r := NewRun(head, RunOptions{Seed: rng.FromInt(7), Chain: c[1:]})
if r.Legs() != 2 || r.LegNo() != 1 || r.IsLastLeg() || r.FinalDestination().Get("ja") != "浅草" || r.TimeLimit() != head.TimeLimitSec {
t.Fatalf("resans start: legs=%d no=%d final=%s limit=%d", r.Legs(), r.LegNo(), r.FinalDestination().Get("ja"), r.TimeLimit())
}
par1 := r.ParSec()
for _, m := range r.Party.Members {
m.Card = 100000 // egna kort med gott om saldo: testet handlar om bytet
}
r.Wallet.Card = 100000
tr := boardAt(t, r, level.Platform)
ride(t, r)
if r.Outcome != Ongoing || r.Level != leg || r.LegNo() != 2 || !r.IsLastLeg() {
t.Fatalf("efter första tåget: outcome=%v level=%s legNo=%d", r.Outcome, r.Level.ID, r.LegNo())
}
if r.TransferAt < 0 || r.FlashKey != FlashTransfer || r.Player.Tile != leg.SpawnPoint() || len(r.Rides) != 1 || r.Rides[0] != tr {
t.Fatalf("bytet: transferAt=%.1f flash=%q tile=%v rides=%d", r.TransferAt, r.FlashKey, r.Player.Tile, len(r.Rides))
}
if r.Time < 10 || r.ParSec() <= par1 || r.Fog.Explored(leg) >= 0.5 {
t.Fatalf("klockan ska fortsätta (%.1f), par adderas (%.0f > %.0f) och dimman är ny (%.2f)", r.Time, r.ParSec(), par1, r.Fog.Explored(leg))
}
if from, to := r.Party.FollowerTiles(1); from != leg.SpawnPoint() || to != leg.SpawnPoint() {
t.Fatalf("sällskapet ska stå på nya starten: %v %v", from, to)
}
boardAt(t, r, level.Platform)
ride(t, r)
if r.Outcome != Won || len(r.Rides) != 2 || r.Score.Total <= 0 {
t.Fatalf("resan ska vinnas efter andra tåget: outcome=%v (%s) rides=%d", r.Outcome, r.LostReason, len(r.Rides))
}
if r.Score.Explore <= 0 || r.Score.Explore > ScoreExploreMax {
t.Fatalf("utforskat är snittet över delbanorna: %d", r.Score.Explore)
}
}
// TestJourneyWrongTrainOnSecondLeg: fel tåg på bytesstationen förlorar resan.
func TestJourneyWrongTrainOnSecondLeg(t *testing.T) {
c := loadChain(t, "007-akihabara")
r := NewRun(c[0], RunOptions{Seed: rng.FromInt(8), Chain: c[1:]})
for _, m := range r.Party.Members {
m.Card = 100000
}
r.Wallet.Card = 100000
boardAt(t, r, level.Platform2) // Yamanote mot Ueno – också rätt
ride(t, r)
if r.Outcome != Ongoing || r.LegNo() != 2 {
t.Fatalf("första tåget: %v", r.Outcome)
}
boardAt(t, r, level.WrongPlatform) // Ginza mot Shibuya: fel håll
ride(t, r)
if r.Outcome != Lost || r.LostReason != LostWrongDirection {
t.Fatalf("outcome=%v reason=%q", r.Outcome, r.LostReason)
}
}
// TestChainLevelsLoad: alla delbanor går att läsa en och en också.
func TestChainLevelsLoad(t *testing.T) {
dir, err := res.Path("data", "levels")
if err != nil {
t.Fatal(err)
}
for _, id := range []string{"007b-ueno", "008b-nippori"} {
l, err := level.LoadFile(filepath.Join(dir, id+".json"))
if err != nil || !l.IsLeg() {
t.Fatalf("%s: %v", id, err)
}
}
}