Files
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

182 lines
4.7 KiB
Go
Raw Permalink 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"
// Ticket är biljettautomaten: en taxetavla med resmål (på japanska) i seedad
// ordning. Spelaren väljer ett resmål och betalar – om det var rätt avgör
// tåget senare. Med sittplatsval följer ett steg där sällskapet placeras i
// vagnen efter sina seedade önskemål.
type Ticket struct {
base
Options []TicketOption
Cursor int
Chosen *TicketOption
Cash int
// Sittplatser (nil Members = inget sittplatssteg).
Stage TicketStage
Members []string
Prefs []SeatPref // önskemål för medlemmar 1..n-1
Assigned []Seat // vald plats per medlem, Row -1 = ingen än
Current int // medlemmen som placeras just nu
Seat Seat // markören i vagnen
Unhappy []int // medlemmar vars önskemål inte uppfylldes (efter köpet)
}
// TicketStage är var i köpet man är.
type TicketStage int
// Steg.
const (
TicketPickDest TicketStage = iota
TicketPickSeat
)
// Vagnen: SeatRows rader med fyra platser – fönster V, gång V | gång H, fönster H.
const (
SeatRows = 3
SeatCols = 4
)
// Seat är en plats i vagnen.
type Seat struct{ Row, Col int }
// Window/Aisle/Left/Right-egenskaper för en plats.
func (s Seat) Window() bool { return s.Col == 0 || s.Col == 3 }
func (s Seat) Aisle() bool { return s.Col == 1 || s.Col == 2 }
func (s Seat) Left() bool { return s.Col <= 1 }
func (s Seat) Right() bool { return s.Col >= 2 }
// Beside: samma rad och samma platspar (0–1 eller 2–3), inte över gången.
func (s Seat) Beside(o Seat) bool { return s.Row == o.Row && s.Col/2 == o.Col/2 && s.Col != o.Col }
// PrefKind är ett slags önskemål.
type PrefKind int
// Önskemål.
const (
PrefWindow PrefKind = iota
PrefAisle
PrefLeft
PrefRight
PrefWith // bredvid en viss person
)
// SeatPref är en medlems önskemål.
type SeatPref struct {
Member int
Kind PrefKind
With int // medlemsindex vid PrefWith
}
// Satisfied kontrollerar önskemålet mot de valda platserna.
func (p SeatPref) Satisfied(assigned []Seat) bool {
s := assigned[p.Member]
if s.Row < 0 {
return false
}
switch p.Kind {
case PrefWindow:
return s.Window()
case PrefAisle:
return s.Aisle()
case PrefLeft:
return s.Left()
case PrefRight:
return s.Right()
case PrefWith:
o := assigned[p.With]
return o.Row >= 0 && s.Beside(o)
}
return true
}
// TicketOption är ett resmål på tavlan.
type TicketOption struct {
Dest string // resmålets namn på japanska
Fare int
}
// NewTicket bygger tavlan: rätt resmål + lockande alternativ, blandade.
// Taxan är seedad per resmål (140–620 yen i steg om 10). members (≥2)
// aktiverar sittplatsvalet med seedade önskemål för alla utom spelaren.
func NewTicket(seed rng.Seed, correct string, others []string, cash int, members []string) *Ticket {
r := newRand(seed, "ticket")
g := &Ticket{Cash: cash}
names := append([]string{correct}, others...)
for _, n := range names {
g.Options = append(g.Options, TicketOption{Dest: n, Fare: 140 + 10*r.intn(49)})
}
r.shuffle(len(g.Options), func(i, j int) { g.Options[i], g.Options[j] = g.Options[j], g.Options[i] })
if len(members) >= 2 && len(members) <= SeatRows*SeatCols {
g.Members = append([]string(nil), members...)
for i := 1; i < len(members); i++ {
p := SeatPref{Member: i, Kind: PrefKind(r.intn(5))}
if p.Kind == PrefWith {
p.With = r.intn(len(members) - 1)
if p.With >= i {
p.With++ // aldrig sig själv
}
}
g.Prefs = append(g.Prefs, p)
}
g.Assigned = make([]Seat, len(members))
for i := range g.Assigned {
g.Assigned[i] = Seat{Row: -1}
}
}
return g
}
// SeatTaken rapporterar om platsen redan är vald.
func (g *Ticket) SeatTaken(s Seat) bool {
for _, a := range g.Assigned {
if a == s {
return true
}
}
return false
}
// Update flyttar markören, köper och placerar sällskapet.
func (g *Ticket) Update(in Input, dt float64) {
if g.done {
return
}
if in.Cancel {
g.done = true // avbrutet: inget köp
return
}
switch g.Stage {
case TicketPickDest:
g.Cursor = wrap(g.Cursor+in.Move, len(g.Options))
if in.Confirm {
opt := g.Options[g.Cursor]
if opt.Fare > g.Cash {
return // har inte råd: välj annat eller avbryt
}
g.Chosen = &opt
if g.Members == nil {
g.done, g.success = true, true
return
}
g.Stage = TicketPickSeat
}
case TicketPickSeat:
g.Seat.Row = wrap(g.Seat.Row+in.Move, SeatRows)
g.Seat.Col = wrap(g.Seat.Col+in.MoveX, SeatCols)
if in.Confirm && !g.SeatTaken(g.Seat) {
g.Assigned[g.Current] = g.Seat
g.Current++
if g.Current >= len(g.Members) {
for _, p := range g.Prefs {
if !p.Satisfied(g.Assigned) {
g.Unhappy = append(g.Unhappy, p.Member)
}
}
g.done, g.success = true, true
}
}
}
}