Files
metro-puzzle-jp/internal/save/save.go
claude f762f9554e
All checks were successful
release / build-release (push) Successful in 1m34s
M9: navigeringslägen, linjenät och terminal
internal/nav med resources/data/network.json (76 stationer, 11 linjer,
Yamanote som ringlinje). Tåg bedöms från lineId + terminus mot banans
station/to: rätt, fel håll, för långt bort (fler än 12 stationer) eller
ingen trafik; correct räknas ut för nätkopplade banor, 1-4 rätta tåg.
Tågresan visar linjens riktiga stationer. Nya förlustorsaker "Fel håll"
och "För långt bort". Listläge (nästa tåg nere till vänster) och
skyltläge (+50 % poäng) växlas med N på bankartan och sparas; -mode.
Terminalen: biljettautomaten visar linjetavlan på nätbanor. Perronger
P p Q q R r. Bana 006 Ikebukuro med fyra perronger. Banorna 001-005
kopplade till nätet. Skript: waitover och pick calm; poängraden
radbryts; skärmdumpar uppdaterade.

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

124 lines
3.4 KiB
Go

// Package save läser och skriver sparfilen: kunskap, plånbok, debuffar,
// progression och den pågående omgångens bana + seed. JSON med
// versionsfält så att gamla filer kan migreras.
package save
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"time"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/lang"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/progress"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/sim"
)
// Version är sparfilens formatversion.
const Version = 1
// AppDir är mappnamnet under användarens konfigmapp.
const AppDir = "metro-puzzle-jp"
// RunRef pekar ut en pågående omgång så att den kan återupptas med samma
// slump (minispel, händelser, läsning).
type RunRef struct {
LevelID string `json:"levelId"`
Seed [2]uint64 `json:"seed"`
Started time.Time `json:"started"`
}
// State är allt som sparas.
type State struct {
Version int `json:"version"`
SavedAt time.Time `json:"savedAt"`
Know *lang.Knowledge `json:"knowledge"`
Wallet *sim.Wallet `json:"wallet"`
Profile *sim.Profile `json:"profile"`
Progress *progress.Progress `json:"progress"`
Current *RunRef `json:"current,omitempty"`
NavMode string `json:"navMode,omitempty"` // "list" (standard) eller "signs"
}
// New är ett nytt spel.
func New() *State {
return &State{Version: Version, Know: lang.NewKnowledge(), Wallet: sim.NewWallet(), Profile: &sim.Profile{}, Progress: progress.New()}
}
// DefaultPath är sparfilens plats: ~/.config/metro-puzzle-jp/save.json på
// Linux, %AppData%\metro-puzzle-jp\save.json på Windows.
func DefaultPath() (string, error) {
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, AppDir, "save.json"), nil
}
// Load läser sparfilen. Saknas den returneras ett nytt spel (och exists=false).
func Load(path string) (st *State, exists bool, err error) {
b, err := os.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
return New(), false, nil
}
if err != nil {
return nil, false, err
}
var s State
if err := json.Unmarshal(b, &s); err != nil {
return nil, true, fmt.Errorf("sparfilen %s är trasig: %w", path, err)
}
if err := migrate(&s); err != nil {
return nil, true, err
}
return &s, true, nil
}
// migrate lyfter äldre versioner till den aktuella och fyller i luckor.
func migrate(s *State) error {
if s.Version > Version {
return fmt.Errorf("sparfilen är från en nyare version (%d > %d)", s.Version, Version)
}
// Version 0 (saknat fält) och 1 har samma form; här läggs framtida steg.
s.Version = Version
if s.Know == nil {
s.Know = lang.NewKnowledge()
}
if s.Know.Words == nil {
s.Know.Words = map[string]bool{}
}
if s.Know.Progress == nil {
s.Know.Progress = map[string]int{}
}
if s.Wallet == nil {
s.Wallet = sim.NewWallet()
}
if s.Profile == nil {
s.Profile = &sim.Profile{}
}
if s.Progress == nil {
s.Progress = progress.New()
}
return nil
}
// Store skriver sparfilen atomiskt (tempfil + byte av namn).
func Store(path string, s *State) error {
s.Version = Version
s.SavedAt = time.Now()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
tmp := path + ".tmp"
if err := os.WriteFile(tmp, b, 0o644); err != nil {
return err
}
return os.Rename(tmp, path)
}