M3: japanska skyltar – pixelfont, ordbok, kunskap, delvis översättning, hjälp
All checks were successful
release / build-release (push) Successful in 1m42s
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>
This commit is contained in:
@@ -1,27 +1,137 @@
|
||||
package game
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
// Debugfonten är 6 px bred per tecken; 320 px ger drygt 50 tecken.
|
||||
const textCols = 50
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
|
||||
// wrap radbryter en text på ordgränser till högst width tecken per rad.
|
||||
func wrap(s string, width int) []string {
|
||||
var lines []string
|
||||
cur := ""
|
||||
for _, w := range strings.Fields(s) {
|
||||
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/lang"
|
||||
)
|
||||
|
||||
type drawTarget = *ebiten.Image
|
||||
|
||||
// Färger för text.
|
||||
var (
|
||||
colText = color.RGBA{255, 255, 255, 255}
|
||||
colDim = color.RGBA{139, 155, 180, 255} // oöversatt / sekundärt
|
||||
colKnown = color.RGBA{254, 174, 52, 255} // kända ord / översättning
|
||||
colReading = color.RGBA{99, 199, 77, 255} // kana som kan läsas men inte förstås
|
||||
colEnglish = color.RGBA{44, 232, 245, 255}
|
||||
colBad = color.RGBA{228, 59, 68, 255}
|
||||
colGood = color.RGBA{99, 199, 77, 255}
|
||||
colWarn = color.RGBA{254, 174, 52, 255}
|
||||
colPanel = color.RGBA{24, 20, 37, 230}
|
||||
colBar = color.RGBA{58, 68, 102, 255}
|
||||
)
|
||||
|
||||
// span är en textbit med färg; en rad är flera spans.
|
||||
type span struct {
|
||||
text string
|
||||
col color.Color
|
||||
}
|
||||
|
||||
type line []span
|
||||
|
||||
// plainLine gör en rad av en enda sträng.
|
||||
func plainLine(s string, col color.Color) line { return line{{s, col}} }
|
||||
|
||||
// shownLine gör en rad av språksystemets segment med färg efter kunskap.
|
||||
// Med readings visas läsningen (romaji) efter kända ord – hinten från nivå 2.
|
||||
func shownLine(shown []lang.Shown, base color.Color, readings bool) line {
|
||||
var l line
|
||||
for _, sh := range shown {
|
||||
col := base
|
||||
switch {
|
||||
case cur == "":
|
||||
cur = w
|
||||
case len([]rune(cur))+1+len([]rune(w)) > width:
|
||||
lines = append(lines, cur)
|
||||
cur = w
|
||||
case sh.Reading:
|
||||
col = colReading
|
||||
case sh.Known && lang.IsJapanese(sh.Text):
|
||||
col = colKnown
|
||||
case sh.Known:
|
||||
col = base
|
||||
default:
|
||||
cur += " " + w
|
||||
col = colDim
|
||||
}
|
||||
l = append(l, span{sh.Text, col})
|
||||
if readings && sh.Romaji != "" {
|
||||
l = append(l, span{"(" + sh.Romaji + ")", colDim})
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// wrapLine radbryter en rad till maxW pixlar. Bryter vid mellanslag, och
|
||||
// mitt i ett ord bara om ordet ensamt är för långt (japanska saknar mellanslag).
|
||||
func (a *Assets) wrapLine(l line, maxW int) []line {
|
||||
var out []line
|
||||
var cur line
|
||||
curW := 0
|
||||
flush := func() {
|
||||
if len(cur) > 0 {
|
||||
out = append(out, cur)
|
||||
cur, curW = nil, 0
|
||||
}
|
||||
}
|
||||
add := func(s string, col color.Color) {
|
||||
w := a.Width(s)
|
||||
if curW+w > maxW && curW > 0 {
|
||||
flush()
|
||||
s = strings.TrimLeft(s, " ")
|
||||
w = a.Width(s)
|
||||
}
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
if n := len(cur); n > 0 && cur[n-1].col == col {
|
||||
cur[n-1].text += s
|
||||
} else {
|
||||
cur = append(cur, span{s, col})
|
||||
}
|
||||
curW += w
|
||||
}
|
||||
for _, sp := range l {
|
||||
for _, tok := range tokens(sp.text) {
|
||||
if a.Width(tok) <= maxW {
|
||||
add(tok, sp.col)
|
||||
continue
|
||||
}
|
||||
for _, r := range tok { // för långt ord: bryt per tecken
|
||||
add(string(r), sp.col)
|
||||
}
|
||||
}
|
||||
}
|
||||
flush()
|
||||
return out
|
||||
}
|
||||
|
||||
// tokens delar en sträng i ord och mellanslagsrunor, båda bevarade.
|
||||
func tokens(s string) []string {
|
||||
var out []string
|
||||
cur := ""
|
||||
for _, r := range s {
|
||||
if r == ' ' {
|
||||
if cur != "" {
|
||||
out = append(out, cur)
|
||||
cur = ""
|
||||
}
|
||||
out = append(out, " ")
|
||||
continue
|
||||
}
|
||||
cur += string(r)
|
||||
}
|
||||
if cur != "" {
|
||||
out = append(out, cur)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// drawLines ritar rader från (x, y) med radhöjd LineH.
|
||||
func (a *Assets) drawLines(dst drawTarget, lines []line, x, y int) {
|
||||
for i, l := range lines {
|
||||
cx := x
|
||||
for _, sp := range l {
|
||||
a.Draw(dst, sp.text, cx, y+i*LineH, sp.col)
|
||||
cx += a.Width(sp.text)
|
||||
}
|
||||
}
|
||||
if cur != "" {
|
||||
lines = append(lines, cur)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user