Files
metro-puzzle-jp/internal/game/scene_words.go
claude 915c684dc2
All checks were successful
release / build-release (push) Successful in 1m46s
M8: progression, items, bankarta och sparfil
- internal/progress: katalog (skills.json 12 noder, items.json 25 items)
  med validering; XP/nivåer (150·n(n+1)/2) och skillpoäng; skillträd med
  krav och kostnad; items per plats med buffar, genvägar (JR Pass, kartor,
  powerbank) och färgton; banresultat och upplåsning (huvudbanor i ordning,
  sidospår efter föräldern); 33 %-regeln som test
- internal/save: versionerad JSON med kunskap, plånbok, debuffar,
  progression och pågående bana + seed; atomisk skrivning; migrering;
  ~/.config/metro-puzzle-jp/save.json resp. %AppData%
- sim: RunOptions får Extra/FogRadius/Landmarks/FreeGates/RevealAll/
  InfiniteBattery; luck påverkar trängselzonen; Fog.RevealAll och extra
  landmärken
- game: bankarta i spelbrädesstil ersätter menyn (⟳ återupptar med sparad
  seed), skillträd (T), garderob (I), butik i kiosken (B), XP/nivå/items/
  upplåsningar på resultatet, spelaren tonas av skins; autospar vid banstart,
  efter banan och vid köp; flaggor -save/-nosave
- level: kind/order/parent/rewards/shop; ny sidobana 002b Kabukicho som
  belönar skinnet Kimono; 001 belönar Salaryman-skinnet; butikssortiment
  på 002/003
- tools/shots: demo-sparfil (build/, aldrig den riktiga), bilder av karta,
  skills, garderob och butik; alla körningar med -nosave
- docs/README/wiki uppdaterade

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

114 lines
3.1 KiB
Go

package game
import (
"fmt"
"image/color"
"sort"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/lang"
)
// WordsScene är ordlistan/språkträdet: alla ord i ordboken, vad spelaren
// kan, framsteg på resten, och möjligheten att lägga språkpoäng på valfria
// ord. Blir ett riktigt träd med skills i M8.
type WordsScene struct {
back Scene
entries []*lang.Entry
sel int
top int
msg string
}
const wordsRows = 9
// NewWordsScene listar ordboken sorterad på tier och ord.
func NewWordsScene(a *Assets, back Scene) *WordsScene {
var es []*lang.Entry
for _, w := range a.Dict.Words() {
es = append(es, a.Dict.Lookup(w))
}
sort.Slice(es, func(i, j int) bool {
if es[i].Tier != es[j].Tier {
return es[i].Tier < es[j].Tier
}
return es[i].Word < es[j].Word
})
return &WordsScene{back: back, entries: es}
}
func (w *WordsScene) Update(g *Game) error {
if backJustPressed() || wordsJustPressed() {
g.SetScene(w.back)
return nil
}
if n := len(w.entries); n > 0 {
w.sel = (w.sel + menuMove() + n) % n
if w.sel < w.top {
w.top = w.sel
}
if w.sel >= w.top+wordsRows {
w.top = w.sel - wordsRows + 1
}
if confirmJustPressed() {
e := w.entries[w.sel]
switch {
case g.Know.Knows(e.Word):
w.msg = g.Assets.T("words.already")
case g.Know.Buy(e):
w.msg = fmt.Sprintf(g.Assets.T("words.bought"), e.Word, e.Translation(UILang))
g.Save()
default:
w.msg = fmt.Sprintf(g.Assets.T("words.too_expensive"), lang.WordCost(e))
}
}
}
return nil
}
func (w *WordsScene) Draw(g *Game, screen *ebiten.Image) {
a := g.Assets
k := g.Know
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("words.title"), 8, 5, colText)
hdr := fmt.Sprintf("%s %d %s Lv%d %d/%d %s", a.T("words.points"), k.Points, a.T("hud.japanese"),
k.Level(), k.Count(), len(w.entries), a.T("hud.words"))
a.Draw(screen, hdr, ScreenW-8-a.Width(hdr), 6, colKnown)
y0 := 30
for i := w.top; i < len(w.entries) && i < w.top+wordsRows; i++ {
e := w.entries[i]
y := y0 + (i-w.top)*LineH
if i == w.sel {
vector.DrawFilledRect(screen, 4, float32(y-1), ScreenW-8, LineH, colBar, false)
}
status, scol := strings.Repeat("●", lang.WordCost(e)), colDim
if k.CanBuy(e) {
scol = colKnown
}
wcol := colText
switch {
case k.Knows(e.Word):
status, scol, wcol = "✓", colGood, colKnown
case k.ProgressOf(e.Word) > 0:
status, scol = fmt.Sprintf("%d/%d", k.ProgressOf(e.Word), lang.ProgressToLearn), colWarn
}
a.Draw(screen, e.Word, 8, y, wcol)
a.Draw(screen, e.Romaji, 92, y, colDim)
a.Draw(screen, e.Translation(UILang), 180, y, colText)
a.Draw(screen, status, ScreenW-8-a.Width(status), y, scol)
}
// rullningsindikator
if len(w.entries) > wordsRows {
a.Draw(screen, fmt.Sprintf("%d-%d / %d", w.top+1, min(len(w.entries), w.top+wordsRows), len(w.entries)), 8, ScreenH-38, colDim)
}
if w.msg != "" {
a.Draw(screen, w.msg, 8, ScreenH-26, colKnown)
}
a.Draw(screen, a.T("words.hint"), 8, ScreenH-13, colDim)
}