All checks were successful
release / build-release (push) Successful in 1m35s
- level: Schedule (ankomst/intervall/uppehåll) med Status(t), spårnummer, avgift, skylttexter per i-ruta, Path (BFS) och UngatedPlatforms – regeln att perronger bara nås via spärrar testas för alla banor - sim.Run: klocka, tågkort (saldo dras vid spärr, tomt kort stoppas), ombordstigning med Enter när tåget är inne, vinst/förlust; headless bot-test klarar bana 001 och förlorar på fel tåg - game: perronginfo (avgår om / nästa tåg om), skyltruta, nekad spärr, tågvagn som ritas när tåget är inne, inforutan flyttas upp när spelaren står nära nederkanten; resultat med spärrpassager och saldo - bana 001 ombyggd så spärrarna är flaskhalsar; bana 002 omgenererad med spärrar runt perrongerna och skylttexter - tools/shots räknar ut vägar med BFS; skriptkommandot waittrain - docs/README/wiki uppdaterade med nya bilder Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
139 lines
3.6 KiB
Go
139 lines
3.6 KiB
Go
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
|
||
}
|