M8: progression, items, bankarta och sparfil
All checks were successful
release / build-release (push) Successful in 1m46s

- internal/progress: katalog (skills.json 12 noder, items.json 25 items)
  med validering; XP/nivåer (150·n(n+1)/2) och skillpoäng; skillträd med
  krav och kostnad; items per plats med buffar, genvägar (JR Pass, kartor,
  powerbank) och färgton; banresultat och upplåsning (huvudbanor i ordning,
  sidospår efter föräldern); 33 %-regeln som test
- internal/save: versionerad JSON med kunskap, plånbok, debuffar,
  progression och pågående bana + seed; atomisk skrivning; migrering;
  ~/.config/metro-puzzle-jp/save.json resp. %AppData%
- sim: RunOptions får Extra/FogRadius/Landmarks/FreeGates/RevealAll/
  InfiniteBattery; luck påverkar trängselzonen; Fog.RevealAll och extra
  landmärken
- game: bankarta i spelbrädesstil ersätter menyn (⟳ återupptar med sparad
  seed), skillträd (T), garderob (I), butik i kiosken (B), XP/nivå/items/
  upplåsningar på resultatet, spelaren tonas av skins; autospar vid banstart,
  efter banan och vid köp; flaggor -save/-nosave
- level: kind/order/parent/rewards/shop; ny sidobana 002b Kabukicho som
  belönar skinnet Kimono; 001 belönar Salaryman-skinnet; butikssortiment
  på 002/003
- tools/shots: demo-sparfil (build/, aldrig den riktiga), bilder av karta,
  skills, garderob och butik; alla körningar med -nosave
- docs/README/wiki uppdaterade

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 11:29:30 +02:00
parent d466333f01
commit 915c684dc2
46 changed files with 3058 additions and 316 deletions

View File

@@ -17,8 +17,28 @@ import (
"strings"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/save"
)
// demoSave skriver en sparfil med lite progression så att kartan, skills
// och garderoben har något att visa. Används bara av skärmdumparna.
func demoSave(path string) error {
st := save.New()
st.Know.Learn("改札")
st.Know.Learn("出口")
st.Know.Points = 2
st.Progress.RecordLevel("001-flygplatsen", true, 3, 941)
st.Progress.RecordLevel("002-shinjuku", true, 2, 760)
st.Progress.AddXP(600)
st.Progress.Items["skin_salaryman"] = true
st.Progress.Items["geta"] = true
st.Progress.Items["water_bottle"] = true
st.Progress.Equipped["body"] = "skin_salaryman"
st.Progress.Equipped["feet"] = "geta"
st.Progress.Skills["focus1"] = true
return save.Store(path, st)
}
type shot struct {
level string
script string // $OUT ersätts med doc/img-mappen
@@ -29,6 +49,8 @@ type shot struct {
// bilderna överlever ändringar i banorna.
var shots []shot
var demo string
func buildShots(levelsDir string) error {
l, err := level.LoadFile(filepath.Join(levelsDir, "001-flygplatsen.json"))
if err != nil {
@@ -48,6 +70,10 @@ func buildShots(levelsDir string) error {
if err != nil {
return err
}
toKiosk, err := moves(l, l.SpawnPoint(), l.Find(level.Kiosk)[0])
if err != nil {
return err
}
l2, err := level.LoadFile(filepath.Join(levelsDir, "002-shinjuku.json"))
if err != nil {
return err
@@ -68,8 +94,9 @@ func buildShots(levelsDir string) error {
// delvisa översättningen syns; hjälpen lär sedan in nästa ord.
known := []string{"-know", "改札,番線,方面", "-seed", "7"}
shots = []shot{
{"", "wait 10; shot $OUT/menu.png; press l; wait 5; press down; press down; press down; wait 5; shot $OUT/words.png; quit",
[]string{"-know", "改札,出口,トイレ", "-points", "3"}},
{"", "wait 10; shot $OUT/map.png; press l; wait 5; press down; press down; press down; wait 5; shot $OUT/words.png; press l; wait 3; press t; wait 5; press down; wait 3; shot $OUT/skills.png; press t; wait 3; press i; wait 5; shot $OUT/items.png; quit",
[]string{"-know", "改札,出口,トイレ", "-points", "3", "-save", "$DEMO"}},
{"001-flygplatsen", toKiosk + "; press b; wait 5; press down; wait 3; shot $OUT/shop.png; quit", []string{"-seed", "7", "-save", "$DEMO"}},
{"001-flygplatsen", "move right 4; move down 2; wait 5; shot $OUT/level-001.png; quit", nil},
{"002-shinjuku", "move down 6; move right 4; wait 5; shot $OUT/level-002.png; quit", nil},
{"001-flygplatsen", toSign + "; wait 5; shot $OUT/sign.png; press h; wait 200; shot $OUT/sign-help.png; wait 200; shot $OUT/sign-learned.png; " + toGoal +
@@ -124,6 +151,9 @@ func main() {
root := repoRoot()
out := filepath.Join(root, "doc", "img")
must(os.MkdirAll(out, 0o755))
demo = filepath.Join(root, "build", "demo-save.json")
must(os.MkdirAll(filepath.Dir(demo), 0o755))
must(demoSave(demo))
must(buildShots(filepath.Join(root, "resources", "data", "levels")))
build := exec.Command("go", "run", "./tools/build", "-mode", "debug")
@@ -134,12 +164,15 @@ func main() {
if runtime.GOOS == "windows" {
exe += ".exe"
}
_ = demo
for _, s := range shots {
args := []string{"-mute", "-script", strings.ReplaceAll(s.script, "$OUT", out)}
args := []string{"-mute", "-nosave", "-script", strings.ReplaceAll(s.script, "$OUT", out)}
if s.level != "" {
args = append(args, "-level", s.level)
}
args = append(args, s.args...)
for _, a := range s.args {
args = append(args, strings.ReplaceAll(a, "$DEMO", demo))
}
run := exec.Command(exe, args...)
run.Dir, run.Stdout, run.Stderr = filepath.Dir(exe), os.Stdout, os.Stderr
if err := run.Run(); err != nil {