Files
metro-puzzle-jp/internal/lang/knowledge.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

188 lines
5.1 KiB
Go

package lang
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"`
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"
// kana-läsningen och alla kana-ord går att läsa (som romaji) utan att förstås.
const KanaUnlockWords = 3
// WordsPerLevel styr japanska-nivån: nivå = kända ord / WordsPerLevel, max MaxLevel.
const (
WordsPerLevel = 4
MaxLevel = 5
)
// NewKnowledge börjar från noll.
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] }
// Learn lär in ett ord. Returnerar true om det var nytt. Låser upp kana när
// tillräckligt många kana-ord är kända.
func (k *Knowledge) Learn(word string) bool {
if k.Words == nil {
k.Words = map[string]bool{}
}
if k.Words[word] {
return false
}
k.Words[word] = true
if !k.Kana {
n := 0
for w := range k.Words {
if HasKana(w) {
n++
}
}
if n >= KanaUnlockWords {
k.Kana = true
}
}
return true
}
// Count är antalet kända ord.
func (k *Knowledge) Count() int { return len(k.Words) }
// Level är japanska-nivån 0..MaxLevel.
func (k *Knowledge) Level() int {
return int(math.Min(MaxLevel, float64(k.Count()/WordsPerLevel)))
}
// Hintar som växer med nivån.
// ShowsDestinationJP: från nivå 1 visar HUD:en resmålet även på japanska så
// att man kan matcha tecknen mot skyltarna.
func (k *Knowledge) ShowsDestinationJP() bool { return k.Level() >= 1 }
// ShowsReadings: från nivå 2 visas läsningen (romaji) för kända ord på skyltar.
func (k *Knowledge) ShowsReadings() bool { return k.Level() >= 2 }
// HelpSeconds är hur länge det tar att få hjälp av en förbipasserande.
// Bättre japanska = snabbare, men aldrig under HelpMinSeconds.
const (
HelpBaseSeconds = 6.0
HelpPerLevel = 0.8
HelpMinSeconds = 2.0
)
// HelpSeconds beräknar väntetiden för en given nivå.
func HelpSeconds(level int) float64 {
return math.Max(HelpMinSeconds, HelpBaseSeconds-HelpPerLevel*float64(level))
}
// EnglishShare är andelen skyltar som också visar engelska, per svårighet.
func EnglishShare(difficulty int) float64 {
switch {
case difficulty <= 1:
return 1.0
case difficulty == 2:
return 0.6
case difficulty == 3:
return 0.3
case difficulty == 4:
return 0.1
default:
return 0
}
}
// ShowEnglish avgör deterministiskt om skylten på (x, y) i en bana visar
// engelska: banor är statiska så valet hashas från bana + position, inte
// från spelomgångens seed.
func ShowEnglish(levelID string, x, y, difficulty int) bool {
share := EnglishShare(difficulty)
if share >= 1 {
return true
}
if share <= 0 {
return false
}
h := uint32(2166136261)
for _, b := range []byte(levelID) {
h = (h ^ uint32(b)) * 16777619
}
for _, v := range []int{x, y} {
h = (h ^ uint32(v+7)) * 16777619
}
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
}