8793bd34f2
The statistics screen gains real depth, plus a hint-count bug fix found along the way. - Best move per variant: the screen shows the actual best-move word (drawn as game tiles; a wildcard shows its letter but no value), broken down by game variant, empty variants omitted. New account_best_move table, written at game finish. - Moves & hint share: two new lifetime tiles — the player's play count and the share of plays that used a hint — from summed account_stats counters (moves, hints_used). Honest-AI games are excluded, like the rest of the stats. - Hint-count fix: the in-game hint badge no longer goes stale across games. The global wallet now rides the wire apart from the per-game allowance (wallet_balance on StateView/HintResult/StatsView), so the client reads the live wallet rather than a per-game snapshot; game_players.hints_used now counts every hint (allowance + wallet), its true per-game total. - Account merge: sums the new moves/hints_used counters and merges the per-variant best moves (higher score kept), which it previously dropped. - Admin: the user card shows Moves and Hints used. - UI polish: tab/label wording, game-over text, and e2e selectors hardened against label changes. All wire additions are trailing (backward-compatible). Docs (ARCHITECTURE, FUNCTIONAL +ru, UISN_DESIGN) updated in step.
51 lines
2.0 KiB
Go
51 lines
2.0 KiB
Go
package game
|
||
|
||
import (
|
||
"reflect"
|
||
"testing"
|
||
|
||
"scrabble/backend/internal/account"
|
||
"scrabble/backend/internal/engine"
|
||
)
|
||
|
||
// TestMainWordTilesHorizontalWithBlank checks that a horizontal main word is decoded into
|
||
// tiles along its row, that the per-letter value is looked up from the value table, and that
|
||
// a blank cell (here the middle 'a') is rendered with a zero value regardless of its letter
|
||
// value — the blank flag is taken from the placed-blank set, not from this move's own tiles.
|
||
func TestMainWordTilesHorizontalWithBlank(t *testing.T) {
|
||
rec := engine.MoveRecord{Dir: engine.Horizontal, MainRow: 7, MainCol: 5, Words: []string{"cat"}}
|
||
blanks := map[[2]int]bool{{7, 6}: true}
|
||
values := map[string]int{"c": 3, "a": 1, "t": 1}
|
||
got := mainWordTiles(rec, blanks, values)
|
||
want := []account.BestMoveTile{
|
||
{Letter: "c", Value: 3, Blank: false},
|
||
{Letter: "a", Value: 0, Blank: true},
|
||
{Letter: "t", Value: 1, Blank: false},
|
||
}
|
||
if !reflect.DeepEqual(got, want) {
|
||
t.Errorf("mainWordTiles = %+v, want %+v", got, want)
|
||
}
|
||
}
|
||
|
||
// TestMainWordTilesVerticalCyrillic checks vertical walk and multi-byte (Cyrillic) letters:
|
||
// the word must be split by rune, not byte, and laid down its column.
|
||
func TestMainWordTilesVerticalCyrillic(t *testing.T) {
|
||
rec := engine.MoveRecord{Dir: engine.Vertical, MainRow: 3, MainCol: 8, Words: []string{"съёмка"}}
|
||
values := map[string]int{"с": 1, "ъ": 10, "ё": 4, "м": 2, "к": 2, "а": 1}
|
||
got := mainWordTiles(rec, map[[2]int]bool{}, values)
|
||
want := []account.BestMoveTile{
|
||
{Letter: "с", Value: 1}, {Letter: "ъ", Value: 10}, {Letter: "ё", Value: 4},
|
||
{Letter: "м", Value: 2}, {Letter: "к", Value: 2}, {Letter: "а", Value: 1},
|
||
}
|
||
if !reflect.DeepEqual(got, want) {
|
||
t.Errorf("mainWordTiles = %+v, want %+v", got, want)
|
||
}
|
||
}
|
||
|
||
// TestMainWordTilesNoWord checks the defensive nil for a record carrying no words.
|
||
func TestMainWordTilesNoWord(t *testing.T) {
|
||
if got := mainWordTiles(engine.MoveRecord{}, nil, nil); got != nil {
|
||
t.Errorf("mainWordTiles(no words) = %+v, want nil", got)
|
||
}
|
||
}
|