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 } } } }