M10c: vem betalar i kiosken och spärrar utan återvändo
All checks were successful
release / build-release (push) Successful in 2m7s

Björns tillägg 2026-10-24. Med egna kort och fler i sällskapet frågar
kiosken vem som betalar (minigame.Payer: kontant, spelarens kort eller en
följares kort; den som inte har råd nekas, Esc avbryter). Avgiften dras
nu när man passerar spärren in i det betalda området (level.InsideGates),
inte på spärrrutan. Banflaggan oneWayGates: incheckad släpps inte ut igen
förrän man åkt; Enter vid spärren kallar på personal (10 s) som låser upp
korten; ny inpassering kostar igen. På i 005, 007/007b och 008/008b.
Skriptkommandon urge och push. Docs, wiki och skärmdumpar uppdaterade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 22:12:13 +02:00
parent 4ae6a1d24c
commit f04d6a88f9
26 changed files with 674 additions and 64 deletions

View File

@@ -267,6 +267,7 @@ type Level struct {
MaxMissedTrains int `json:"maxMissedTrains"` // >0: fler missade tåg än så förlorar banan
IndividualCards bool `json:"individualCards"` // alla i sällskapet har egna kort (och biljetter) som spärren kräver
SeatSelection bool `json:"seatSelection"` // biljettköpet fortsätter med sittplatsval efter sällskapets önskemål
OneWayGates bool `json:"oneWayGates"` // spärren släpper inte ut en igen förrän man åkt; personal vid spärren låser upp (tar tid)
FareYen int `json:"fareYen"` // vad spärren drar från kortet (0 = standard)
Grid []string `json:"grid,omitempty"` // envåningsbana (kompatibilitet) – blir Floors[0]
Floors []Storey `json:"floors,omitempty"` // flervåningsbana; alla våningar lika stora
@@ -618,6 +619,32 @@ func (l *Level) Solvable() bool {
return false
}
// InsideGates är rutorna innanför spärrarna: allt som nås från perrongerna
// utan att passera en spärr. Används av spärrar utan återvändo.
func (l *Level) InsideGates() map[Point]bool {
seen := map[Point]bool{}
var queue []Point
for _, t := range PlatformTiles {
for _, p := range l.Find(t) {
seen[p] = true
queue = append(queue, p)
}
}
for len(queue) > 0 {
p := queue[0]
queue = queue[1:]
for _, n := range l.Neighbors(p) {
t := l.At(n)
if seen[n] || !t.Passable() || t == Gate {
continue
}
seen[n] = true
queue = append(queue, n)
}
}
return seen
}
// UngatedPlatforms listar perronger som går att nå från start utan att
// passera någon spärr. Speldesignen kräver att alla perronger ligger
// innanför spärrar, så listan ska vara tom för skeppade banor.

View File

@@ -732,3 +732,29 @@ func TestStoryChains(t *testing.T) {
t.Errorf("giltig kedja: %v", err)
}
}
func TestOneWayGatesAndInside(t *testing.T) {
src := `{"id":"ow","name":{"sv":"x"},"difficulty":1,"destination":{"sv":"x"},"oneWayGates":true,
"grid":["#########","#S.G.P.G#","#########"],
"trains":[{"platform":"P","track":"1","line":{"en":"A"},"destination":{"en":"B"},"correct":true}]}`
l, err := Parse([]byte(src))
if err != nil {
t.Fatal(err)
}
if !l.OneWayGates {
t.Fatal("flaggan ska läsas")
}
in := l.InsideGates()
want := map[Point]bool{{X: 4, Y: 1}: true, {X: 5, Y: 1}: true, {X: 6, Y: 1}: true}
if len(in) != len(want) {
t.Fatalf("innanför spärrarna: %v", in)
}
for p := range want {
if !in[p] {
t.Errorf("%v ska vara innanför", p)
}
}
if in[Point{X: 3, Y: 1}] || in[Point{X: 1, Y: 1}] {
t.Fatal("spärren och starten är inte innanför")
}
}