Files
scrabble-game/backend/internal/game/stats_test.go
T
developer 8793bd34f2
CI / changes (push) Successful in 2s
CI / unit (push) Successful in 9s
CI / integration (push) Successful in 17s
CI / ui (push) Successful in 52s
CI / gate (push) Successful in 0s
CI / deploy (push) Successful in 1m8s
feat(stats): best-move word, moves & hint-share, and a hint-count fix (#81)
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.
2026-06-17 22:17:27 +00:00

51 lines
2.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 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)
}
}