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>
188 lines
5.1 KiB
Go
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
|
|
}
|