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

View File

@@ -0,0 +1,58 @@
// Package archtest innehåller arkitekturtester: regler om vilka paket som
// får bero på vad. Körs som vanliga tester med `go test ./...`.
package archtest
import (
"os/exec"
"strings"
"testing"
)
// Paket (relativt modulroten) som får importera Ebitengine. All annan kod
// under internal/ ska gå att testa utan fönster.
var mayUseEbitengine = map[string]bool{
"internal/game": true,
"internal/ui": true,
"internal/audio": true,
}
const ebitengine = "github.com/hajimehoshi/ebiten/"
func TestOnlyPresentationPackagesImportEbitengine(t *testing.T) {
root := goList(t, "-m", "-f", "{{.Dir}}")
module := goList(t, "-m", "-f", "{{.Path}}")
out := goListIn(t, root, "-f", "{{.ImportPath}}|{{join .Deps \",\"}}", "./...")
for _, line := range strings.Split(out, "\n") {
pkg, deps, ok := strings.Cut(line, "|")
if !ok {
continue
}
rel := strings.TrimPrefix(strings.TrimPrefix(pkg, module), "/")
if strings.HasPrefix(rel, "cmd/") || mayUseEbitengine[rel] {
continue
}
for _, d := range strings.Split(deps, ",") {
if strings.HasPrefix(d, ebitengine) {
t.Errorf("%s får inte bero på Ebitengine (via %s) – flytta logiken till ett rent paket eller lägg till paketet i mayUseEbitengine", rel, d)
break
}
}
}
}
func goList(t *testing.T, args ...string) string {
t.Helper()
return goListIn(t, "", args...)
}
func goListIn(t *testing.T, dir string, args ...string) string {
t.Helper()
cmd := exec.Command("go", append([]string{"list"}, args...)...)
cmd.Dir = dir
out, err := cmd.Output()
if err != nil {
t.Fatalf("go list %v: %v", args, err)
}
return strings.TrimSpace(string(out))
}