package sim import "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level" // Outcome är hur en banomgång står sig. type Outcome int // Utfall. const ( Ongoing Outcome = iota Won // klev på rätt tåg Lost // klev på fel tåg ) // Card är tågkortet (IC-kortet). Saldo i yen. type Card struct{ Balance int } // CanPay rapporterar om saldot räcker. func (c *Card) CanPay(yen int) bool { return c.Balance >= yen } // Pay drar beloppet om det går. func (c *Card) Pay(yen int) bool { if !c.CanPay(yen) { return false } c.Balance -= yen return true } // StartBalanceYen är kortets saldo vid banstart tills M6 ger riktig ekonomi. const StartBalanceYen = 2000 // Run är tillståndet för en pågående bana: spelaren, klockan, kortet och // utfallet. Ren logik – LevelScene ritar den, testerna kör den headless. type Run struct { Level *level.Level Player *Mover Card Card Time float64 // sekunder sedan banstart (styr tidtabellerna) Steps int GatePasses int DeniedAt float64 // när spärren senast nekade (för HUD); < 0 = aldrig Outcome Outcome Boarded *level.Train } // NewRun startar banan med spelaren på startrutan. func NewRun(l *level.Level, speed float64) *Run { return &Run{ Level: l, Player: NewMover(l.SpawnPoint(), speed), Card: Card{Balance: StartBalanceYen}, DeniedAt: -1, } } // CanEnter avgör om spelaren får gå in på en ruta: inte vägg, och en spärr // kräver att kortet kan betala avgiften (att gå mellan två spärrutor är gratis). func (r *Run) CanEnter(p level.Point) bool { t := r.Level.At(p.X, p.Y) if !t.Passable() { return false } if t == level.Gate && !r.inGate() && !r.Card.CanPay(r.Level.FareYen) { r.DeniedAt = r.Time return false } return true } func (r *Run) inGate() bool { return r.Level.At(r.Player.Tile.X, r.Player.Tile.Y) == level.Gate } // Step för banan framåt dt sekunder. want är hållen riktning, board=true // betyder att spelaren försöker kliva på tåget där hen står. func (r *Run) Step(dt float64, want Dir, board bool) { if r.Outcome != Ongoing { return } r.Time += dt before := r.Player.Tile wasGate := r.inGate() r.Steps += r.Player.Step(dt, want, r.CanEnter) if r.Player.Tile != before && r.inGate() && !wasGate { r.Card.Pay(r.Level.FareYen) r.GatePasses++ } if board && !r.Player.Moving() { if tr, at, _ := r.PlatformHere(); tr != nil && at { r.Board(tr) } } } // PlatformHere returnerar tåget vid perrongen spelaren står på (nil om // spelaren inte står på en perrong), om det är inne, och sekunder kvar // till avgång respektive ankomst. func (r *Run) PlatformHere() (tr *level.Train, atPlatform bool, seconds float64) { t := r.Level.At(r.Player.Tile.X, r.Player.Tile.Y) if t != level.Platform && t != level.WrongPlatform { return nil, false, 0 } tr = r.Level.TrainAt(t) if tr == nil { return nil, false, 0 } atPlatform, seconds = tr.Schedule.Status(r.Time) return tr, atPlatform, seconds } // SignHere returnerar skylten spelaren står stilla på, annars nil. func (r *Run) SignHere() *level.SignText { if r.Player.Moving() { return nil } return r.Level.SignAt(r.Player.Tile) } // Board kliver på ett tåg och avgör banan. func (r *Run) Board(tr *level.Train) { r.Boarded = tr if tr.Correct { r.Outcome = Won } else { r.Outcome = Lost } } // TrainVisible rapporterar om tåget för en perrongruta står inne just nu. func (r *Run) TrainVisible(t level.Tile) bool { tr := r.Level.TrainAt(t) if tr == nil { return false } at, _ := tr.Schedule.Status(r.Time) return at }