Files
metro-puzzle-jp/tools/genart/main.go
claude daa2089eda
All checks were successful
release / build-release (push) Successful in 1m49s
Inlärning (framsteg, stjärnor, ordlista) och M4: lämmeltåget
Inlärning enligt Björns regler:
- lang.Knowledge: framsteg per ord (3 läsningar = känt), språkpoäng,
  köp av valfria ord (kostnad = tier), Award per stjärna
- sim.Run: RunOptions med seed; stjärnor 1–3 mot par (parSec eller
  kortaste vägen × 0,5 s + 20 s); ≥2 stjärnor lär resmålets ord; att
  stanna på skylt/perrong ger seedad 35 % chans till framsteg per okänt ord
- game: ordlista (L) med kända ord, framsteg och kostnad; stjärnor, poäng
  och inlärt resmål på resultatskärmen; framstegsnotis; flaggor -points/-seed

M4 lämmeltåget:
- sim.Party: medlemmar med mat/dryck/toa-mätare, behov under tröskel,
  seedade händelser var 45 s / antal (toa, törst, hunger, distraktion),
  fartstraff, toalett (W, 3 s/person) och kiosk (K, 150 ¥/person),
  följare på ledarens spår
- level: rutor W och K, party och parSec i bandatan; banor med sällskap
  måste ha toalett och kiosk (testas); 001 har 2, 002 har 3 i sällskapet
- game: följare med färgton, behovsmarkering över huvudet, mätarpanel,
  facilitetsrutor med förlopp; genart ritar toalett och kiosk
- skriptläget: move stannar exakt på målrutan även när farten inte är
  bildruteexakt (sällskapets fartstraff), stall-skydd mot väggar
- docs/README/wiki/spelbeskrivning uppdaterade

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-23 21:29:09 +02:00

252 lines
6.6 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) 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) { // 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)
}