All checks were successful
release / build-release (push) Successful in 1m37s
- 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>
122 lines
3.1 KiB
Go
122 lines
3.1 KiB
Go
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
|
||
}
|