// Genererar dokumentationens skärmdumpar deterministiskt: // // go run ./tools/shots // // Bygger debug-binären, kör den med -script för varje bild och skriver // PNG:er till doc/img/. Vägarna genom banorna räknas ut med BFS från // bandatan. Kör om detta när grafik, UI eller banor ändrats, och committa // bilderna – README och wikin pekar på dem. package main import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level" ) type shot struct { level string script string // $OUT ersätts med doc/img-mappen args []string // extra flaggor till spelet } // shots byggs i main eftersom vägarna räknas ut från bandatan (BFS), så att // bilderna överlever ändringar i banorna. var shots []shot func buildShots(levelsDir string) error { l, err := level.LoadFile(filepath.Join(levelsDir, "001-flygplatsen.json")) if err != nil { return err } sign := l.Find(level.Sign)[1] // skylten vid spärrarna goal := l.Find(level.Platform)[0] toSign, err := moves(l, l.SpawnPoint(), sign) if err != nil { return err } toGoal, err := moves(l, sign, goal) if err != nil { return err } // Skyltbilderna tas med några kända ord så att markeringen och den // delvisa översättningen syns; hjälpen lär sedan in nästa ord. known := []string{"-know", "改札,番線,方面"} shots = []shot{ {"", "wait 10; shot $OUT/menu.png; quit", nil}, {"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 + "; waittrain; wait 30; shot $OUT/platform.png; press enter; wait 5; shot $OUT/result.png; quit", known}, } return nil } // moves gör om kortaste vägen mellan två rutor till "move DIR N"-steg. func moves(l *level.Level, from, to level.Point) (string, error) { path := l.Path(from, to, l.CanEnter) if path == nil { return "", fmt.Errorf("ingen väg %v -> %v i %s", from, to, l.ID) } var parts []string dir, n := "", 0 flush := func() { if n > 0 { parts = append(parts, fmt.Sprintf("move %s %d", dir, n)) } } for i := 1; i < len(path); i++ { d := dirName(path[i-1], path[i]) if d != dir { flush() dir, n = d, 0 } n++ } flush() return strings.Join(parts, "; "), nil } func dirName(a, b level.Point) string { switch { case b.X > a.X: return "right" case b.X < a.X: return "left" case b.Y > a.Y: return "down" default: return "up" } } func main() { root := repoRoot() out := filepath.Join(root, "doc", "img") must(os.MkdirAll(out, 0o755)) must(buildShots(filepath.Join(root, "resources", "data", "levels"))) build := exec.Command("go", "run", "./tools/build", "-mode", "debug") build.Dir, build.Stdout, build.Stderr = root, os.Stdout, os.Stderr must(build.Run()) exe := filepath.Join(root, "build", "debug", "metro-puzzle-jp") if runtime.GOOS == "windows" { exe += ".exe" } for _, s := range shots { args := []string{"-script", strings.ReplaceAll(s.script, "$OUT", out)} if s.level != "" { args = append(args, "-level", s.level) } args = append(args, s.args...) run := exec.Command(exe, args...) run.Dir, run.Stdout, run.Stderr = filepath.Dir(exe), os.Stdout, os.Stderr if err := run.Run(); err != nil { fail(fmt.Errorf("%s: %w", s.script, err)) } } fmt.Println("klart:", out) } func repoRoot() string { dir, _ := os.Getwd() for { if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { return dir } parent := filepath.Dir(dir) if parent == dir { fail(fmt.Errorf("hittade ingen go.mod")) } dir = parent } } func must(err error) { if err != nil { fail(err) } } func fail(err error) { fmt.Fprintln(os.Stderr, "shots:", err) os.Exit(1) }