package game import ( "scrabble/backend/internal/engine" "scrabble/backend/internal/notify" ) // The mappers below project the game domain into the wire-agnostic notify.* input // structs the enriched live events carry. They keep the wire schema out of the // game package: notify owns the FlatBuffers encoding, this file only resolves the // values (seat display names, last-activity sort key) into its input shapes. // gameSummary projects a game.Game into the notify.GameSummary embedded in enriched // events. names is the seat-indexed display-name slice from seatNames; LastActivityUnix // mirrors the gateway view (the current turn's start while active, the finish time once // finished). func gameSummary(g Game, names []string) notify.GameSummary { seats := make([]notify.SeatStanding, 0, len(g.Seats)) for _, s := range g.Seats { name := "" if s.Seat >= 0 && s.Seat < len(names) { name = names[s.Seat] } seats = append(seats, notify.SeatStanding{ Seat: s.Seat, AccountID: s.AccountID.String(), DisplayName: name, Score: s.Score, HintsUsed: s.HintsUsed, IsWinner: s.IsWinner, }) } last := g.TurnStartedAt if g.FinishedAt != nil { last = *g.FinishedAt } return notify.GameSummary{ ID: g.ID.String(), Variant: g.Variant.String(), DictVersion: g.DictVersion, Status: g.Status, Players: g.Players, ToMove: g.ToMove, TurnTimeoutSecs: int(g.TurnTimeout.Seconds()), MoveCount: g.MoveCount, EndReason: g.EndReason, Seats: seats, LastActivityUnix: last.Unix(), } } // playerState projects a StateView into the notify.PlayerState carried by the // match_found / game_started events. The rack is re-encoded to wire alphabet indices; // the variant alphabet display table is embedded when includeAlphabet is set (an // initial view whose recipient may not have cached the variant yet). func playerState(v StateView, names []string, includeAlphabet bool) (notify.PlayerState, error) { rack, err := engine.EncodeRack(v.Game.Variant, v.Rack) if err != nil { return notify.PlayerState{}, err } ps := notify.PlayerState{ Game: gameSummary(v.Game, names), Seat: v.Seat, Rack: rack, BagLen: v.BagLen, HintsRemaining: v.HintsRemaining, } if includeAlphabet { tab, err := engine.AlphabetTable(v.Game.Variant) if err != nil { return notify.PlayerState{}, err } ps.Alphabet = make([]notify.AlphabetLetter, len(tab)) for i, e := range tab { ps.Alphabet[i] = notify.AlphabetLetter{Index: int(e.Index), Letter: e.Letter, Value: e.Value} } } return ps, nil }