M13: titelskärm, språkval, tutorial, tips och pausmeny
All checks were successful
release / build-release (push) Successful in 1m40s

Björns feedback efter v0.1.0. Titelskärm med premissen i text och menyn
Fortsätt/Starta, Tutorial, Inställningar, Avsluta (Enter två gånger);
språkval första gången spelet startar. Tutorial i fem sidor med riktiga
sprites och en exempelskylt där resmålet markeras och resten tonas ned,
plus nyckeln 番線/方面/改札 och biljettipset. Kontextuella tips i spelet
tills första banan är klarad, och resmålets tecken markeras på skyltar
och avgångstavlor under tiden. Esc i en bana öppnar en pausmeny
(Fortsätt, Lämna banan, Inställningar, Avsluta spelet) i stället för att
lämna banan; Esc på bankartan går till titeln. Kortare lägestexter så att
bankartans rader ryms på svenska. Test för markeringen, docs, wiki och
skärmdumpar.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 17:13:32 +01:00
parent 86f8130735
commit f0a0bc1522
26 changed files with 622 additions and 32 deletions

85
internal/game/tips.go Normal file
View File

@@ -0,0 +1,85 @@
package game
import (
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
)
// Tutorialtips i spelet: visas en gång var, i ordning när villkoret
// uppfylls, tills första banan är klarad. {dest} och {ja} byts mot
// resmålet i latinska bokstäver respektive japanska tecken.
type tipDef struct {
key string
when func(s *LevelScene) bool
}
var tutorialTips = []tipDef{
{"tip.start", func(s *LevelScene) bool { return true }},
{"tip.sign", func(s *LevelScene) bool { return s.run.SignHere() != nil }},
{"tip.party", func(s *LevelScene) bool { return len(s.run.Party.Urges()) > 0 }},
{"tip.gate", func(s *LevelScene) bool { return s.run.DeniedAt >= 0 || s.run.GatePasses > 0 }},
{"tip.platform", func(s *LevelScene) bool { tr, _, _ := s.run.PlatformHere(); return tr != nil }},
}
const tipShowSec = 7.0
type tipState struct {
shown map[string]bool
current string
since float64
}
// tutorialActive: tipsen och skyltmarkeringen gäller tills första banan är
// klarad (och aldrig i oändligt läge).
func (s *LevelScene) tutorialActive(g *Game) bool {
return !s.endless && !g.Progress.Cleared("001-flygplatsen")
}
// updateTips väljer nästa tips som ska visas.
func (s *LevelScene) updateTips(g *Game) {
if !s.tutorialActive(g) {
return
}
r := s.run
t := &s.tips
if t.shown == nil {
t.shown = map[string]bool{}
}
if t.current != "" && r.Time-t.since < tipShowSec {
return
}
t.current = ""
for _, d := range tutorialTips {
if !t.shown[d.key] && d.when(s) {
t.shown[d.key] = true
t.current, t.since = d.key, r.Time
return
}
}
}
// tipText fyller i resmålet i ett tips.
func (s *LevelScene) tipText(a *Assets, key string) string {
d := s.run.Level.Destination
return strings.NewReplacer("{dest}", d.Get("romaji"), "{ja}", d.Get("ja")).Replace(a.T(key))
}
// drawTip ritar det aktuella tipset under HUD:en.
func (s *LevelScene) drawTip(g *Game, screen *ebiten.Image) {
t := &s.tips
if t.current == "" || s.run.Mini != nil || s.run.Outcome != 0 {
return
}
a := g.Assets
lines := a.wrapLine(highlightLine(s.tipText(a, t.current), s.run.Level.Destination.Get("ja"), colGood, colText), ScreenW-16)
h := 6 + LineH*len(lines)
y := 26
if s.infoBox.Dy() > 0 && s.infoBox.Min.Y < ScreenH/2 {
y = s.infoBox.Max.Y + 2 // inforutan ligger upptill: lägg tipset under den
}
vector.DrawFilledRect(screen, 4, float32(y), ScreenW-8, float32(h), colPanel, false)
vector.DrawFilledRect(screen, 4, float32(y), 2, float32(h), colKnown, false)
a.drawLines(screen, lines, 10, y+3)
}