Files
metro-puzzle-jp/tools/genart/main.go
claude de61568e44
All checks were successful
release / build-release (push) Successful in 1m33s
M9b: folk, filer, reklam, sorl och vagnval
Folk på stationen (NPC:er som vandrar, kliver av tåg och går mot
utgången, blockerar spelaren men inte varandra), vänster-/högerfiler
som bromsar på fel sida, bredare gångar, reklamaffischer (clutter),
japanskt sorl i loop (noise), vagnval med kvinnovagn (stort avdrag med
man i sällskapet, tidsstyrd), kömarkeringar på perrongen, trängsel per
vagn (bonus för tom vagn, svettig-debuff och svårare resa i full vagn,
släpp förbi passagerare) och att folk kliver av innan man får gå på.
Ny bana 005 Ueno. Skärmdumpsverktyget kan filtrera på bana/skript.

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

322 lines
8.8 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,
// trappa upp, trappa ner, fil vänster, fil höger, affisch ×3) 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) { // trappa upp: steg som blir ljusare uppåt + pil
floor(c)
for i := 0; i < 5; i++ {
shade := uint8(90 + i*25)
c.rect(2, 13-i*3, 12, 3, color.RGBA{shade, shade + 10, shade + 30, 255})
}
c.rect(7, 1, 2, 4, white) // pil upp
c.rect(6, 2, 4, 1, white)
c.rect(5, 3, 6, 1, white)
},
func(c canvas) { // trappa ner: steg som blir mörkare neråt + pil
floor(c)
for i := 0; i < 5; i++ {
shade := uint8(190 - i*25)
c.rect(2, 1+i*3, 12, 3, color.RGBA{shade, shade + 10, shade + 30, 255})
}
c.rect(7, 11, 2, 4, boxDark) // pil ner
c.rect(6, 13, 4, 1, boxDark)
c.rect(5, 12, 6, 1, boxDark)
},
func(c canvas) { // fil vänster: golv med vänsterpilar
floor(c)
for _, y := range []int{3, 11} {
c.rect(4, y, 8, 1, metalDark)
c.rect(3, y-1, 1, 3, metalDark)
}
},
func(c canvas) { // fil höger: golv med högerpilar
floor(c)
for _, y := range []int{3, 11} {
c.rect(4, y, 8, 1, metalDark)
c.rect(12, y-1, 1, 3, metalDark)
}
},
func(c canvas) { // affisch 1: färgglad reklam
c.rect(0, 0, 16, 16, wallMid)
c.rect(2, 1, 12, 14, rgb(250, 230, 120))
c.rect(3, 2, 10, 5, rgb(228, 59, 68))
c.rect(4, 8, 8, 1, boxDark)
c.rect(4, 10, 6, 1, boxDark)
c.rect(4, 12, 7, 1, boxDark)
},
func(c canvas) { // affisch 2: blå lockskylt som liknar en riktig skylt
c.rect(0, 0, 16, 16, wallMid)
c.rect(1, 2, 14, 12, signBoard)
c.rect(1, 2, 14, 1, white)
c.rect(3, 5, 10, 1, white)
c.rect(3, 8, 6, 1, white)
c.rect(3, 11, 8, 1, yellow)
},
func(c canvas) { // affisch 3: rosa med stort tecken
c.rect(0, 0, 16, 16, wallMid)
c.rect(2, 1, 12, 14, rgb(255, 170, 200))
c.rect(5, 4, 6, 6, white)
c.rect(7, 4, 2, 6, rgb(200, 60, 120))
c.rect(5, 6, 6, 2, rgb(200, 60, 120))
c.rect(4, 12, 8, 1, rgb(120, 30, 70))
},
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)
}