M3: japanska skyltar – pixelfont, ordbok, kunskap, delvis översättning, hjälp
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:
2026-10-23 20:14:36 +02:00
parent 0e10cbf123
commit b67493fddb
40 changed files with 1846 additions and 174 deletions

View File

@@ -1,16 +1,26 @@
package game
import (
"bytes"
"fmt"
"image"
"image/color"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/text/v2"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/lang"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/res"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/sim"
)
// UILang är UI-språket (sv/en) – blir en inställning i M12.
const UILang = "sv"
// Radhöjd för 10-pixelsfonten.
const LineH = 12
// Ordningen på rutorna i resources/sprites/tiles.png (måste matcha tools/genart).
var tileOrder = []level.Tile{
level.Wall, level.Floor, level.Spawn, level.Platform, level.Gate, level.TicketMachine, level.Sign,
@@ -24,6 +34,9 @@ type Assets struct {
Train *ebiten.Image // tågvagn, ritas vid perrongen när tåget är inne
Player [4][3]*ebiten.Image // [riktning: ner, upp, vänster, höger][bildruta: stilla, gå1, gå2]
Text map[string]string
Dict *lang.Dictionary
Font *text.GoTextFace // PixelMplus 10 px – UI och skyltar
FontBig *text.GoTextFace // PixelMplus 12 px – rubriker
}
// LoadAssets läser grafik och texter.
@@ -61,12 +74,62 @@ func LoadAssets() (*Assets, error) {
}
}
if err := res.LoadJSON(&a.Text, "text", "sv.json"); err != nil {
if err := res.LoadJSON(&a.Text, UILang+".json"); err != nil {
if err := res.LoadJSON(&a.Text, "text", UILang+".json"); err != nil {
return nil, err
}
}
dict, err := res.ReadFile("text", "jp-dictionary.json")
if err != nil {
return nil, err
}
if a.Dict, err = lang.ParseDictionary(dict); err != nil {
return nil, err
}
if a.Font, err = loadFont("PixelMplus10-Regular.ttf", 10); err != nil {
return nil, err
}
if a.FontBig, err = loadFont("PixelMplus12-Regular.ttf", 12); err != nil {
return nil, err
}
return a, nil
}
func loadFont(name string, size float64) (*text.GoTextFace, error) {
b, err := res.ReadFile("text", "fonts", name)
if err != nil {
return nil, err
}
src, err := text.NewGoTextFaceSource(bytes.NewReader(b))
if err != nil {
return nil, fmt.Errorf("font %s: %w", name, err)
}
return &text.GoTextFace{Source: src, Size: size}, nil
}
// Draw ritar en rad text med 10-pixelsfonten; (x, y) är övre vänstra hörnet.
// Heltalspositioner ger pixelskarpa glyfer.
func (a *Assets) Draw(dst *ebiten.Image, s string, x, y int, col color.Color) {
a.drawFace(dst, a.Font, s, x, y, col)
}
// DrawBig ritar med 12-pixelsfonten.
func (a *Assets) DrawBig(dst *ebiten.Image, s string, x, y int, col color.Color) {
a.drawFace(dst, a.FontBig, s, x, y, col)
}
func (a *Assets) drawFace(dst *ebiten.Image, face *text.GoTextFace, s string, x, y int, col color.Color) {
op := &text.DrawOptions{}
op.GeoM.Translate(float64(x), float64(y))
op.ColorScale.ScaleWithColor(col)
text.Draw(dst, s, face, op)
}
// Width är textens bredd i pixlar med 10-pixelsfonten.
func (a *Assets) Width(s string) int { return int(text.Advance(s, a.Font)) }
// T slår upp en UI-text; saknas nyckeln visas nyckeln själv så att det syns.
func (a *Assets) T(key string) string {
if s, ok := a.Text[key]; ok {