All checks were successful
release / build-release (push) Successful in 1m40s
Björns feedback efter v0.1.0. Titelskärm med premissen i text och menyn Fortsätt/Starta, Tutorial, Inställningar, Avsluta (Enter två gånger); språkval första gången spelet startar. Tutorial i fem sidor med riktiga sprites och en exempelskylt där resmålet markeras och resten tonas ned, plus nyckeln 番線/方面/改札 och biljettipset. Kontextuella tips i spelet tills första banan är klarad, och resmålets tecken markeras på skyltar och avgångstavlor under tiden. Esc i en bana öppnar en pausmeny (Fortsätt, Lämna banan, Inställningar, Avsluta spelet) i stället för att lämna banan; Esc på bankartan går till titeln. Kortare lägestexter så att bankartans rader ryms på svenska. Test för markeringen, docs, wiki och skärmdumpar. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
161 lines
3.6 KiB
Go
161 lines
3.6 KiB
Go
package game
|
||
|
||
import (
|
||
"image/color"
|
||
"strings"
|
||
|
||
"github.com/hajimehoshi/ebiten/v2"
|
||
|
||
"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 sh.Reading:
|
||
col = colReading
|
||
case sh.Known && lang.IsJapanese(sh.Text):
|
||
col = colKnown
|
||
case sh.Known:
|
||
col = base
|
||
default:
|
||
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)
|
||
}
|
||
}
|
||
}
|
||
|
||
// highlightLine är tutorialens sätt att läsa en skylt: resmålets japanska
|
||
// tecken i markeringsfärg, allt annat nedtonat.
|
||
func highlightLine(text, needle string, hi, dim color.Color) line {
|
||
if needle == "" || !strings.Contains(text, needle) {
|
||
return plainLine(text, dim)
|
||
}
|
||
var l line
|
||
for {
|
||
i := strings.Index(text, needle)
|
||
if i < 0 {
|
||
if text != "" {
|
||
l = append(l, span{text, dim})
|
||
}
|
||
return l
|
||
}
|
||
if i > 0 {
|
||
l = append(l, span{text[:i], dim})
|
||
}
|
||
l = append(l, span{needle, hi})
|
||
text = text[i+len(needle):]
|
||
}
|
||
}
|