All checks were successful
release / build-release (push) Successful in 1m41s
- sim.Wallet: IC-kort, kontanter, pappersbiljett, telefon med batteri, spenderat; följer med mellan banor (Game.Wallet) - internal/minigame: gemensamt Game-gränssnitt (Update/Done/Success), all slump från del-seeds: TopUp (hitta チャージ, belopp, mata sedlar), Ticket (taxetavla med resmål på japanska), Crowd (tidsfönster, två försök), Ride (stationerna passerar, kliv av vid resmålet) - sim.Run: spärren kräver biljett på biljettbanor annars användbart kort; telefonen ger ×1,1 fart men 15 % seedad omskanning (frysning 1,5 s), dött batteri gör kortet oanvändbart; automaten startar påfyllning eller biljettköp; tåget kontrollerar biljetten; fulla tåg kräver trängsel; resan avgör vinst/förlust (fel station, åkte förbi, fel tåg); StepMini låter klockan och sällskapet ticka medan rörelsen pausar - level: ticketRequired, rideStops, Train.Crowded; 002 är biljettbana med fullt tåg och fyra stopp; ordboken har stationer (kind) + sju nya namn - game: vyer för minispelen, plånbok i HUD, P i menyn = kort i telefonen, förlustorsaker och spenderat på resultatet, flaggor -phone/-card, skriptkommandon hold/waitstop; inforuta/minikarta döljs under minispel - plan: M7b (Björn) – tågvind, ankomstindikator nära rälsen, in-/utkörningsanimation för tåg och bussar – i spelbeskrivning, roadmap, testplan - docs/README/wiki med tre nya bilder (automat, biljettavla, tågresa) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
263 lines
8.8 KiB
Go
263 lines
8.8 KiB
Go
package lang
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func testDict() *Dictionary {
|
|
d := NewDictionary()
|
|
d.Add("改札", Entry{Kana: "かいさつ", Romaji: "kaisatsu", En: "Ticket gate", Sv: "Spärr", Tier: 1})
|
|
d.Add("番線", Entry{Kana: "ばんせん", Romaji: "bansen", En: "Track no.", Sv: "Spår", Tier: 1})
|
|
d.Add("方面", Entry{Kana: "ほうめん", Romaji: "hōmen", En: "for", Sv: "mot", Tier: 2})
|
|
d.Add("新宿", Entry{Kana: "しんじゅく", Romaji: "Shinjuku", En: "Shinjuku", Sv: "Shinjuku", Tier: 2})
|
|
d.Add("新宿駅", Entry{Kana: "しんじゅくえき", Romaji: "Shinjuku-eki", En: "Shinjuku Station", Sv: "Shinjuku station", Tier: 3})
|
|
d.Add("のりば", Entry{Kana: "のりば", Romaji: "noriba", En: "Platform", Sv: "Perrong", Tier: 1})
|
|
d.Add("トイレ", Entry{Kana: "トイレ", Romaji: "toire", En: "Toilet", Sv: "Toalett", Tier: 1})
|
|
d.Add("チャージ", Entry{Kana: "チャージ", Romaji: "chāji", En: "Top up", Sv: "Fyll på", Tier: 2})
|
|
return d
|
|
}
|
|
|
|
func TestTokenizeLongestMatchAndKinds(t *testing.T) {
|
|
d := testDict()
|
|
segs := d.Tokenize("改札 ← 2番線 新宿駅方面 京成")
|
|
var got []string
|
|
for _, s := range segs {
|
|
got = append(got, map[Kind]string{Plain: "P", Word: "W", Unknown: "U"}[s.Kind]+":"+s.Text)
|
|
}
|
|
want := []string{"W:改札", "P: ← 2", "W:番線", "P: ", "W:新宿駅", "W:方面", "P: ", "U:京成"}
|
|
if strings.Join(got, "|") != strings.Join(want, "|") {
|
|
t.Fatalf("got %v\nwant %v", got, want)
|
|
}
|
|
if u := d.Unknown("改札 京成 のりば 上野"); strings.Join(u, ",") != "京成,上野" {
|
|
t.Fatalf("Unknown = %v", u)
|
|
}
|
|
if w := d.WordsIn("新宿方面 新宿"); strings.Join(w, ",") != "新宿,方面,新宿" {
|
|
t.Fatalf("WordsIn = %v", w)
|
|
}
|
|
}
|
|
|
|
func TestRenderPartialTranslation(t *testing.T) {
|
|
d := testDict()
|
|
k := NewKnowledge()
|
|
k.Learn("改札")
|
|
k.Learn("方面")
|
|
r := Render(d, k, "改札 → 2番線 新宿方面", "sv")
|
|
if r.Complete {
|
|
t.Fatal("ska inte vara komplett med okända ord")
|
|
}
|
|
if got := PlainText(r.Partial); got != "Spärr → 2番線 新宿 mot" {
|
|
t.Fatalf("Partial = %q", got)
|
|
}
|
|
known := map[string]bool{}
|
|
for _, sh := range r.Original {
|
|
known[sh.Text] = sh.Known
|
|
}
|
|
if !known["改札"] || !known["方面"] || known["番線"] || known["新宿"] {
|
|
t.Fatalf("markering fel: %+v", r.Original)
|
|
}
|
|
// Allt känt (nil) -> komplett.
|
|
full := Render(d, nil, "改札 → 2番線 新宿方面", "sv")
|
|
if !full.Complete || PlainText(full.Partial) != "Spärr → 2 Spår Shinjuku mot" {
|
|
t.Fatalf("full = %q complete=%v", PlainText(full.Partial), full.Complete)
|
|
}
|
|
// Engelska som UI-språk.
|
|
if got := PlainText(Render(d, nil, "改札", "en").Partial); got != "Ticket gate" {
|
|
t.Fatalf("en = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestKanaReadingWithoutMeaning(t *testing.T) {
|
|
d := testDict()
|
|
k := NewKnowledge()
|
|
r := Render(d, k, "のりば", "sv")
|
|
if PlainText(r.Partial) != "のりば" {
|
|
t.Fatalf("utan kana ska kana-ordet stå kvar: %q", PlainText(r.Partial))
|
|
}
|
|
k.Kana = true
|
|
r = Render(d, k, "のりば 改札", "sv")
|
|
if PlainText(r.Partial) != "noriba 改札" || !r.Partial[0].Reading || r.Complete {
|
|
t.Fatalf("med kana: %q reading=%v complete=%v", PlainText(r.Partial), r.Partial[0].Reading, r.Complete)
|
|
}
|
|
}
|
|
|
|
func TestLearnUnlocksKanaAndLevels(t *testing.T) {
|
|
k := NewKnowledge()
|
|
if k.Level() != 0 || k.Kana {
|
|
t.Fatal("startar på noll")
|
|
}
|
|
if !k.Learn("改札") || k.Learn("改札") {
|
|
t.Fatal("Learn ska rapportera nytt bara första gången")
|
|
}
|
|
k.Learn("のりば")
|
|
k.Learn("トイレ")
|
|
if k.Kana {
|
|
t.Fatal("två kana-ord räcker inte")
|
|
}
|
|
k.Learn("チャージ")
|
|
if !k.Kana {
|
|
t.Fatal("tre kana-ord ska låsa upp kana")
|
|
}
|
|
if k.Count() != 4 || k.Level() != 1 || !k.ShowsDestinationJP() || k.ShowsReadings() {
|
|
t.Fatalf("count=%d level=%d", k.Count(), k.Level())
|
|
}
|
|
for i := 0; i < 40; i++ {
|
|
k.Learn(strings.Repeat("語", i+1))
|
|
}
|
|
if k.Level() != MaxLevel || !k.ShowsReadings() {
|
|
t.Fatalf("level=%d", k.Level())
|
|
}
|
|
}
|
|
|
|
func TestReadingsShownFromLevel2(t *testing.T) {
|
|
d := testDict()
|
|
k := NewKnowledge()
|
|
for _, w := range []string{"改札", "番線", "方面", "新宿", "新宿駅", "のりば", "トイレ", "チャージ"} {
|
|
k.Learn(w)
|
|
}
|
|
r := Render(d, k, "改札", "sv")
|
|
if k.Level() < 2 || r.Original[0].Romaji != "kaisatsu" {
|
|
t.Fatalf("level=%d romaji=%q", k.Level(), r.Original[0].Romaji)
|
|
}
|
|
}
|
|
|
|
func TestFirstUnknownWord(t *testing.T) {
|
|
d := testDict()
|
|
k := NewKnowledge()
|
|
k.Learn("改札")
|
|
if w := FirstUnknownWord(d, k, "改札 2番線 新宿方面"); w != "番線" {
|
|
t.Fatalf("got %q", w)
|
|
}
|
|
if w := FirstUnknownWord(d, k, "改札 改札"); w != "" {
|
|
t.Fatalf("allt känt ska ge tom sträng, fick %q", w)
|
|
}
|
|
}
|
|
|
|
func TestHelpSeconds(t *testing.T) {
|
|
prev := math.Inf(1)
|
|
for lvl := 0; lvl <= MaxLevel; lvl++ {
|
|
s := HelpSeconds(lvl)
|
|
if s > prev || s < HelpMinSeconds {
|
|
t.Fatalf("nivå %d: %.1f s (förra %.1f)", lvl, s, prev)
|
|
}
|
|
prev = s
|
|
}
|
|
if HelpSeconds(0) != HelpBaseSeconds || HelpSeconds(100) != HelpMinSeconds {
|
|
t.Fatal("gränserna stämmer inte")
|
|
}
|
|
}
|
|
|
|
func TestEnglishShareAndDeterminism(t *testing.T) {
|
|
if EnglishShare(1) != 1 || EnglishShare(0) != 1 || EnglishShare(5) != 0 || EnglishShare(9) != 0 {
|
|
t.Fatal("ytterligheterna")
|
|
}
|
|
for d := 1; d < 5; d++ {
|
|
if EnglishShare(d) <= EnglishShare(d+1) {
|
|
t.Fatalf("andelen ska minska med svårigheten: %d", d)
|
|
}
|
|
}
|
|
for i := 0; i < 50; i++ {
|
|
if !ShowEnglish("x", i, 0, 1) || ShowEnglish("x", i, 0, 5) {
|
|
t.Fatal("svårighet 1 = alltid engelska, 5 = aldrig")
|
|
}
|
|
}
|
|
if ShowEnglish("lvl", 3, 4, 2) != ShowEnglish("lvl", 3, 4, 2) {
|
|
t.Fatal("ska vara deterministiskt")
|
|
}
|
|
// Andelen ska ungefär stämma över många skyltar.
|
|
n := 0
|
|
for x := 0; x < 100; x++ {
|
|
for y := 0; y < 10; y++ {
|
|
if ShowEnglish("lvl", x, y, 2) {
|
|
n++
|
|
}
|
|
}
|
|
}
|
|
if n < 500 || n > 700 {
|
|
t.Fatalf("svårighet 2: %d/1000 visar engelska, vill ha ~600", n)
|
|
}
|
|
}
|
|
|
|
func TestParseDictionaryValidation(t *testing.T) {
|
|
good := `{"_comment": "x", "改札": {"kana": "かいさつ", "romaji": "kaisatsu", "en": "Gate", "sv": "Spärr", "tier": 1}}`
|
|
d, err := ParseDictionary([]byte(good))
|
|
if err != nil || d.Len() != 1 || d.Lookup("改札") == nil || d.Lookup("改札").Word != "改札" {
|
|
t.Fatalf("d=%v err=%v", d, err)
|
|
}
|
|
for name, src := range map[string]string{
|
|
"saknar sv": `{"改札": {"kana": "か", "romaji": "k", "en": "G", "tier": 1}}`,
|
|
"tier 0": `{"改札": {"kana": "か", "romaji": "k", "en": "G", "sv": "S", "tier": 0}}`,
|
|
"latinsk nyckel": `{"gate": {"kana": "か", "romaji": "k", "en": "G", "sv": "S", "tier": 1}}`,
|
|
"trasig": `{`,
|
|
} {
|
|
if _, err := ParseDictionary([]byte(src)); err == nil {
|
|
t.Errorf("%s: förväntade fel", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCharClasses(t *testing.T) {
|
|
if !IsKana('の') || !IsKana('ト') || !IsKana('ー') || IsKana('改') || IsKana('a') {
|
|
t.Fatal("IsKana")
|
|
}
|
|
if !IsKanji('改') || IsKanji('の') || !IsAllKana("のりば") || IsAllKana("新宿") || IsAllKana("") {
|
|
t.Fatal("IsKanji/IsAllKana")
|
|
}
|
|
if !HasKana("成田エクスプレス") || HasKana("新宿") || !IsJapanese("2番線") || IsJapanese("Track 2") {
|
|
t.Fatal("HasKana/IsJapanese")
|
|
}
|
|
}
|
|
|
|
func TestProgressLearnsAfterEnoughReads(t *testing.T) {
|
|
k := NewKnowledge()
|
|
for i := 1; i < ProgressToLearn; i++ {
|
|
if k.AddProgress("改札") || k.ProgressOf("改札") != i || k.Knows("改札") {
|
|
t.Fatalf("efter %d framsteg: progress=%d known=%v", i, k.ProgressOf("改札"), k.Knows("改札"))
|
|
}
|
|
}
|
|
if !k.AddProgress("改札") || !k.Knows("改札") || k.ProgressOf("改札") != 0 {
|
|
t.Fatal("tredje framsteget ska lära ordet och nollställa framstegen")
|
|
}
|
|
if k.AddProgress("改札") {
|
|
t.Fatal("kända ord får inga framsteg")
|
|
}
|
|
}
|
|
|
|
func TestBuyWordsWithPoints(t *testing.T) {
|
|
d := testDict()
|
|
k := NewKnowledge()
|
|
e1, e3 := d.Lookup("改札"), d.Lookup("新宿駅") // tier 1 resp. 3
|
|
if WordCost(e1) != 1 || WordCost(e3) != 3 {
|
|
t.Fatal("kostnad = tier")
|
|
}
|
|
if k.CanBuy(e1) || k.Buy(e1) {
|
|
t.Fatal("utan poäng ska inget gå att köpa")
|
|
}
|
|
if got := k.Award(3); got != 3*PointsPerStar || k.Points != got {
|
|
t.Fatalf("Award = %d, points=%d", got, k.Points)
|
|
}
|
|
k.Progress["新宿駅"] = 2
|
|
if !k.Buy(e3) || !k.Knows("新宿駅") || k.Points != 0 || k.ProgressOf("新宿駅") != 0 {
|
|
t.Fatalf("köp misslyckades: points=%d known=%v", k.Points, k.Knows("新宿駅"))
|
|
}
|
|
if k.CanBuy(e3) {
|
|
t.Fatal("redan känt ord ska inte gå att köpa")
|
|
}
|
|
if k.Award(-2) != 0 || k.Points != 0 {
|
|
t.Fatal("negativa stjärnor ger inget")
|
|
}
|
|
}
|
|
|
|
func TestOfKind(t *testing.T) {
|
|
d := testDict()
|
|
d.Add("池袋", Entry{Kana: "いけぶくろ", Romaji: "Ikebukuro", En: "Ikebukuro", Sv: "Ikebukuro", Tier: 3, Kind: "station"})
|
|
d.Add("品川", Entry{Kana: "しながわ", Romaji: "Shinagawa", En: "Shinagawa", Sv: "Shinagawa", Tier: 3, Kind: "station"})
|
|
if got := strings.Join(d.OfKind("station"), ","); got != "品川,池袋" && got != "池袋,品川" {
|
|
t.Fatalf("OfKind = %q", got)
|
|
}
|
|
if len(d.OfKind("x")) != 0 {
|
|
t.Fatal("okänd kategori är tom")
|
|
}
|
|
}
|