Files
metro-puzzle-jp/internal/sim/party_test.go
claude daa2089eda
All checks were successful
release / build-release (push) Successful in 1m49s
Inlärning (framsteg, stjärnor, ordlista) och M4: lämmeltåget
Inlärning enligt Björns regler:
- lang.Knowledge: framsteg per ord (3 läsningar = känt), språkpoäng,
  köp av valfria ord (kostnad = tier), Award per stjärna
- sim.Run: RunOptions med seed; stjärnor 1–3 mot par (parSec eller
  kortaste vägen × 0,5 s + 20 s); ≥2 stjärnor lär resmålets ord; att
  stanna på skylt/perrong ger seedad 35 % chans till framsteg per okänt ord
- game: ordlista (L) med kända ord, framsteg och kostnad; stjärnor, poäng
  och inlärt resmål på resultatskärmen; framstegsnotis; flaggor -points/-seed

M4 lämmeltåget:
- sim.Party: medlemmar med mat/dryck/toa-mätare, behov under tröskel,
  seedade händelser var 45 s / antal (toa, törst, hunger, distraktion),
  fartstraff, toalett (W, 3 s/person) och kiosk (K, 150 ¥/person),
  följare på ledarens spår
- level: rutor W och K, party och parSec i bandatan; banor med sällskap
  måste ha toalett och kiosk (testas); 001 har 2, 002 har 3 i sällskapet
- game: följare med färgton, behovsmarkering över huvudet, mätarpanel,
  facilitetsrutor med förlopp; genart ritar toalett och kiosk
- skriptläget: move stannar exakt på målrutan även när farten inte är
  bildruteexakt (sällskapets fartstraff), stall-skydd mot väggar
- docs/README/wiki/spelbeskrivning uppdaterade

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-23 21:29:09 +02:00

