package edge import ( fb "scrabble/pkg/fbs/scrabblefb" ) // Game is the decoded non-private game summary the driver needs to decide a turn. type Game struct { ID string Variant string DictVer string Status string Players int ToMove int MoveCount int Seats []string // account ids in seat order } // Active reports whether the game is still in progress. func (g Game) Active() bool { return g.Status == "active" } // SeatOf returns the seat index of accountID, or -1 if it is not seated. func (g Game) SeatOf(accountID string) int { for i, id := range g.Seats { if id == accountID { return i } } return -1 } // State is a player's private view: the shared game plus their seat, rack (alphabet // indices; 255 a blank) and bag size. type State struct { Game Game Seat int Rack []byte BagLen int } // Tile is one placed tile from a decoded history record (concrete letter, blank flag). type Tile struct { Row, Col int Letter string Blank bool } // Move is one decoded history record (a committed play carries Tiles; pass/exchange // carry only Action). type Move struct { Action string Dir string Tiles []Tile } // Invitation is the decoded subset the assembler matches on. type Invitation struct { ID string InviterID string Status string GameID string } func decodeGameView(gv *fb.GameView) Game { g := Game{ ID: string(gv.Id()), Variant: string(gv.Variant()), DictVer: string(gv.DictVersion()), Status: string(gv.Status()), Players: int(gv.Players()), ToMove: int(gv.ToMove()), MoveCount: int(gv.MoveCount()), } n := gv.SeatsLength() g.Seats = make([]string, n) var sv fb.SeatView for j := 0; j < n; j++ { if gv.Seats(&sv, j) { g.Seats[sv.Seat()] = string(sv.AccountId()) } } return g } // decodeState reads a StateView payload. func decodeState(payload []byte) State { sv := fb.GetRootAsStateView(payload, 0) var gv fb.GameView st := State{ Seat: int(sv.Seat()), BagLen: int(sv.BagLen()), Rack: append([]byte(nil), sv.RackBytes()...), } if g := sv.Game(&gv); g != nil { st.Game = decodeGameView(g) } return st } // decodeHistory reads a History payload into the decoded move journal. func decodeHistory(payload []byte) []Move { h := fb.GetRootAsHistory(payload, 0) n := h.MovesLength() moves := make([]Move, 0, n) var mr fb.MoveRecord for j := 0; j < n; j++ { if !h.Moves(&mr, j) { continue } m := Move{Action: string(mr.Action()), Dir: string(mr.Dir())} tn := mr.TilesLength() m.Tiles = make([]Tile, 0, tn) var tr fb.TileRecord for k := 0; k < tn; k++ { if mr.Tiles(&tr, k) { m.Tiles = append(m.Tiles, Tile{ Row: int(tr.Row()), Col: int(tr.Col()), Letter: string(tr.Letter()), Blank: tr.Blank(), }) } } moves = append(moves, m) } return moves } // decodeMoveResultGame reads a MoveResult payload and returns its post-move game. func decodeMoveResultGame(payload []byte) Game { mr := fb.GetRootAsMoveResult(payload, 0) var gv fb.GameView if g := mr.Game(&gv); g != nil { return decodeGameView(g) } return Game{} } // decodeGameList reads a GameList payload. func decodeGameList(payload []byte) []Game { gl := fb.GetRootAsGameList(payload, 0) n := gl.GamesLength() games := make([]Game, 0, n) var gv fb.GameView for j := 0; j < n; j++ { if gl.Games(&gv, j) { games = append(games, decodeGameView(&gv)) } } return games } // decodeInvitationList reads an InvitationList payload into the matched subset. func decodeInvitationList(payload []byte) []Invitation { il := fb.GetRootAsInvitationList(payload, 0) n := il.InvitationsLength() out := make([]Invitation, 0, n) var inv fb.Invitation var ref fb.AccountRef for j := 0; j < n; j++ { if !il.Invitations(&inv, j) { continue } iv := Invitation{ ID: string(inv.Id()), Status: string(inv.Status()), GameID: string(inv.GameId()), } if r := inv.Inviter(&ref); r != nil { iv.InviterID = string(r.AccountId()) } out = append(out, iv) } return out } // decodeMatch reads a MatchResult payload. func decodeMatch(payload []byte) (matched bool, game Game) { mr := fb.GetRootAsMatchResult(payload, 0) if !mr.Matched() { return false, Game{} } var gv fb.GameView if g := mr.Game(&gv); g != nil { return true, decodeGameView(g) } return true, Game{} }