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.
This commit was merged in pull request #81.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Code generated by go-jet DO NOT EDIT.
|
||||
//
|
||||
// WARNING: Changes to this file may cause incorrect behavior
|
||||
// and will be lost if the code is regenerated
|
||||
//
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AccountBestMove struct {
|
||||
AccountID uuid.UUID `sql:"primary_key"`
|
||||
Variant string `sql:"primary_key"`
|
||||
Score int32
|
||||
Tiles string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
@@ -20,4 +20,6 @@ type AccountStats struct {
|
||||
MaxGamePoints int32
|
||||
MaxWordPoints int32
|
||||
UpdatedAt time.Time
|
||||
Moves int32
|
||||
HintsUsed int32
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// Code generated by go-jet DO NOT EDIT.
|
||||
//
|
||||
// WARNING: Changes to this file may cause incorrect behavior
|
||||
// and will be lost if the code is regenerated
|
||||
//
|
||||
|
||||
package table
|
||||
|
||||
import (
|
||||
"github.com/go-jet/jet/v2/postgres"
|
||||
)
|
||||
|
||||
var AccountBestMove = newAccountBestMoveTable("backend", "account_best_move", "")
|
||||
|
||||
type accountBestMoveTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
AccountID postgres.ColumnString
|
||||
Variant postgres.ColumnString
|
||||
Score postgres.ColumnInteger
|
||||
Tiles postgres.ColumnString
|
||||
UpdatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
DefaultColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type AccountBestMoveTable struct {
|
||||
accountBestMoveTable
|
||||
|
||||
EXCLUDED accountBestMoveTable
|
||||
}
|
||||
|
||||
// AS creates new AccountBestMoveTable with assigned alias
|
||||
func (a AccountBestMoveTable) AS(alias string) *AccountBestMoveTable {
|
||||
return newAccountBestMoveTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new AccountBestMoveTable with assigned schema name
|
||||
func (a AccountBestMoveTable) FromSchema(schemaName string) *AccountBestMoveTable {
|
||||
return newAccountBestMoveTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new AccountBestMoveTable with assigned table prefix
|
||||
func (a AccountBestMoveTable) WithPrefix(prefix string) *AccountBestMoveTable {
|
||||
return newAccountBestMoveTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new AccountBestMoveTable with assigned table suffix
|
||||
func (a AccountBestMoveTable) WithSuffix(suffix string) *AccountBestMoveTable {
|
||||
return newAccountBestMoveTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newAccountBestMoveTable(schemaName, tableName, alias string) *AccountBestMoveTable {
|
||||
return &AccountBestMoveTable{
|
||||
accountBestMoveTable: newAccountBestMoveTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newAccountBestMoveTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newAccountBestMoveTableImpl(schemaName, tableName, alias string) accountBestMoveTable {
|
||||
var (
|
||||
AccountIDColumn = postgres.StringColumn("account_id")
|
||||
VariantColumn = postgres.StringColumn("variant")
|
||||
ScoreColumn = postgres.IntegerColumn("score")
|
||||
TilesColumn = postgres.StringColumn("tiles")
|
||||
UpdatedAtColumn = postgres.TimestampzColumn("updated_at")
|
||||
allColumns = postgres.ColumnList{AccountIDColumn, VariantColumn, ScoreColumn, TilesColumn, UpdatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{ScoreColumn, TilesColumn, UpdatedAtColumn}
|
||||
defaultColumns = postgres.ColumnList{UpdatedAtColumn}
|
||||
)
|
||||
|
||||
return accountBestMoveTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
AccountID: AccountIDColumn,
|
||||
Variant: VariantColumn,
|
||||
Score: ScoreColumn,
|
||||
Tiles: TilesColumn,
|
||||
UpdatedAt: UpdatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
DefaultColumns: defaultColumns,
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,8 @@ type accountStatsTable struct {
|
||||
MaxGamePoints postgres.ColumnInteger
|
||||
MaxWordPoints postgres.ColumnInteger
|
||||
UpdatedAt postgres.ColumnTimestampz
|
||||
Moves postgres.ColumnInteger
|
||||
HintsUsed postgres.ColumnInteger
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
@@ -72,9 +74,11 @@ func newAccountStatsTableImpl(schemaName, tableName, alias string) accountStatsT
|
||||
MaxGamePointsColumn = postgres.IntegerColumn("max_game_points")
|
||||
MaxWordPointsColumn = postgres.IntegerColumn("max_word_points")
|
||||
UpdatedAtColumn = postgres.TimestampzColumn("updated_at")
|
||||
allColumns = postgres.ColumnList{AccountIDColumn, WinsColumn, LossesColumn, DrawsColumn, MaxGamePointsColumn, MaxWordPointsColumn, UpdatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{WinsColumn, LossesColumn, DrawsColumn, MaxGamePointsColumn, MaxWordPointsColumn, UpdatedAtColumn}
|
||||
defaultColumns = postgres.ColumnList{WinsColumn, LossesColumn, DrawsColumn, MaxGamePointsColumn, MaxWordPointsColumn, UpdatedAtColumn}
|
||||
MovesColumn = postgres.IntegerColumn("moves")
|
||||
HintsUsedColumn = postgres.IntegerColumn("hints_used")
|
||||
allColumns = postgres.ColumnList{AccountIDColumn, WinsColumn, LossesColumn, DrawsColumn, MaxGamePointsColumn, MaxWordPointsColumn, UpdatedAtColumn, MovesColumn, HintsUsedColumn}
|
||||
mutableColumns = postgres.ColumnList{WinsColumn, LossesColumn, DrawsColumn, MaxGamePointsColumn, MaxWordPointsColumn, UpdatedAtColumn, MovesColumn, HintsUsedColumn}
|
||||
defaultColumns = postgres.ColumnList{WinsColumn, LossesColumn, DrawsColumn, MaxGamePointsColumn, MaxWordPointsColumn, UpdatedAtColumn, MovesColumn, HintsUsedColumn}
|
||||
)
|
||||
|
||||
return accountStatsTable{
|
||||
@@ -88,6 +92,8 @@ func newAccountStatsTableImpl(schemaName, tableName, alias string) accountStatsT
|
||||
MaxGamePoints: MaxGamePointsColumn,
|
||||
MaxWordPoints: MaxWordPointsColumn,
|
||||
UpdatedAt: UpdatedAtColumn,
|
||||
Moves: MovesColumn,
|
||||
HintsUsed: HintsUsedColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
|
||||
Reference in New Issue
Block a user