Files
metro-puzzle-jp/internal/minigame/topup.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

122 lines
3.1 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.

package minigame
import "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/rng"
// TopUp är påfyllningsautomaten: (vems kort, om alla har egna), hitta
// チャージ-knappen bland japanska knappar i seedad ordning, välj belopp, mata
// in sedlar genom att hålla Enter. Fel knapp kostar en straffsekund eller två.
type TopUp struct {
base
Members []string // namn när alla har egna kort (annars tom)
Member int // vald person (index i Members, 0 = spelaren)
Stage TopUpStage
Buttons []string // knapparnas text, seedad ordning
Cursor int
Amounts []int
Mistakes int
Penalty float64 // sekunder kvar av frysning efter fel knapp
Fill float64 // 0..1 sedelinmatning
Amount int // valt belopp (resultat)
MaxCash int // vad spelaren har råd med
}
// TopUpStage är var i flödet man är.
type TopUpStage int
// Steg.
const (
TopUpPickButton TopUpStage = iota
TopUpPickAmount
TopUpFeed
TopUpPickMember // först, bara med egna kort
)
// Knapparna på en japansk biljettautomat. チャージ är den rätta.
var topUpButtons = []string{"チャージ", "きっぷ", "定期券", "取消"}
// TopUpAmounts är beloppen man kan välja.
var TopUpAmounts = []int{1000, 2000, 3000, 5000}
const (
topUpPenaltySec = 1.5
topUpFeedPerSec = 0.8 // hålla Enter: fyller på 1/0.8 s
topUpDrainPerSec = 1.2
)
// NewTopUp skapar automaten. maxCash begränsar beloppen. members anges
// när alla i sällskapet har egna kort; då väljs person först.
func NewTopUp(seed rng.Seed, maxCash int, members []string) *TopUp {
r := newRand(seed, "topup")
g := &TopUp{Buttons: append([]string(nil), topUpButtons...), MaxCash: maxCash}
if len(members) > 1 {
g.Members = append([]string(nil), members...)
g.Stage = TopUpPickMember
}
r.shuffle(len(g.Buttons), func(i, j int) { g.Buttons[i], g.Buttons[j] = g.Buttons[j], g.Buttons[i] })
for _, a := range TopUpAmounts {
if a <= maxCash {
g.Amounts = append(g.Amounts, a)
}
}
if len(g.Amounts) == 0 {
g.done = true // inga pengar: inget att göra
}
return g
}
// Update för automaten framåt.
func (g *TopUp) Update(in Input, dt float64) {
if g.done {
return
}
if in.Cancel {
g.done = true
return
}
if g.Penalty > 0 {
g.Penalty -= dt
return
}
switch g.Stage {
case TopUpPickMember:
g.Cursor = wrap(g.Cursor+in.Move, len(g.Members))
if in.Confirm {
g.Member = g.Cursor
g.Stage, g.Cursor = TopUpPickButton, 0
}
case TopUpPickButton:
g.Cursor = wrap(g.Cursor+2*in.Move+in.MoveX, len(g.Buttons)) // 2×2-rutnät
if in.Confirm {
if g.Buttons[g.Cursor] == topUpButtons[0] {
g.Stage, g.Cursor = TopUpPickAmount, 0
} else {
g.Mistakes++
g.Penalty = topUpPenaltySec
}
}
case TopUpPickAmount:
g.Cursor = wrap(g.Cursor+in.Move, len(g.Amounts))
if in.Confirm {
g.Amount = g.Amounts[g.Cursor]
g.Stage = TopUpFeed
}
case TopUpFeed:
if in.Hold {
g.Fill += topUpFeedPerSec * dt
} else {
g.Fill = max(0, g.Fill-topUpDrainPerSec*dt)
}
if g.Fill >= 1 {
g.Fill = 1
g.done, g.success = true, true
}
}
}
func wrap(i, n int) int {
if n == 0 {
return 0
}
return ((i % n) + n) % n
}