M0: skelett, byggkedja och dokumentation för JP Subway Pussel

- Go 1.27 + Ebitengine v2.10, intern upplösning 320x180 med heltalsskalning
- internal/res (resursuppslag), internal/rng (seedad slump), internal/level
  (banformat, validering, BFS-lösbarhet) med tester; internal/archtest
  kontrollerar att bara presentationslagret beror på Ebitengine
- tools/build: debug -> build/debug/, release -> build/release/<os>-<arch>/
  med resources/ bredvid binären; Windows korsbyggs utan C-verktyg
- VS Code tasks (build/test/release) och launch (dlv)
- Första banan 001-flygplatsen, sv/en-texter, skyltordbok, placeholder-sprite
- doc/: spelbeskrivning (renskriven), mål och tester, roadmap med teknikval
  (M0–M12), plan.md; README med bygg- och beroendeinformation

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 18:52:10 +02:00
commit c1f9546af6
32 changed files with 2165 additions and 0 deletions

76
internal/res/res_test.go Normal file
View File

@@ -0,0 +1,76 @@
package res
import (
"path/filepath"
"strings"
"testing"
)
func existsIn(paths ...string) func(string) bool {
set := map[string]bool{}
for _, p := range paths {
set[filepath.Clean(p)] = true
}
return func(p string) bool { return set[filepath.Clean(p)] }
}
func TestLocate_EnvWins(t *testing.T) {
got, err := Locate("/opt/custom", "/bin", "/work", existsIn("/opt/custom", "/bin/resources"))
if err != nil || got != "/opt/custom" {
t.Fatalf("got %q, %v; want /opt/custom", got, err)
}
}
func TestLocate_EnvMissingIsError(t *testing.T) {
_, err := Locate("/nope", "/bin", "/work", existsIn("/bin/resources"))
if err == nil || !strings.Contains(err.Error(), EnvVar) {
t.Fatalf("expected error mentioning %s, got %v", EnvVar, err)
}
}
func TestLocate_ExeDirBeforeCwd(t *testing.T) {
got, err := Locate("", "/app", "/work", existsIn("/app/resources", "/work/resources"))
if err != nil || got != filepath.Join("/app", DirName) {
t.Fatalf("got %q, %v", got, err)
}
}
func TestLocate_WalksUpFromCwd(t *testing.T) {
// Som vid `go run`/dlv: binären i en temp-mapp, cwd djupt inne i repot.
got, err := Locate("", "/tmp/go-build123", "/repo/internal/level", existsIn("/repo/resources"))
if err != nil || got != filepath.Join("/repo", DirName) {
t.Fatalf("got %q, %v", got, err)
}
}
func TestLocate_NothingFound(t *testing.T) {
_, err := Locate("", "/a", "/b/c", existsIn())
if err == nil {
t.Fatal("expected error")
}
}
// TestDir_RealRepo kör mot det riktiga repot: testet körs med cwd = paketmappen,
// så uppsökningen måste hitta <repo>/resources.
func TestDir_RealRepo(t *testing.T) {
d, err := Dir()
if err != nil {
t.Fatal(err)
}
if filepath.Base(d) != DirName {
t.Fatalf("unexpected dir %q", d)
}
if _, err := ReadFile("text", "sv.json"); err != nil {
t.Fatalf("resources/text/sv.json ska finnas: %v", err)
}
var m map[string]string
if err := LoadJSON(&m, "text", "sv.json"); err != nil {
t.Fatal(err)
}
if m["game.title"] == "" {
t.Fatal("sv.json saknar game.title")
}
if _, err := LoadImage("sprites", "placeholder.png"); err != nil {
t.Fatal(err)
}
}