M1: rörelse, kollision, kamera, scener, tileset, skärmdumpar, CI
Some checks failed
release / build-release (push) Failing after 27s
Some checks failed
release / build-release (push) Failing after 27s
- internal/sim: rutbaserad Mover (4 riktningar, riktningsbyte vid rutgräns, bildrute-exakt ankomst) och Camera (följer, klampar, centrerar små kartor), med tester - internal/game: scener meny -> bana -> resultat, tangentbord + gamepad, tileset och spelaranimation, HUD, skriptläge (-script) för deterministiska skärmdumpar och tester - tools/genart genererar placeholder-grafik (tiles.png, player.png); tools/shots tar doc/img/*.png som README och wiki visar - bana 002-shinjuku (33x21) för kamerascroll; level.List/LoadFile/TrainAt - .gitea/workflows/release.yaml: vet + test + linux/windows-zippar till rullande latest på brasse-linux01-runnern (linux-amd64) - docs: roadmap M1 klar, tester, plan, README med bilder och styrning Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
217
tools/genart/main.go
Normal file
217
tools/genart/main.go
Normal file
@@ -0,0 +1,217 @@
|
||||
// Genererar placeholder-grafik tills riktig pixel art finns:
|
||||
//
|
||||
// go run ./tools/genart
|
||||
//
|
||||
// Skriver resources/sprites/tiles.png (en remsa 16×16-rutor i ordningen
|
||||
// vägg, golv, start, perrong, spärr, automat, skylt) och
|
||||
// resources/sprites/player.png (3 bildrutor × 4 riktningar: ner, upp,
|
||||
// vänster, höger; kolumn 0 = stilla, 1–2 = gå). Ritas med enkla rektanglar
|
||||
// så att allt är deterministiskt och lätt att justera.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const T = 16
|
||||
|
||||
func rgb(r, g, b uint8) color.RGBA { return color.RGBA{r, g, b, 255} }
|
||||
|
||||
var (
|
||||
wallDark, wallMid, wallLight = rgb(38, 43, 68), rgb(58, 68, 102), rgb(90, 105, 136)
|
||||
floorA, floorB, grout = rgb(139, 155, 180), rgb(128, 143, 168), rgb(108, 121, 146)
|
||||
yellow, yellowDark = rgb(254, 174, 52), rgb(200, 130, 30)
|
||||
metal, metalDark = rgb(190, 198, 208), rgb(120, 130, 145)
|
||||
screenC, boxDark = rgb(44, 232, 245), rgb(50, 60, 80)
|
||||
signBoard, white = rgb(24, 60, 120), rgb(255, 255, 255)
|
||||
green, greenDark = rgb(99, 199, 77), rgb(60, 140, 50)
|
||||
skin, hair, capC, capDark = rgb(240, 200, 160), rgb(60, 40, 30), rgb(228, 59, 68), rgb(170, 40, 50)
|
||||
jacket, jacketDark = rgb(44, 232, 245), rgb(30, 170, 185)
|
||||
pants, shoe, eye = rgb(38, 43, 68), rgb(255, 255, 255), rgb(38, 43, 68)
|
||||
)
|
||||
|
||||
type canvas struct {
|
||||
img *image.NRGBA
|
||||
ox, oy int
|
||||
}
|
||||
|
||||
func (c canvas) rect(x, y, w, h int, col color.RGBA) {
|
||||
for yy := y; yy < y+h; yy++ {
|
||||
for xx := x; xx < x+w; xx++ {
|
||||
c.img.SetNRGBA(c.ox+xx, c.oy+yy, color.NRGBA{col.R, col.G, col.B, col.A})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func floor(c canvas) {
|
||||
c.rect(0, 0, 7, 7, floorA)
|
||||
c.rect(8, 0, 7, 7, floorB)
|
||||
c.rect(0, 8, 7, 7, floorB)
|
||||
c.rect(8, 8, 7, 7, floorA)
|
||||
c.rect(7, 0, 1, 16, grout)
|
||||
c.rect(15, 0, 1, 16, grout)
|
||||
c.rect(0, 7, 16, 1, grout)
|
||||
c.rect(0, 15, 16, 1, grout)
|
||||
}
|
||||
|
||||
var tiles = []func(canvas){
|
||||
func(c canvas) { // vägg
|
||||
c.rect(0, 0, 16, 16, wallMid)
|
||||
c.rect(0, 0, 16, 2, wallLight)
|
||||
c.rect(0, 14, 16, 2, wallDark)
|
||||
c.rect(0, 7, 16, 1, wallDark)
|
||||
c.rect(7, 2, 1, 5, wallDark)
|
||||
c.rect(3, 8, 1, 6, wallDark)
|
||||
c.rect(11, 8, 1, 6, wallDark)
|
||||
},
|
||||
floor, // golv
|
||||
func(c canvas) { // start: golv + dörrmatta
|
||||
floor(c)
|
||||
c.rect(3, 10, 10, 4, green)
|
||||
c.rect(3, 10, 10, 1, greenDark)
|
||||
c.rect(3, 13, 10, 1, greenDark)
|
||||
},
|
||||
func(c canvas) { // perrong: golv + gul ledlinje + kant
|
||||
floor(c)
|
||||
c.rect(0, 11, 16, 3, yellow)
|
||||
for x := 1; x < 16; x += 4 {
|
||||
c.rect(x, 12, 2, 1, yellowDark)
|
||||
}
|
||||
c.rect(0, 15, 16, 1, wallDark)
|
||||
},
|
||||
func(c canvas) { // spärr: två stolpar med grön lampa
|
||||
floor(c)
|
||||
for _, x := range []int{1, 11} {
|
||||
c.rect(x, 1, 4, 13, metal)
|
||||
c.rect(x, 1, 4, 1, white)
|
||||
c.rect(x, 13, 4, 1, metalDark)
|
||||
c.rect(x+1, 3, 2, 2, green)
|
||||
}
|
||||
},
|
||||
func(c canvas) { // automat
|
||||
floor(c)
|
||||
c.rect(3, 1, 10, 13, boxDark)
|
||||
c.rect(4, 2, 8, 5, screenC)
|
||||
c.rect(5, 9, 6, 1, metal)
|
||||
c.rect(5, 11, 2, 1, yellow)
|
||||
c.rect(8, 11, 2, 1, green)
|
||||
},
|
||||
func(c canvas) { // skylt
|
||||
floor(c)
|
||||
c.rect(7, 7, 2, 9, boxDark)
|
||||
c.rect(2, 1, 12, 7, signBoard)
|
||||
c.rect(2, 1, 12, 1, white)
|
||||
c.rect(3, 3, 10, 1, white)
|
||||
c.rect(3, 5, 6, 1, white)
|
||||
},
|
||||
}
|
||||
|
||||
// player ritar en figur som tittar åt facing (0 ner, 1 upp, 2 vänster, 3 höger)
|
||||
// i bildruta frame (0 stilla, 1–2 gå).
|
||||
func player(c canvas, facing, frame int) {
|
||||
// ben + skor
|
||||
ly, ry := 11, 11
|
||||
lx, rx := 5, 9
|
||||
switch frame {
|
||||
case 1:
|
||||
ly = 10
|
||||
case 2:
|
||||
ry = 10
|
||||
}
|
||||
if facing >= 2 { // sidovy: benen närmare varandra, växelvis fram
|
||||
lx, rx = 6, 8
|
||||
if frame == 1 {
|
||||
lx, rx = 5, 8
|
||||
} else if frame == 2 {
|
||||
lx, rx = 6, 9
|
||||
}
|
||||
}
|
||||
c.rect(lx, ly, 2, 14-ly, pants)
|
||||
c.rect(rx, ry, 2, 14-ry, pants)
|
||||
c.rect(lx, 14, 2, 1, shoe)
|
||||
c.rect(rx, 14, 2, 1, shoe)
|
||||
// kropp + armar
|
||||
c.rect(4, 6, 8, 5, jacket)
|
||||
c.rect(4, 6, 8, 1, jacketDark)
|
||||
c.rect(3, 7, 1, 3, jacket)
|
||||
c.rect(12, 7, 1, 3, jacket)
|
||||
// huvud
|
||||
switch facing {
|
||||
case 0: // ner
|
||||
c.rect(4, 1, 8, 5, skin)
|
||||
c.rect(3, 0, 10, 2, capC)
|
||||
c.rect(3, 2, 10, 1, capDark)
|
||||
c.rect(6, 4, 1, 1, eye)
|
||||
c.rect(9, 4, 1, 1, eye)
|
||||
case 1: // upp: bakhuvud
|
||||
c.rect(4, 1, 8, 5, hair)
|
||||
c.rect(3, 0, 10, 2, capC)
|
||||
c.rect(3, 2, 10, 1, capDark)
|
||||
case 2: // vänster
|
||||
c.rect(4, 1, 8, 5, skin)
|
||||
c.rect(9, 1, 3, 5, hair)
|
||||
c.rect(3, 0, 10, 2, capC)
|
||||
c.rect(1, 2, 8, 1, capDark)
|
||||
c.rect(5, 4, 1, 1, eye)
|
||||
case 3: // höger
|
||||
c.rect(4, 1, 8, 5, skin)
|
||||
c.rect(4, 1, 3, 5, hair)
|
||||
c.rect(3, 0, 10, 2, capC)
|
||||
c.rect(7, 2, 8, 1, capDark)
|
||||
c.rect(10, 4, 1, 1, eye)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
out := filepath.Join(repoRoot(), "resources", "sprites")
|
||||
|
||||
tilesImg := image.NewNRGBA(image.Rect(0, 0, T*len(tiles), T))
|
||||
for i, draw := range tiles {
|
||||
draw(canvas{tilesImg, i * T, 0})
|
||||
}
|
||||
save(filepath.Join(out, "tiles.png"), tilesImg)
|
||||
|
||||
playerImg := image.NewNRGBA(image.Rect(0, 0, T*3, T*4))
|
||||
for facing := 0; facing < 4; facing++ {
|
||||
for frame := 0; frame < 3; frame++ {
|
||||
player(canvas{playerImg, frame * T, facing * T}, facing, frame)
|
||||
}
|
||||
}
|
||||
save(filepath.Join(out, "player.png"), playerImg)
|
||||
}
|
||||
|
||||
func save(path string, img image.Image) {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := png.Encode(f, img); err != nil {
|
||||
fail(err)
|
||||
}
|
||||
fmt.Println("skrev", path)
|
||||
}
|
||||
|
||||
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 fail(err error) {
|
||||
fmt.Fprintln(os.Stderr, "genart:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
82
tools/shots/main.go
Normal file
82
tools/shots/main.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// 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/. Kör om detta när grafik eller UI ändrats, och
|
||||
// committa bilderna – README och wikin pekar på dem.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type shot struct {
|
||||
file string
|
||||
level string
|
||||
script string
|
||||
}
|
||||
|
||||
var shots = []shot{
|
||||
{"menu.png", "", "wait 10; shot %s; quit"},
|
||||
{"level-001.png", "001-flygplatsen", "move right 4; move down 2; wait 5; shot %s; quit"},
|
||||
{"level-002.png", "002-shinjuku", "move down 6; move right 4; wait 5; shot %s; quit"},
|
||||
{"result.png", "001-flygplatsen",
|
||||
"move down 2; move right 2; move down 2; move left 2; move down 4; move right 17; move down 1; move left 1; wait 15; shot %s; quit"},
|
||||
}
|
||||
|
||||
func main() {
|
||||
root := repoRoot()
|
||||
out := filepath.Join(root, "doc", "img")
|
||||
must(os.MkdirAll(out, 0o755))
|
||||
|
||||
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", fmt.Sprintf(s.script, filepath.Join(out, s.file))}
|
||||
if s.level != "" {
|
||||
args = append(args, "-level", s.level)
|
||||
}
|
||||
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.file, 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)
|
||||
}
|
||||
Reference in New Issue
Block a user