Files
metro-puzzle-jp/tools/genart/main.go
claude d466333f01
All checks were successful
release / build-release (push) Successful in 1m42s
M7b: tågfaser, räls, in-/utkörning, vind, ankomstindikator och melodier
- level.Schedule.Phase: away/approaching/arriving/dwelling/closing/leaving
  ur tidtabellen med progress; Boardable (öppna dörrar) och Present;
  validering av minsta uppehåll/borta-tid; TrackTile för rälsen
- sim.Run: ombordstigning bara med öppna dörrar, TrainVisible via Present,
  PlatformPhase och NearTrack (närmaste perrong inom två rutor)
- game/trains.go: rälsrutor längs kantraden (roterade för vertikala spår),
  tre vagnar som bromsar in och accelererar ut längs rälsen, strålkastare
  under inrullningen, blinkande lampor vid kanten för den som står nära,
  dammpartiklar längs perrongen; kameran får en rutas marginal så att
  kanträlsen syns
- game/audio.go: ebiten/v2/audio med avgångsmelodi (approaching) och
  dörrsignal (closing), volym efter avstånd, -mute; tools/gensound
  syntetiserar placeholder-WAV:ar
- HUD: fasberoende perrongtext och "tåg på väg in" nära rälsen;
  skriptkommandot waitphase; skärmdump av ankomsten
- plan (Björn 2026-10-24): räls/väg-grafik och melodierna i M7b; folk/NPC:er,
  vänster-/högertrafik, bredare gångar, reklam- och ljudröra som nytt steg
  M9b – i spelbeskrivning, roadmap, testplan
- docs/README/wiki uppdaterade

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 11:03:12 +02:00

263 lines
6.9 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 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, tågvagn, toalett, kiosk, räls) 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)
trainBody, trainStripe = rgb(230, 234, 240), rgb(228, 59, 68)
toiletBlue, awningRed = rgb(40, 110, 200), rgb(228, 59, 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)
},
func(c canvas) { // tågvagn (ritas på rutan bortom perrongkanten när tåget är inne)
c.rect(0, 0, 16, 16, wallDark)
c.rect(0, 2, 16, 12, trainBody)
c.rect(0, 2, 16, 1, metalDark)
c.rect(0, 9, 16, 2, trainStripe)
for _, x := range []int{1, 5, 12} {
c.rect(x, 4, 3, 4, screenC)
}
c.rect(9, 4, 2, 9, metalDark) // dörr
c.rect(2, 14, 3, 2, boxDark) // hjul
c.rect(11, 14, 3, 2, boxDark)
},
func(c canvas) { // toalett: vit dörr med blå skylt
floor(c)
c.rect(3, 1, 10, 13, white)
c.rect(3, 1, 10, 1, metalDark)
c.rect(5, 3, 6, 5, toiletBlue)
c.rect(7, 4, 2, 3, white) // figur
c.rect(10, 9, 1, 1, boxDark)
},
func(c canvas) { // räls (horisontell; roteras för vertikala spår): slipers och två stålskenor
c.rect(0, 0, 16, 16, wallDark)
c.rect(0, 0, 16, 1, wallMid)
for x := 1; x < 16; x += 4 {
c.rect(x, 3, 2, 10, boxDark) // slipers
}
c.rect(0, 5, 16, 1, metal)
c.rect(0, 6, 16, 1, metalDark)
c.rect(0, 10, 16, 1, metal)
c.rect(0, 11, 16, 1, metalDark)
},
func(c canvas) { // kiosk: randig markis och disk
floor(c)
c.rect(1, 1, 14, 3, awningRed)
for x := 1; x < 15; x += 4 {
c.rect(x, 1, 2, 3, white)
}
c.rect(2, 4, 12, 6, boxDark)
c.rect(3, 5, 3, 2, screenC) // flaskor
c.rect(7, 5, 3, 2, yellow)
c.rect(11, 5, 2, 2, green)
c.rect(2, 10, 12, 3, metal) // disk
},
}
// 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)
}