Files
metro-puzzle-jp/tools/shots/main.go
claude daa2089eda
All checks were successful
release / build-release (push) Successful in 1m49s
Inlärning (framsteg, stjärnor, ordlista) och M4: lämmeltåget
Inlärning enligt Björns regler:
- lang.Knowledge: framsteg per ord (3 läsningar = känt), språkpoäng,
  köp av valfria ord (kostnad = tier), Award per stjärna
- sim.Run: RunOptions med seed; stjärnor 1–3 mot par (parSec eller
  kortaste vägen × 0,5 s + 20 s); ≥2 stjärnor lär resmålets ord; att
  stanna på skylt/perrong ger seedad 35 % chans till framsteg per okänt ord
- game: ordlista (L) med kända ord, framsteg och kostnad; stjärnor, poäng
  och inlärt resmål på resultatskärmen; framstegsnotis; flaggor -points/-seed

M4 lämmeltåget:
- sim.Party: medlemmar med mat/dryck/toa-mätare, behov under tröskel,
  seedade händelser var 45 s / antal (toa, törst, hunger, distraktion),
  fartstraff, toalett (W, 3 s/person) och kiosk (K, 150 ¥/person),
  följare på ledarens spår
- level: rutor W och K, party och parSec i bandatan; banor med sällskap
  måste ha toalett och kiosk (testas); 001 har 2, 002 har 3 i sällskapet
- game: följare med färgton, behovsmarkering över huvudet, mätarpanel,
  facilitetsrutor med förlopp; genart ritar toalett och kiosk
- skriptläget: move stannar exakt på målrutan även när farten inte är
  bildruteexakt (sällskapets fartstraff), stall-skydd mot väggar
- docs/README/wiki/spelbeskrivning uppdaterade

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-23 21:29:09 +02:00

152 lines
4.0 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.

// 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", "改札,番線,方面", "-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"}},
{"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)
}