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 }