Files
scrabble-game/backend/internal/engine/alphabet_test.go
T
Ilia Denisov 26aa154547
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 37s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
R1: schema & naming reset — squash migrations, rename variants
Squash the 12 goose migrations into one 00001_baseline.sql (there is no prod
data; verified schema-identical to the chain via a pg_dump diff + the green
integration suite) and rename the game-variant labels
english/russian_scrabble/erudit -> scrabble_en/scrabble_ru/erudit_ru across the
backend, the FlatBuffers wire values and the UI.

dawg filenames and the Go enum identifiers are unchanged; the i18n display keys
are kept. Adds PRERELEASE.md (the R1-R7 pre-release tracker), linked from
CLAUDE.md. Contour DB wipe and the scrabble-dictionary tidy are follow-ups.
2026-06-09 12:09:50 +02:00

111 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package engine
import (
"errors"
"slices"
"testing"
)
// TestAlphabetTableEnglish pins the English table against the solver ruleset: 26 letters,
// contiguous indices, the concrete lower-case characters the solver emits and the standard
// tile values. This is the real parity check the UI no longer carries (Stage 13).
func TestAlphabetTableEnglish(t *testing.T) {
tab, err := AlphabetTable(VariantEnglish)
if err != nil {
t.Fatalf("AlphabetTable(scrabble_en): %v", err)
}
if len(tab) != 26 {
t.Fatalf("size = %d, want 26", len(tab))
}
for i, e := range tab {
if int(e.Index) != i {
t.Errorf("entry %d has Index %d, want %d (index must equal position)", i, e.Index, i)
}
}
// a=index0/value1, q=index16/value10, z=index25/value10.
if tab[0].Letter != "a" || tab[0].Value != 1 {
t.Errorf("entry 0 = %q/%d, want a/1", tab[0].Letter, tab[0].Value)
}
if tab[16].Letter != "q" || tab[16].Value != 10 {
t.Errorf("entry 16 = %q/%d, want q/10", tab[16].Letter, tab[16].Value)
}
if tab[25].Letter != "z" || tab[25].Value != 10 {
t.Errorf("entry 25 = %q/%d, want z/10", tab[25].Letter, tab[25].Value)
}
}
// TestAlphabetTableRussianVariants pins both Russian variants: they share the 33-letter
// alphabet but differ in tile values — most visibly ё (index 6), worth 3 in Russian
// Scrabble and 0 in Эрудит.
func TestAlphabetTableRussianVariants(t *testing.T) {
ru, err := AlphabetTable(VariantRussianScrabble)
if err != nil {
t.Fatalf("AlphabetTable(scrabble_ru): %v", err)
}
er, err := AlphabetTable(VariantErudit)
if err != nil {
t.Fatalf("AlphabetTable(erudit_ru): %v", err)
}
if len(ru) != 33 || len(er) != 33 {
t.Fatalf("sizes = %d/%d, want 33/33", len(ru), len(er))
}
if ru[0].Letter != "а" || ru[0].Value != 1 {
t.Errorf("scrabble_ru entry 0 = %q/%d, want а/1", ru[0].Letter, ru[0].Value)
}
if ru[6].Letter != "ё" || ru[6].Value != 3 {
t.Errorf("scrabble_ru ё (entry 6) = %q/%d, want ё/3", ru[6].Letter, ru[6].Value)
}
if er[6].Letter != "ё" || er[6].Value != 0 {
t.Errorf("erudit_ru ё (entry 6) = %q/%d, want ё/0", er[6].Letter, er[6].Value)
}
if ru[32].Letter != "я" || er[32].Letter != "я" {
t.Errorf("last letter = %q/%q, want я/я", ru[32].Letter, er[32].Letter)
}
}
// TestAlphabetTableUnknownVariant rejects a variant outside the catalogue.
func TestAlphabetTableUnknownVariant(t *testing.T) {
if _, err := AlphabetTable(Variant(99)); !errors.Is(err, ErrUnknownVariant) {
t.Fatalf("got %v, want ErrUnknownVariant", err)
}
}
// TestRackCodecRoundTrip pins the rack/exchange index codec the edge uses: EncodeRack maps
// concrete letters (with "?" for a blank) to indices (BlankIndex for the blank) and
// DecodeTiles inverts it. EncodeRack is case-insensitive so it accepts the lower-case
// Hand form and an upper-case letter alike.
func TestRackCodecRoundTrip(t *testing.T) {
letters := []string{"c", "a", "t", "?"}
idx, err := EncodeRack(VariantEnglish, letters)
if err != nil {
t.Fatalf("EncodeRack: %v", err)
}
if want := []int{2, 0, 19, BlankIndex}; !slices.Equal(idx, want) {
t.Fatalf("EncodeRack = %v, want %v", idx, want)
}
back, err := DecodeTiles(VariantEnglish, idx)
if err != nil {
t.Fatalf("DecodeTiles: %v", err)
}
if !slices.Equal(back, letters) {
t.Fatalf("DecodeTiles = %v, want %v", back, letters)
}
if up, err := EncodeRack(VariantEnglish, []string{"C"}); err != nil || !slices.Equal(up, []int{2}) {
t.Errorf("EncodeRack upper-case = %v,%v; want [2],nil", up, err)
}
}
// TestDecodeWordAndBounds covers the word-check decode and the out-of-range guard.
func TestDecodeWordAndBounds(t *testing.T) {
w, err := DecodeWord(VariantEnglish, []int{2, 0, 19})
if err != nil || w != "cat" {
t.Fatalf("DecodeWord = %q,%v; want cat,nil", w, err)
}
if _, err := LetterForIndex(VariantEnglish, 26); !errors.Is(err, ErrIllegalPlay) {
t.Errorf("out-of-range index: got %v, want ErrIllegalPlay", err)
}
if _, err := DecodeWord(VariantEnglish, []int{BlankIndex}); !errors.Is(err, ErrIllegalPlay) {
t.Errorf("blank in word: got %v, want ErrIllegalPlay", err)
}
}