M9b: folk, filer, reklam, sorl och vagnval
All checks were successful
release / build-release (push) Successful in 1m33s

Folk på stationen (NPC:er som vandrar, kliver av tåg och går mot
utgången, blockerar spelaren men inte varandra), vänster-/högerfiler
som bromsar på fel sida, bredare gångar, reklamaffischer (clutter),
japanskt sorl i loop (noise), vagnval med kvinnovagn (stort avdrag med
man i sällskapet, tidsstyrd), kömarkeringar på perrongen, trängsel per
vagn (bonus för tom vagn, svettig-debuff och svårare resa i full vagn,
släpp förbi passagerare) och att folk kliver av innan man får gå på.
Ny bana 005 Ueno. Skärmdumpsverktyget kan filtrera på bana/skript.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 12:33:44 +02:00
parent 8fca14e42d
commit de61568e44
49 changed files with 1568 additions and 136 deletions

View File

@@ -1,7 +1,6 @@
package minigame
import (
"math"
"strings"
"testing"
@@ -114,8 +113,8 @@ func TestTicketBoardSeededAndAffordable(t *testing.T) {
}
func TestCrowdZoneAndAttempts(t *testing.T) {
g := NewCrowd(rng.FromInt(7), 2, 1)
if g.ZoneLo < 0.1 || g.ZoneHi > 0.9 || math.Abs((g.ZoneHi-g.ZoneLo)-crowdZoneWidth) > 1e-9 {
g := NewCrowd(rng.FromInt(7), 2, 1, 1)
if w := g.ZoneHi - g.ZoneLo; g.ZoneLo < 0.1 || g.ZoneHi > 0.9 || w <= 0.1 || w > 0.6 {
t.Fatalf("zon %.2f–%.2f", g.ZoneLo, g.ZoneHi)
}
// Vänta tills markören är utanför zonen och tryck: ett bommat försök.
@@ -135,7 +134,7 @@ func TestCrowdZoneAndAttempts(t *testing.T) {
t.Fatal("träff i zonen ska lyckas")
}
// Två bom = tåget går.
f := NewCrowd(rng.FromInt(7), 2, 1)
f := NewCrowd(rng.FromInt(7), 2, 1, 1)
for n := 0; n < 2; n++ {
for f.InZone() {
f.Update(Input{}, frame)
@@ -146,7 +145,7 @@ func TestCrowdZoneAndAttempts(t *testing.T) {
t.Fatal("två bom ska misslyckas")
}
// Markören studsar och håller sig i 0..1.
b := NewCrowd(rng.FromInt(8), 1, 1)
b := NewCrowd(rng.FromInt(8), 1, 1, 1)
for i := 0; i < 600; i++ {
b.Update(Input{}, frame)
if b.Pos < 0 || b.Pos > 1 {
@@ -157,11 +156,11 @@ func TestCrowdZoneAndAttempts(t *testing.T) {
func TestRideStopsAndGettingOff(t *testing.T) {
fillers := []string{"東京", "上野", "新宿", "成田"}
g := NewRide(rng.FromInt(3), "渋谷", fillers, 4, true)
g := NewRide(rng.FromInt(3), "渋谷", fillers, 4, true, 0)
if len(g.Stops) != 4 || g.Dest < 1 || g.Stops[g.Dest] != "渋谷" {
t.Fatalf("stops=%v dest=%d", g.Stops, g.Dest)
}
h := NewRide(rng.FromInt(3), "渋谷", fillers, 4, true)
h := NewRide(rng.FromInt(3), "渋谷", fillers, 4, true, 0)
if strings.Join(g.Stops, ",") != strings.Join(h.Stops, ",") {
t.Fatal("samma seed = samma linje")
}
@@ -174,13 +173,13 @@ func TestRideStopsAndGettingOff(t *testing.T) {
t.Fatal("kliva av på resmålet ska lyckas")
}
// Kliva av för tidigt.
e := NewRide(rng.FromInt(3), "渋谷", fillers, 4, true)
e := NewRide(rng.FromInt(3), "渋谷", fillers, 4, true, 0)
e.Update(Input{Confirm: true}, frame)
if !e.Done() || e.Success() || e.GotOff != 0 {
t.Fatal("fel station = misslyckat")
}
// Åka förbi.
m := NewRide(rng.FromInt(3), "渋谷", fillers, 4, true)
m := NewRide(rng.FromInt(3), "渋谷", fillers, 4, true, 0)
for i := 0; i < 60*20 && !m.Done(); i++ {
m.Update(Input{}, frame)
}
@@ -188,7 +187,7 @@ func TestRideStopsAndGettingOff(t *testing.T) {
t.Fatalf("åka förbi: done=%v success=%v index=%d", m.Done(), m.Success(), m.Index)
}
// Fel tåg: resmålet finns inte på linjen, så det går aldrig att lyckas.
w := NewRide(rng.FromInt(3), "渋谷", fillers, 4, false)
w := NewRide(rng.FromInt(3), "渋谷", fillers, 4, false, 0)
if w.Dest != -1 || strings.Contains(strings.Join(w.Stops, ","), "渋谷") {
t.Fatalf("fel tåg: %v", w.Stops)
}
@@ -199,7 +198,7 @@ func TestRideStopsAndGettingOff(t *testing.T) {
}
func TestRideWithoutFillersStillHasDestination(t *testing.T) {
g := NewRide(rng.FromInt(1), "渋谷", nil, 3, true)
g := NewRide(rng.FromInt(1), "渋谷", nil, 3, true, 0)
if len(g.Stops) != 3 || g.Dest < 1 || g.Stops[g.Dest] != "渋谷" || g.Stops[0] != UnknownStop {
t.Fatalf("stops=%v dest=%d", g.Stops, g.Dest)
}
@@ -295,14 +294,118 @@ func TestSeatSelectionPreferences(t *testing.T) {
}
func TestCrowdLuckWidensZone(t *testing.T) {
normal := NewCrowd(rng.FromInt(3), 2, 1)
lucky := NewCrowd(rng.FromInt(3), 2, 1.5)
unlucky := NewCrowd(rng.FromInt(3), 2, 0.5)
normal := NewCrowd(rng.FromInt(3), 2, 1, 1)
lucky := NewCrowd(rng.FromInt(3), 2, 1.5, 1)
unlucky := NewCrowd(rng.FromInt(3), 2, 0.5, 1)
if lucky.ZoneHi-lucky.ZoneLo <= normal.ZoneHi-normal.ZoneLo || unlucky.ZoneHi-unlucky.ZoneLo >= normal.ZoneHi-normal.ZoneLo {
t.Fatal("tur ska ge bredare zon, otur smalare")
}
huge := NewCrowd(rng.FromInt(3), 2, 100)
huge := NewCrowd(rng.FromInt(3), 2, 100, 1)
if huge.ZoneHi-huge.ZoneLo > 0.6+1e-9 || huge.ZoneHi > 0.9+1e-9 {
t.Fatal("zonen klampas")
}
}
func TestCarPickAlightingAndChoice(t *testing.T) {
g := NewCarPick(rng.FromInt(5), 4, 1, AlightSec)
h := NewCarPick(rng.FromInt(5), 4, 1, AlightSec)
if len(g.Cars) != 4 || !g.Cars[0].WomenOnly || g.Cars[1].WomenOnly {
t.Fatalf("cars=%+v", g.Cars)
}
for i := range g.Cars {
if g.Cars[i] != h.Cars[i] {
t.Fatal("samma seed = samma trängsel")
}
if g.Cars[i].Crowd < 0 || g.Cars[i].Crowd > 2 {
t.Fatal("trängsel 0–2")
}
}
hasLow := false
for _, c := range g.Cars {
hasLow = hasLow || c.Crowd == 0
}
if !hasLow {
t.Fatal("minst en vagn med lite folk")
}
// För tidigt: folk kliver av först.
g.Update(Input{Confirm: true}, frame)
g.Update(Input{Confirm: true}, frame)
if g.Done() || g.Early != 2 {
t.Fatalf("early=%d done=%v", g.Early, g.Done())
}
for g.Alighting > 0 {
g.Update(Input{}, frame)
}
g.Update(Input{MoveX: 1}, frame)
before := g.Cursor
g.Update(Input{Confirm: true}, frame)
if !g.Done() || !g.Success() || g.Chosen == nil || g.Chosen.Index != before+1 {
t.Fatalf("val: chosen=%+v cursor=%d", g.Chosen, before)
}
c := NewCarPick(rng.FromInt(5), 1, 0, 0)
if len(c.Cars) != 1 || c.Cars[0].WomenOnly {
t.Fatal("ett tåg med en vagn")
}
c.Update(Input{Cancel: true}, frame)
if !c.Done() || c.Success() {
t.Fatal("Esc avbryter")
}
}
func TestCrowdLevelNarrowsZone(t *testing.T) {
w := func(level int) float64 {
g := NewCrowd(rng.FromInt(2), 2, 1, level)
return g.ZoneHi - g.ZoneLo
}
if !(w(0) > w(1) && w(1) > w(2)) {
t.Fatalf("zon: %.2f %.2f %.2f", w(0), w(1), w(2))
}
}
func TestRideCrowdLetPass(t *testing.T) {
fillers := []string{"東京", "上野", "新宿", "成田", "品川"}
g := NewRide(rng.FromInt(6), "渋谷", fillers, 5, true, 2)
if g.StopSec != RideStopSec(2) || RideStopSec(0) != 2.5 || RideStopSec(2) != 1.5 {
t.Fatal("tid per station")
}
wants := 0
for i, w := range g.WantsOut {
if w {
wants++
if i == g.Dest {
t.Fatal("ingen vill ut vid resmålet")
}
}
}
if wants == 0 {
t.Fatal("med mycket folk vill någon förbi")
}
// Släpp förbi vid första stationen som vill, blockera nästa.
released, blockedSeen := false, false
for i := 0; i < 60*60 && !g.Done(); i++ {
in := Input{}
if g.NeedsLetPass() && !released {
in.MoveX = -1
released = true
}
if g.Index == g.Dest {
in.Confirm = true
}
g.Update(in, frame)
if g.Blocked > 0 {
blockedSeen = true
}
}
if !g.Done() || !g.Success() {
t.Fatal("ska ändå komma fram")
}
if wants >= 2 && !blockedSeen {
t.Fatalf("minst en station ska ha blockerats: blocked=%d wants=%d", g.Blocked, wants)
}
calm := NewRide(rng.FromInt(6), "渋谷", fillers, 5, true, 0)
for _, w := range calm.WantsOut {
if w {
t.Fatal("utan trängsel vill ingen förbi")
}
}
}