All checks were successful
release / build-release (push) Successful in 1m42s
- 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>
97 lines
2.1 KiB
Go
97 lines
2.1 KiB
Go
package lang
|
|
|
|
import "strings"
|
|
|
|
// Kind är vad ett textsegment är.
|
|
type Kind int
|
|
|
|
// Segmenttyper.
|
|
const (
|
|
Plain Kind = iota // allt som inte är japanska: siffror, pilar, latin, mellanslag
|
|
Word // ett ord ur ordboken
|
|
Unknown // japanska tecken som inte täcks av ordboken
|
|
)
|
|
|
|
// Segment är en bit av en skylttext.
|
|
type Segment struct {
|
|
Text string
|
|
Kind Kind
|
|
Entry *Entry // satt när Kind == Word
|
|
}
|
|
|
|
// Tokenize delar upp en text i ordboksord (längsta matchning först), okänd
|
|
// japanska och övrig text. Sammanhängande okänd japanska slås ihop till ett
|
|
// segment så att tester kan peka ut vad som saknas i ordboken.
|
|
func (d *Dictionary) Tokenize(s string) []Segment {
|
|
runes := []rune(s)
|
|
var out []Segment
|
|
flush := func(kind Kind, text string) {
|
|
if text == "" {
|
|
return
|
|
}
|
|
if n := len(out); n > 0 && out[n-1].Kind == kind && kind != Word {
|
|
out[n-1].Text += text
|
|
return
|
|
}
|
|
out = append(out, Segment{Text: text, Kind: kind})
|
|
}
|
|
for i := 0; i < len(runes); {
|
|
r := runes[i]
|
|
if !IsKana(r) && !IsKanji(r) {
|
|
flush(Plain, string(r))
|
|
i++
|
|
continue
|
|
}
|
|
if w := d.longestMatch(runes[i:]); w != "" {
|
|
out = append(out, Segment{Text: w, Kind: Word, Entry: d.entries[w]})
|
|
i += len([]rune(w))
|
|
continue
|
|
}
|
|
flush(Unknown, string(r))
|
|
i++
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (d *Dictionary) longestMatch(rest []rune) string {
|
|
for _, w := range d.words { // längsta först
|
|
wr := []rune(w)
|
|
if len(wr) > len(rest) {
|
|
continue
|
|
}
|
|
match := true
|
|
for k, c := range wr {
|
|
if rest[k] != c {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if match {
|
|
return w
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Unknown returnerar den japanska i texten som ordboken inte täcker.
|
|
func (d *Dictionary) Unknown(s string) []string {
|
|
var out []string
|
|
for _, seg := range d.Tokenize(s) {
|
|
if seg.Kind == Unknown {
|
|
out = append(out, strings.TrimSpace(seg.Text))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// WordsIn returnerar ordboksorden i texten i ordning (med dubbletter).
|
|
func (d *Dictionary) WordsIn(s string) []string {
|
|
var out []string
|
|
for _, seg := range d.Tokenize(s) {
|
|
if seg.Kind == Word {
|
|
out = append(out, seg.Text)
|
|
}
|
|
}
|
|
return out
|
|
}
|