Files
metro-puzzle-jp/tools/genart/main.go
claude a5a660b32f
All checks were successful
release / build-release (push) Successful in 1m35s
M2: tåg med tidtabell, spärrar med kort, skyltar, vinst/förlust
- level: Schedule (ankomst/intervall/uppehåll) med Status(t), spårnummer,
  avgift, skylttexter per i-ruta, Path (BFS) och UngatedPlatforms – regeln
  att perronger bara nås via spärrar testas för alla banor
- sim.Run: klocka, tågkort (saldo dras vid spärr, tomt kort stoppas),
  ombordstigning med Enter när tåget är inne, vinst/förlust; headless
  bot-test klarar bana 001 och förlorar på fel tåg
- game: perronginfo (avgår om / nästa tåg om), skyltruta, nekad spärr,
  tågvagn som ritas när tåget är inne, inforutan flyttas upp när spelaren
  står nära nederkanten; resultat med spärrpassager och saldo
- bana 001 ombyggd så spärrarna är flaskhalsar; bana 002 omgenererad med
  spärrar runt perrongerna och skylttexter
- tools/shots räknar ut vägar med BFS; skriptkommandot waittrain
- docs/README/wiki uppdaterade med nya bilder

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-23 19:36:45 +02:00

231 lines
5.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) 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)
)
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)
},
}
// 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)
}