package game import ( "fmt" "path/filepath" "sort" "strings" "testing" "github.com/hajimehoshi/ebiten/v2/text/v2" "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level" "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/res" ) // loadTexts läser ett språks UI-texter. func loadTexts(t *testing.T, lang string) map[string]string { t.Helper() m := map[string]string{} if err := res.LoadJSON(&m, "text", lang+".json"); err != nil { t.Fatal(err) } return m } func uiFace(t *testing.T) *text.GoTextFace { t.Helper() face, err := loadFont("PixelMplus10-Regular.ttf", 10) if err != nil { t.Fatal(err) } return face } // TestSingleLineTextsFit: texter som ritas på en rad utan radbrytning ska // rymmas i sitt utrymme på båda språken. Bredderna mäts med den riktiga // fonten. Texter som radbryts vid visning (tutorial, tips, premiss …) ingår // inte. func TestSingleLineTextsFit(t *testing.T) { face := uiFace(t) width := func(s string) int { return int(text.Advance(s, face)) } const ( full = ScreenW - 16 // en rad över hela skärmen med marginal panel = ScreenW - 24 - 16 // minispelspanelens innerbredd resW = ScreenW - 56 // resultatpanelen pause = ScreenW - 96 // pausmenyn ) type check struct { key string max int args []any } checks := []check{ {"map.hint", full, nil}, {"map.hint2", full, nil}, {"menu.hint", full, nil}, {"settings.hint", full, nil}, {"title.hint", full, nil}, {"tutorial.hint", full, nil}, {"ending.hint", full, nil}, {"result.hint", resW, nil}, {"result.hint_endless", resW, nil}, {"result.hint_endless_over", resW, nil}, {"pause.hint", pause, nil}, {"pause.quit_confirm", pause, nil}, {"pause.resume", pause, nil}, {"pause.leave", pause, nil}, {"pause.quit", pause, nil}, {"title.quit_confirm", ScreenW - 32, nil}, {"title.tutorial", 110, nil}, {"menu.settings", 110, nil}, {"menu.quit", 110, nil}, {"menu.continue", 110, nil}, {"menu.start", 110, nil}, {"items.hint", full, nil}, {"skills.hint", full, nil}, {"words.hint", full, nil}, {"shop.hint", panel, nil}, {"mini.car.hint", panel, nil}, {"mini.car.pick", panel, nil}, {"mini.car.alighting", panel, nil}, {"mini.car.men", panel, nil}, {"mini.crowd.title", panel - 60, nil}, {"mini.crowd.hint", panel, []any{2}}, {"mini.ride.title", panel - 60, nil}, {"mini.ride.hint", panel, nil}, {"mini.ride.let_pass", panel, nil}, {"mini.ticket.title", panel - 60, nil}, {"mini.ticket.hint", panel, []any{"Narita Airport", 5000}}, {"mini.seat.hint", panel, []any{"Ville"}}, {"mini.seat.prefs", panel, nil}, {"mini.topup.title", panel - 60, nil}, {"mini.topup.feed", panel, []any{5000}}, {"mini.topup.pick", panel, nil}, {"mini.topup.amount", panel, nil}, {"mini.topup.member", panel, nil}, {"mini.topup.wrong", panel - 60, nil}, {"mini.payer.title", panel - 60, nil}, {"mini.payer.price", panel, []any{900}}, {"mini.payer.short", panel, nil}, {"mini.payer.hint", panel, nil}, {"hud.kiosk_prompt", full - 60, []any{4, 800}}, {"hud.toilet_prompt", full, []any{4, 32.0}}, {"hud.machine_topup", full, []any{100000, 100000}}, {"hud.machine_ticket", full, []any{100000}}, {"hud.help_prompt", full, []any{30.0}}, {"hud.train_near", full, nil}, {"hud.staff_busy", full, nil}, {"hud.toilet_busy", full, nil}, {"hud.kiosk_none", full - 60, nil}, {"hud.toilet_none", full, nil}, {"settings.master", 118, nil}, {"settings.effects", 118, nil}, {"settings.music", 118, nil}, {"settings.lang", 118, nil}, {"settings.scale", 118, nil}, {"settings.fullscreen", 118, nil}, {"settings.scale_auto", ScreenW - 130 - 8, nil}, {"ending.summary", full, nil}, {"ending.story_done", full * 2, nil}, } for _, lang := range []string{"sv", "en"} { m := loadTexts(t, lang) for _, c := range checks { s, ok := m[c.key] if !ok { t.Errorf("%s: nyckeln %s saknas", lang, c.key) continue } if len(c.args) > 0 { s = fmt.Sprintf(s, c.args...) } if w := width(s); w > c.max { t.Errorf("%s %s är %d px, får vara %d: %q", lang, c.key, w, c.max, s) } } // Bankartans sammansatta rader. line2 := fmt.Sprintf(m["menu.phone"], m["menu.on"]) + " " + fmt.Sprintf(m["menu.mode"], m["mode.signs"]) if w := width(line2); w > full { t.Errorf("%s bankartans lägesrad är %d px: %q", lang, w, line2) } endless := m["map.endless"] + " (" + fmt.Sprintf(m["map.endless_best"], 99999, 99) + ") " + m["map.ngplus"] + " (" + fmt.Sprintf(m["map.ngplus_level"], 9) + ")" if w := width(endless); w > full { t.Errorf("%s bankartans oändligt-rad är %d px: %q", lang, w, endless) } } } // TestLevelNamesFitHUD: bannamnet, resmålet och mätarna ska rymmas på // HUD:ens första rad även med fullt sällskap och byte, på båda språken. func TestLevelNamesFitHUD(t *testing.T) { face := uiFace(t) width := func(s string) int { return int(text.Advance(s, face)) } dir, err := res.Path("data", "levels") if err != nil { t.Fatal(err) } all, err := level.LoadAll(dir) if err != nil { t.Fatal(err) } for _, lang := range []string{"sv", "en"} { m := loadTexts(t, lang) for _, l := range all { name := l.Name.Get(lang) if l.NumFloors() > 1 { name += " " + l.FloorName(0) } chain := level.ChainOf(all, l.ID) if len(chain) > 1 { name += " 1/2" } dest := m["hud.destination"] + ": " + level.FinalOf(all, l).Destination.Get("romaji") if len(chain) > 1 { dest += " " + m["hud.via"] + " " + l.Destination.Get("romaji") } metersX := ScreenW - 4 - max(1, l.Party)*(meterW+meterGap) if w := 4 + width(name) + 12 + width(dest); w > metersX-4 { t.Errorf("%s %s: HUD-raden %q + %q är %d px, mätarna börjar vid %d", lang, l.ID, name, dest, w, metersX) } } } } // TestReportTextWidths skriver (med -v) ut alla texter bredare än skärmen, // som hjälp vid textgranskning. Fäller inget: de flesta radbryts vid visning. func TestReportTextWidths(t *testing.T) { face := uiFace(t) for _, lang := range []string{"sv", "en"} { m := loadTexts(t, lang) var keys []string for k := range m { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { for _, line := range strings.Split(m[k], "\n") { if w := int(text.Advance(line, face)); w > ScreenW-16 { t.Logf("WIDE %s %-28s %4d px %q", lang, k, w, line) } } } } _ = filepath.Base }