feat(stats): show the best move word per game variant
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m11s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m11s
Replace the single "best move" number on the statistics screen with a
full-width per-variant breakdown: the highest-scoring play in each variant
the player has played, drawn as game tiles (a wildcard shows its letter but
no value), with the words and scores right-aligned to shared edges.
- backend: new account_best_move table (PK account_id+variant) keeping the
main word as JSON tiles {letter,value,blank}; captured at game finish in
buildStats (blank flags taken from every placed blank — equivalent to the
final board), upserted in the finish transaction and replaced only by a
strictly higher-scoring play. Guest/honest-AI games still record nothing.
GetStats + statsDTO expose best_moves.
- wire: StatsView gains best_moves:[BestMoveView{variant,score,word:[BestMoveTile]}]
(trailing, backward-compatible); gateway encodeStats + UI codec updated.
- ui: new WordTiles component (board's tile look, fixed px size); Stats.svelte
drops the maxWord card and adds the full-width best-move card (catalogue
order, empty variants omitted).
- docs: ARCHITECTURE §9 + schema, FUNCTIONAL (+ru), UI_DESIGN.
Tests: mainWordTiles unit + buildStats end-to-end (inttest) + gateway and UI
codec round-trips (incl. a blank tile) + e2e.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
// A best-move word drawn as a row of game tiles, mirroring the board's placed-tile
|
||||
// look (letter top-left, point value bottom-right) at a small fixed size. A blank tile
|
||||
// shows its letter but no value, exactly as on the board. Letters are upper-cased for
|
||||
// display. The tile values ride on each tile, so this renders without the variant's
|
||||
// alphabet table (which the statistics screen has not cached).
|
||||
import type { BestMoveTile } from '../lib/model';
|
||||
|
||||
let { word }: { word: BestMoveTile[] } = $props();
|
||||
|
||||
const label = $derived(word.map((t) => t.letter).join('').toUpperCase());
|
||||
</script>
|
||||
|
||||
<span class="word" aria-label={label}>
|
||||
{#each word as tile, i (i)}
|
||||
<span class="tile" class:blank={tile.blank} aria-hidden="true">
|
||||
<span class="letter">{tile.letter.toUpperCase()}</span>
|
||||
{#if !tile.blank}<span class="val">{tile.value}</span>{/if}
|
||||
</span>
|
||||
{/each}
|
||||
</span>
|
||||
|
||||
<style>
|
||||
.word {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.tile {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: var(--tile-bg);
|
||||
color: var(--tile-text);
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 -2px 0 var(--tile-edge);
|
||||
}
|
||||
.letter {
|
||||
position: absolute;
|
||||
top: 6%;
|
||||
left: 11%;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
.val {
|
||||
position: absolute;
|
||||
right: 8%;
|
||||
bottom: 3%;
|
||||
font-size: 7px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user