Files
metro-puzzle-jp/internal/game/scene_menu.go
claude b2995d4ca9
All checks were successful
release / build-release (push) Successful in 1m37s
M7: svårighet, tid, poäng, debuffar, egna kort och sittplatsval
- sim/stats.go: Stats (fart, törst, fokus, toalett, tur), debuffar som
  multiplikatorer, Profile som följer med mellan banor och räknar ned
- sim.Run: tidsgräns och maxantal missade tåg förlorar banan; poängformel
  (tid mot par, pengar, sällskapets mätare, utforskat, avdrag); debuffar
  tjänas av många steg, lång tid, tom matmätare och seedad otur vid
  kiosken; stats påverkar fart, mätartakter, läsning och hjälptid
- egna kort (individualCards): Member.Card, spärren kräver allas saldo och
  drar från alla, automaten frågar vems kort, biljetter köps åt alla
- sittplatsval (seatSelection): Ticket får ett sittplatssteg med seedade
  önskemål (fönster/gång/sida/bredvid); missnöjda ger mätar- och poängavdrag
- level: maxMissedTrains, individualCards, seatSelection; bana 003 Shibuya
  (svårighet 3, fyra i sällskapet) och 002 med tidsgräns och sittplatser;
  ordboken utökad; generatorn är spec-driven
- game: tid kvar/missade i HUD, debuffar i meny/HUD/resultat, poäng och
  förlustorsaker på resultatet, vyer för personval och vagn, skript-pick,
  flaggan -debuffs; HUD och resultat komprimerade för fyra i sällskapet
- plan (Björn 2026-10-24): flera våningar med trappor som M9a, egna kort
  och sittplatsval som svårighetsinställningar i M7 – i spelbeskrivning,
  roadmap, testplan
- docs/README/wiki med sittplatsbilden

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

113 lines
3.1 KiB
Go

package game
import (
"fmt"
"image/color"
"path/filepath"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/buildinfo"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
)
type menuEntry struct {
id string
name string
dest string
stars string
}
// MenuScene listar banorna och låter spelaren välja.
type MenuScene struct {
entries []menuEntry
sel int
err error
}
// NewMenuScene läser in banlistan.
func NewMenuScene(a *Assets) (*MenuScene, error) {
ids, err := level.List(a.LevelsDir)
if err != nil {
return nil, err
}
m := &MenuScene{}
for _, id := range ids {
l, err := level.LoadFile(filepath.Join(a.LevelsDir, id+".json"))
if err != nil {
m.err = err
continue
}
m.entries = append(m.entries, menuEntry{
id: id,
name: l.Name.Get(UILang) + " " + l.Name.Get("ja"),
dest: l.Destination.Get("ja") + " " + l.Destination.Get("romaji"),
stars: strings.Repeat("★", l.Difficulty) + strings.Repeat("☆", 5-l.Difficulty),
})
}
return m, nil
}
func (m *MenuScene) Update(g *Game) error {
if backJustPressed() {
return ebiten.Termination
}
if wordsJustPressed() {
g.SetScene(NewWordsScene(g.Assets, m))
return nil
}
if phoneJustPressed() {
g.Wallet.Phone = !g.Wallet.Phone
}
if n := len(m.entries); n > 0 {
m.sel = (m.sel + menuMove() + n) % n
if confirmJustPressed() {
s, err := NewLevelScene(g.Assets, m.entries[m.sel].id, g, g.seed())
if err != nil {
m.err = err
return nil
}
g.SetScene(s)
}
}
return nil
}
func (m *MenuScene) Draw(g *Game, screen *ebiten.Image) {
a := g.Assets
screen.Fill(color.RGBA{24, 20, 37, 255})
vector.DrawFilledRect(screen, 0, 0, ScreenW, 22, color.RGBA{38, 43, 68, 255}, false)
a.DrawBig(screen, a.T("game.title"), 8, 5, colText)
a.Draw(screen, a.T("menu.choose_level"), 8, 30, colDim)
for i, e := range m.entries {
y := 46 + i*14
if i == m.sel {
vector.DrawFilledRect(screen, 6, float32(y-2), ScreenW-12, 13, colBar, false)
}
a.Draw(screen, e.name, 12, y, colText)
a.Draw(screen, "→ "+e.dest, 150, y, colKnown)
a.Draw(screen, e.stars, ScreenW-62, y, colWarn)
}
if m.err != nil {
a.Draw(screen, "FEL: "+m.err.Error(), 8, ScreenH-40, colBad)
}
onoff := a.T("menu.off")
if g.Wallet.Phone {
onoff = a.T("menu.on")
}
a.Draw(screen, fmt.Sprintf("%s Lv%d %d %s %s %d %s %d¥ %s %d¥ %s", a.T("hud.japanese"), g.Know.Level(), g.Know.Count(), a.T("hud.words"),
a.T("words.points"), g.Know.Points, a.T("hud.card"), g.Wallet.Card, a.T("hud.cash"), g.Wallet.Cash, fmt.Sprintf(a.T("menu.phone"), onoff)), 8, ScreenH-38, colDim)
if len(g.Profile.Debuffs) > 0 {
var parts []string
for _, d := range g.Profile.Debuffs {
parts = append(parts, fmt.Sprintf("%s (%d)", a.T("debuff."+string(d.Kind)), d.LevelsLeft))
}
a.Draw(screen, a.T("hud.debuffs")+": "+strings.Join(parts, ", "), 8, ScreenH-50, colBad)
}
a.Draw(screen, a.T("menu.hint"), 8, ScreenH-26, colDim)
a.Draw(screen, fmt.Sprintf("%s (%s)", buildinfo.Version, buildinfo.Mode), 8, ScreenH-13, colDim)
}