All checks were successful
release / build-release (push) Successful in 1m35s
- 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>
103 lines
2.7 KiB
Go
103 lines
2.7 KiB
Go
package game
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
|
|
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
|
|
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/res"
|
|
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/sim"
|
|
)
|
|
|
|
// Ordningen på rutorna i resources/sprites/tiles.png (måste matcha tools/genart).
|
|
var tileOrder = []level.Tile{
|
|
level.Wall, level.Floor, level.Spawn, level.Platform, level.Gate, level.TicketMachine, level.Sign,
|
|
}
|
|
|
|
// Assets är allt som laddats från resources/.
|
|
type Assets struct {
|
|
ResDir string
|
|
LevelsDir string
|
|
Tiles map[level.Tile]*ebiten.Image
|
|
Train *ebiten.Image // tågvagn, ritas vid perrongen när tåget är inne
|
|
Player [4][3]*ebiten.Image // [riktning: ner, upp, vänster, höger][bildruta: stilla, gå1, gå2]
|
|
Text map[string]string
|
|
}
|
|
|
|
// LoadAssets läser grafik och texter.
|
|
func LoadAssets() (*Assets, error) {
|
|
dir, err := res.Dir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a := &Assets{ResDir: dir, Tiles: map[level.Tile]*ebiten.Image{}}
|
|
a.LevelsDir, _ = res.Path("data", "levels")
|
|
|
|
tiles, err := loadImage("sprites", "tiles.png")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sub := func(i int) *ebiten.Image {
|
|
return tiles.SubImage(image.Rect(i*TileSize, 0, (i+1)*TileSize, TileSize)).(*ebiten.Image)
|
|
}
|
|
if w, need := tiles.Bounds().Dx(), TileSize*(len(tileOrder)+1); w < need {
|
|
return nil, fmt.Errorf("tiles.png är %d px bred, behöver minst %d", w, need)
|
|
}
|
|
for i, t := range tileOrder {
|
|
a.Tiles[t] = sub(i)
|
|
}
|
|
a.Tiles[level.WrongPlatform] = a.Tiles[level.Platform] // samma utseende; tåget avgör
|
|
a.Train = sub(len(tileOrder))
|
|
|
|
player, err := loadImage("sprites", "player.png")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for row := 0; row < 4; row++ {
|
|
for col := 0; col < 3; col++ {
|
|
a.Player[row][col] = player.SubImage(image.Rect(col*TileSize, row*TileSize, (col+1)*TileSize, (row+1)*TileSize)).(*ebiten.Image)
|
|
}
|
|
}
|
|
|
|
if err := res.LoadJSON(&a.Text, "text", "sv.json"); err != nil {
|
|
return nil, err
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// T slår upp en UI-text; saknas nyckeln visas nyckeln själv så att det syns.
|
|
func (a *Assets) T(key string) string {
|
|
if s, ok := a.Text[key]; ok {
|
|
return s
|
|
}
|
|
return key
|
|
}
|
|
|
|
// PlayerFrame returnerar rätt bildruta för riktning och gångfas.
|
|
func (a *Assets) PlayerFrame(facing sim.Dir, moving bool, animT float64) *ebiten.Image {
|
|
row := 0
|
|
switch facing {
|
|
case sim.Up:
|
|
row = 1
|
|
case sim.Left:
|
|
row = 2
|
|
case sim.Right:
|
|
row = 3
|
|
}
|
|
col := 0
|
|
if moving {
|
|
col = 1 + int(animT/0.12)%2
|
|
}
|
|
return a.Player[row][col]
|
|
}
|
|
|
|
func loadImage(parts ...string) (*ebiten.Image, error) {
|
|
img, err := res.LoadImage(parts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ebiten.NewImageFromImage(img), nil
|
|
}
|