Inlärning (framsteg, stjärnor, ordlista) och M4: lämmeltåget
All checks were successful
release / build-release (push) Successful in 1m49s

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>
This commit is contained in:
2026-10-23 21:29:09 +02:00
parent b67493fddb
commit daa2089eda
38 changed files with 1436 additions and 115 deletions

View File

@@ -4,9 +4,11 @@ import "math"
// Knowledge är vad spelaren kan: ord ur ordboken och om hen kan läsa kana.
type Knowledge struct {
Words map[string]bool `json:"words"`
Kana bool `json:"kana"`
Helps int `json:"helps"` // antal gånger spelaren frågat om hjälp
Words map[string]bool `json:"words"`
Progress map[string]int `json:"progress"` // framsteg på ord som ännu inte är kända
Points int `json:"points"` // språkpoäng att lägga på valfria ord
Kana bool `json:"kana"`
Helps int `json:"helps"` // antal gånger spelaren frågat om hjälp
}
// KanaUnlockWords: när så här många kända ord innehåller kana "lossnar"
@@ -20,7 +22,9 @@ const (
)
// NewKnowledge börjar från noll.
func NewKnowledge() *Knowledge { return &Knowledge{Words: map[string]bool{}} }
func NewKnowledge() *Knowledge {
return &Knowledge{Words: map[string]bool{}, Progress: map[string]int{}}
}
// Knows rapporterar om ordet är känt.
func (k *Knowledge) Knows(word string) bool { return k.Words[word] }
@@ -115,3 +119,69 @@ func ShowEnglish(levelID string, x, y, difficulty int) bool {
}
return float64(h%1000)/1000 < share
}
// Inlärning utöver hjälp (Björns regler 2026-10-23):
// - bra resultat på en bana lär ut resmålets ord (StarsToLearnDestination),
// - varje läst okänt ord har chansen ReadChance att ge +1 framsteg; vid
// ProgressToLearn framsteg kan man ordet,
// - språkpoäng (Points) tjänas på banor och läggs på valfria ord i ordlistan.
const (
ReadChance = 0.35
ProgressToLearn = 3
StarsToLearnDestination = 2
PointsPerStar = 1
)
// ProgressOf är hur många framsteg ordet har (0 om känt eller aldrig läst).
func (k *Knowledge) ProgressOf(word string) int {
if k.Progress == nil {
return 0
}
return k.Progress[word]
}
// AddProgress ger ett framsteg på ett okänt ord. Returnerar true om ordet
// därmed blev känt.
func (k *Knowledge) AddProgress(word string) bool {
if k.Knows(word) {
return false
}
if k.Progress == nil {
k.Progress = map[string]int{}
}
k.Progress[word]++
if k.Progress[word] >= ProgressToLearn {
delete(k.Progress, word)
k.Learn(word)
return true
}
return false
}
// WordCost är vad ett ord kostar i språkpoäng: dess tier (minst 1).
func WordCost(e *Entry) int { return max(1, e.Tier) }
// CanBuy rapporterar om ordet går att köpa för poängen.
func (k *Knowledge) CanBuy(e *Entry) bool {
return e != nil && !k.Knows(e.Word) && k.Points >= WordCost(e)
}
// Buy lär in ordet mot språkpoäng. Returnerar false om det inte går.
func (k *Knowledge) Buy(e *Entry) bool {
if !k.CanBuy(e) {
return false
}
k.Points -= WordCost(e)
if k.Progress != nil {
delete(k.Progress, e.Word)
}
k.Learn(e.Word)
return true
}
// Award ger språkpoäng för stjärnor på en klarad bana.
func (k *Knowledge) Award(stars int) int {
p := max(0, stars) * PointsPerStar
k.Points += p
return p
}

View File

@@ -208,3 +208,43 @@ func TestCharClasses(t *testing.T) {
t.Fatal("HasKana/IsJapanese")
}
}
func TestProgressLearnsAfterEnoughReads(t *testing.T) {
k := NewKnowledge()
for i := 1; i < ProgressToLearn; i++ {
if k.AddProgress("改札") || k.ProgressOf("改札") != i || k.Knows("改札") {
t.Fatalf("efter %d framsteg: progress=%d known=%v", i, k.ProgressOf("改札"), k.Knows("改札"))
}
}
if !k.AddProgress("改札") || !k.Knows("改札") || k.ProgressOf("改札") != 0 {
t.Fatal("tredje framsteget ska lära ordet och nollställa framstegen")
}
if k.AddProgress("改札") {
t.Fatal("kända ord får inga framsteg")
}
}
func TestBuyWordsWithPoints(t *testing.T) {
d := testDict()
k := NewKnowledge()
e1, e3 := d.Lookup("改札"), d.Lookup("新宿駅") // tier 1 resp. 3
if WordCost(e1) != 1 || WordCost(e3) != 3 {
t.Fatal("kostnad = tier")
}
if k.CanBuy(e1) || k.Buy(e1) {
t.Fatal("utan poäng ska inget gå att köpa")
}
if got := k.Award(3); got != 3*PointsPerStar || k.Points != got {
t.Fatalf("Award = %d, points=%d", got, k.Points)
}
k.Progress["新宿駅"] = 2
if !k.Buy(e3) || !k.Knows("新宿駅") || k.Points != 0 || k.ProgressOf("新宿駅") != 0 {
t.Fatalf("köp misslyckades: points=%d known=%v", k.Points, k.Knows("新宿駅"))
}
if k.CanBuy(e3) {
t.Fatal("redan känt ord ska inte gå att köpa")
}
if k.Award(-2) != 0 || k.Points != 0 {
t.Fatal("negativa stjärnor ger inget")
}
}