M7: svårighet, tid, poäng, debuffar, egna kort och sittplatsval
All checks were successful
release / build-release (push) Successful in 1m37s
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>
This commit is contained in:
@@ -4,13 +4,91 @@ 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
|
||||
// spärren/tåget senare.
|
||||
// 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.
|
||||
@@ -20,8 +98,9 @@ type TicketOption struct {
|
||||
}
|
||||
|
||||
// NewTicket bygger tavlan: rätt resmål + lockande alternativ, blandade.
|
||||
// Taxan är seedad per resmål (140–620 yen i steg om 10).
|
||||
func NewTicket(seed rng.Seed, correct string, others []string, cash int) *Ticket {
|
||||
// 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...)
|
||||
@@ -29,25 +108,74 @@ func NewTicket(seed rng.Seed, correct string, others []string, cash int) *Ticket
|
||||
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
|
||||
}
|
||||
|
||||
// Update flyttar markören och köper.
|
||||
// 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
|
||||
g.done = true // avbrutet: inget köp
|
||||
return
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
g.Chosen = &opt
|
||||
g.done, g.success = true, true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user