M11: oändligt läge och New Game+
All checks were successful
release / build-release (push) Successful in 1m34s

internal/endless lottar mål ur en seed: en av storyns nätkopplade
stationer (aldrig samma två gånger i rad) med ett resmål som minst ett
tåg går rakt till men inte alla; banan kopieras, bedöms om mot nätet,
sällskapet växer var tredje mål och tidsgränsen krymper. O på bankartan
(eller -endless) startar en omgång; klarade mål ger poäng och XP, Enter
ger nästa mål, förlust eller Esc avslutar och omgången hamnar i
sparfilens poänglista (fem bästa). G två gånger startar New Game+: items,
banresultat, pengar och debuffar nollställs, skills, XP och japanskan
behålls, sällskapet är en person större per varv. Docs, wiki, tester och
skärmdumpar uppdaterade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 23:20:12 +02:00
parent 46bd330dfc
commit 36a45563ec
22 changed files with 556 additions and 19 deletions

View File

@@ -9,6 +9,7 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"time"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/lang"
@@ -40,6 +41,38 @@ type State struct {
Progress *progress.Progress `json:"progress"`
Current *RunRef `json:"current,omitempty"`
NavMode string `json:"navMode,omitempty"` // "list" (standard) eller "signs"
Endless []EndlessRecord `json:"endless,omitempty"` // oändligt läge: bästa omgångarna, högst poäng först
}
// EndlessRecord är en avslutad omgång i oändligt läge.
type EndlessRecord struct {
Score int `json:"score"`
Goals int `json:"goals"` // antal klarade mål
Seed [2]uint64 `json:"seed"`
When time.Time `json:"when"`
}
// MaxEndlessRecords är hur många omgångar som sparas i listan.
const MaxEndlessRecords = 5
// AddEndless lägger in en omgång i listan (sorterad på poäng, högst först)
// och rapporterar om den är ny bästa.
func (s *State) AddEndless(r EndlessRecord) (best bool) {
best = len(s.Endless) == 0 || r.Score > s.Endless[0].Score
s.Endless = append(s.Endless, r)
sort.SliceStable(s.Endless, func(i, j int) bool { return s.Endless[i].Score > s.Endless[j].Score })
if len(s.Endless) > MaxEndlessRecords {
s.Endless = s.Endless[:MaxEndlessRecords]
}
return best
}
// BestEndless är bästa omgången (nil om ingen).
func (s *State) BestEndless() *EndlessRecord {
if len(s.Endless) == 0 {
return nil
}
return &s.Endless[0]
}
// New är ett nytt spel.

View File

@@ -76,3 +76,19 @@ func TestDefaultPath(t *testing.T) {
t.Fatalf("path=%q err=%v", p, err)
}
}
func TestEndlessRecords(t *testing.T) {
s := New()
if s.BestEndless() != nil {
t.Fatal("tom lista")
}
if !s.AddEndless(EndlessRecord{Score: 100, Goals: 1}) || s.AddEndless(EndlessRecord{Score: 50, Goals: 1}) || !s.AddEndless(EndlessRecord{Score: 300, Goals: 3}) {
t.Fatal("ny bästa ska rapporteras bara när poängen slår listans topp")
}
for i := 0; i < 10; i++ {
s.AddEndless(EndlessRecord{Score: 10 + i})
}
if len(s.Endless) != MaxEndlessRecords || s.BestEndless().Score != 300 || s.Endless[1].Score != 100 {
t.Fatalf("listan: %+v", s.Endless)
}
}