M6: plånbok, biljetter, kortet i telefonen och seedade minispel
All checks were successful
release / build-release (push) Successful in 1m41s
All checks were successful
release / build-release (push) Successful in 1m41s
- sim.Wallet: IC-kort, kontanter, pappersbiljett, telefon med batteri, spenderat; följer med mellan banor (Game.Wallet) - internal/minigame: gemensamt Game-gränssnitt (Update/Done/Success), all slump från del-seeds: TopUp (hitta チャージ, belopp, mata sedlar), Ticket (taxetavla med resmål på japanska), Crowd (tidsfönster, två försök), Ride (stationerna passerar, kliv av vid resmålet) - sim.Run: spärren kräver biljett på biljettbanor annars användbart kort; telefonen ger ×1,1 fart men 15 % seedad omskanning (frysning 1,5 s), dött batteri gör kortet oanvändbart; automaten startar påfyllning eller biljettköp; tåget kontrollerar biljetten; fulla tåg kräver trängsel; resan avgör vinst/förlust (fel station, åkte förbi, fel tåg); StepMini låter klockan och sällskapet ticka medan rörelsen pausar - level: ticketRequired, rideStops, Train.Crowded; 002 är biljettbana med fullt tåg och fyra stopp; ordboken har stationer (kind) + sju nya namn - game: vyer för minispelen, plånbok i HUD, P i menyn = kort i telefonen, förlustorsaker och spenderat på resultatet, flaggor -phone/-card, skriptkommandon hold/waitstop; inforuta/minikarta döljs under minispel - plan: M7b (Björn) – tågvind, ankomstindikator nära rälsen, in-/utkörningsanimation för tåg och bussar – i spelbeskrivning, roadmap, testplan - docs/README/wiki med tre nya bilder (automat, biljettavla, tågresa) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
107
internal/minigame/topup.go
Normal file
107
internal/minigame/topup.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package minigame
|
||||
|
||||
import "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/rng"
|
||||
|
||||
// TopUp är påfyllningsautomaten: 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
|
||||
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
|
||||
)
|
||||
|
||||
// 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.
|
||||
func NewTopUp(seed rng.Seed, maxCash int) *TopUp {
|
||||
r := newRand(seed, "topup")
|
||||
g := &TopUp{Buttons: append([]string(nil), topUpButtons...), MaxCash: maxCash}
|
||||
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 TopUpPickButton:
|
||||
g.Cursor = wrap(g.Cursor+in.Move, len(g.Buttons))
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user