Files
metro-puzzle-jp/internal/lang/knowledge.go
claude b67493fddb
All checks were successful
release / build-release (push) Successful in 1m42s
M3: japanska skyltar – pixelfont, ordbok, kunskap, delvis översättning, hjälp
- resources/text/fonts: PixelMplus 10/12 px (M+ FONT LICENSE) + licens;
  all text ritas via text/v2 på heltalspositioner, debugfonten är borta
- internal/lang: ordbok (60 ord, validerad), tokenisering med längsta
  matchning, Knowledge (ord + kana, nivå 0–5, hintar), Render (kända ord
  markerade/översatta, okända kvar, kana som romaji), EnglishShare/
  ShowEnglish per svårighet (deterministiskt per bana+position),
  HelpSeconds; datatest kräver att all japanska i banorna finns i ordboken
- sim.Run: AskHelp (H) med väntetid efter nivå, avbryts om man går,
  avslöjar skylten och lär första okända ordet; Knowledge delas mellan banor
- game: skylt- och avgångstavlor renderade genom kunskapen med färgkodning,
  engelsk rad på lätta banor, hjälp-förlopp, "Nytt ord"-notis, HUD visar
  resmålet på japanska från nivå 1 och japanska-nivå; meny med svårighet;
  pixelbaserad radbrytning av färgade textsegment; -know-flagga
- tools/shots: tre skyltbilder (skylt, hjälp, nytt ord) med förkunskap
- docs/README/wiki uppdaterade

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-23 20:14:36 +02:00

118 lines
3.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"`
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{}} }
// 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
}