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):] } }