144 lines
4.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sim
import (
"strings"
"testing"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/rng"
)
func TestMetersDecayAtRates(t *testing.T) {
p := NewParty(2, pt(1, 1), rng.FromInt(1))
for i := 0; i < 60*60; i++ { // 60 s
p.Advance(frame, float64(i)*frame)
}
m := p.Members[1].Meters
for _, c := range []struct {
name string
got float64
rate float64
}{{"mat", m.Food, FoodPerSec}, {"dryck", m.Drink, DrinkPerSec}, {"toa", m.Toilet, ToiletPerSec}} {
if want := 100 - c.rate*60; c.got > want+0.5 || c.got < want-0.5 {
t.Errorf("%s = %.1f, vill ha ~%.1f", c.name, c.got, want)
}
}
}
// TestMoreMembersMoreEvents: fler i lämmeltåget ger fler händelser.
func TestMoreMembersMoreEvents(t *testing.T) {
count := func(n int) int {
p := NewParty(n, pt(1, 1), rng.FromInt(3))
for i := 0; i < 60*600; i++ { // 10 min
p.Advance(frame, float64(i)*frame)
}
return p.Events
}
one, four := count(1), count(4)
if one < 8 || one > 20 {
t.Fatalf("ensam: %d händelser på 10 min (väntat ~13)", one)
}
if four < 3*one {
t.Fatalf("fyra i sällskapet (%d) ska ge klart fler händelser än en (%d)", four, one)
}
}
func TestEventsAreSeeded(t *testing.T) {
run := func(seed uint64) []string {
p := NewParty(3, pt(1, 1), rng.FromInt(seed))
for i := 0; i < 60*300; i++ {
p.Advance(frame, float64(i)*frame)
}
return p.Log
}
a, b := run(9), run(9)
if strings.Join(a, "|") != strings.Join(b, "|") {
t.Fatal("samma seed ska ge samma händelser")
}
if strings.Join(a, "|") == strings.Join(run(10), "|") {
t.Fatal("olika seeds ska ge olika händelser")
}
if len(a) == 0 {
t.Fatal("inga händelser på fem minuter")
}
}
func TestUrgesSlowAndFacilitiesRestore(t *testing.T) {
p := NewParty(3, pt(1, 1), rng.FromInt(1))
if p.SpeedFactor() != 1 {
t.Fatal("utan behov full fart")
}
anna, ville := p.Members[1], p.Members[2]
anna.setUrge(NeedToilet, 0)
anna.Meters.Toilet = 10
if p.SpeedFactor() != UrgeSpeedFactor || len(p.NeedsToilet()) != 1 {
t.Fatalf("toabehov: fart=%.2f behöver=%d", p.SpeedFactor(), len(p.NeedsToilet()))
}
if n := p.UseToilet(); n != 1 || anna.Urge != NoUrge || anna.Meters.Toilet != 100 || p.SpeedFactor() != 1 {
t.Fatalf("toaletten: n=%d urge=%v toa=%.0f", n, anna.Urge, anna.Meters.Toilet)
}
ville.setUrge(Thirsty, 0)
ville.Meters.Drink = 5
anna.Meters.Food = 40 // under RestoreBelow men utan behov – kiosken hjälper ändå
paid := 0
pay := func(yen int) bool { paid += yen; return true }
if n := p.BuyAtKiosk(pay); n != 2 || paid != 2*KioskPriceYen || ville.Urge != NoUrge || ville.Meters.Drink != 100 || anna.Meters.Food != 100 {
t.Fatalf("kiosken: n=%d betalt=%d", n, paid)
}
// Slut på pengar: ingen försörjs.
ville.Meters.Drink = 5
if n := p.BuyAtKiosk(func(int) bool { return false }); n != 0 || ville.Meters.Drink != 5 {
t.Fatal("utan pengar ska ingen få något")
}
ville.Meters.Drink = 100 // annars uppstår törsten igen när distraktionen går över
p.nextEventAt = 1e9 // inga slumphändelser i resten av testet
ville.setUrge(Distracted, 100)
if p.SpeedFactor() != DistractedSpeedFactor {
t.Fatal("distraktion halverar farten")
}
p.Advance(frame, 100+DistractionSec)
if ville.Urge != NoUrge {
t.Fatal("distraktionen ska gå över av sig själv")
}
ville.Meters.Food = 0
if p.SpeedFactor() != EmptySpeedFactor {
t.Fatal("tom mätare ger stor fartstraff")
}
}
func TestUrgeArisesBelowThreshold(t *testing.T) {
p := NewParty(1, pt(1, 1), rng.FromInt(1))
p.Members[0].Meters.Drink = UrgeThreshold - 1
p.Advance(frame, 0)
if p.Members[0].Urge != Thirsty {
t.Fatalf("urge=%v", p.Members[0].Urge)
}
}
func TestFollowersTrailLeader(t *testing.T) {
p := NewParty(3, pt(1, 1), rng.FromInt(1))
for _, tile := range []level.Point{pt(2, 1), pt(3, 1), pt(3, 2)} {
p.OnLeaderArrived(tile)
}
p.OnLeaderArrived(pt(3, 2)) // samma ruta igen: ingen dubblett
if from, to := p.FollowerTiles(1); from != pt(3, 1) || to != pt(3, 2) {
t.Fatalf("följare 1: %v -> %v", from, to)
}
if from, to := p.FollowerTiles(2); from != pt(2, 1) || to != pt(3, 1) {
t.Fatalf("följare 2: %v -> %v", from, to)
}
// Vid start står alla på startrutan.
q := NewParty(3, pt(5, 5), rng.FromInt(1))
if from, to := q.FollowerTiles(2); from != pt(5, 5) || to != pt(5, 5) {
t.Fatalf("start: %v -> %v", from, to)
}
}
func TestPartySizeIsClamped(t *testing.T) {
if NewParty(0, pt(0, 0), rng.FromInt(1)).Size() != 1 || NewParty(99, pt(0, 0), rng.FromInt(1)).Size() != len(PartyNames) {
t.Fatal("storleken ska klampas")
}
}