package snake import ( "context" "database/sql" "github.com/ryanhamamura/c4/db/repository" ) // Persistence methods on SnakeStore (used during Get to hydrate from DB). func (ss *SnakeStore) saveSnakeGame(sg *SnakeGame) error { ctx := context.Background() boardJSON := "{}" if sg.State != nil { boardJSON = sg.State.ToJSON() } var gridWidth, gridHeight sql.NullInt64 if sg.State != nil { gridWidth = sql.NullInt64{Int64: int64(sg.State.Width), Valid: true} gridHeight = sql.NullInt64{Int64: int64(sg.State.Height), Valid: true} } _, err := ss.queries.GetSnakeGame(ctx, sg.ID) if err == sql.ErrNoRows { _, err = ss.queries.CreateSnakeGame(ctx, repository.CreateSnakeGameParams{ ID: sg.ID, Board: boardJSON, Status: int64(sg.Status), GridWidth: gridWidth, GridHeight: gridHeight, GameMode: int64(sg.Mode), SnakeSpeed: int64(sg.Speed), }) return err } if err != nil { return err } return ss.queries.UpdateSnakeGame(ctx, updateSnakeGameParams(sg, boardJSON)) } func (ss *SnakeStore) loadSnakeGame(id string) (*SnakeGame, error) { row, err := ss.queries.GetSnakeGame(context.Background(), id) if err != nil { return nil, err } return snakeGameFromRow(row) } func (ss *SnakeStore) loadSnakePlayers(id string) ([]*Player, error) { rows, err := ss.queries.GetSnakePlayers(context.Background(), id) if err != nil { return nil, err } return snakePlayersFromRows(rows), nil } // Persistence methods on SnakeGameInstance (used during gameplay mutations). func (si *SnakeGameInstance) saveSnakeGame(sg *SnakeGame) error { ctx := context.Background() boardJSON := "{}" if sg.State != nil { boardJSON = sg.State.ToJSON() } var gridWidth, gridHeight sql.NullInt64 if sg.State != nil { gridWidth = sql.NullInt64{Int64: int64(sg.State.Width), Valid: true} gridHeight = sql.NullInt64{Int64: int64(sg.State.Height), Valid: true} } _, err := si.queries.GetSnakeGame(ctx, sg.ID) if err == sql.ErrNoRows { _, err = si.queries.CreateSnakeGame(ctx, repository.CreateSnakeGameParams{ ID: sg.ID, Board: boardJSON, Status: int64(sg.Status), GridWidth: gridWidth, GridHeight: gridHeight, GameMode: int64(sg.Mode), SnakeSpeed: int64(sg.Speed), }) return err } if err != nil { return err } return si.queries.UpdateSnakeGame(ctx, updateSnakeGameParams(sg, boardJSON)) } func (si *SnakeGameInstance) saveSnakePlayer(gameID string, player *Player) error { var userID, guestPlayerID sql.NullString if player.UserID != nil { userID = sql.NullString{String: *player.UserID, Valid: true} } else { guestPlayerID = sql.NullString{String: string(player.ID), Valid: true} } return si.queries.CreateSnakePlayer(context.Background(), repository.CreateSnakePlayerParams{ GameID: gameID, UserID: userID, GuestPlayerID: guestPlayerID, Nickname: player.Nickname, Color: int64(player.Slot + 1), Slot: int64(player.Slot), }) } // Shared helpers for domain ↔ DB mapping. func updateSnakeGameParams(sg *SnakeGame, boardJSON string) repository.UpdateSnakeGameParams { var winnerUserID sql.NullString if sg.Winner != nil && sg.Winner.UserID != nil { winnerUserID = sql.NullString{String: *sg.Winner.UserID, Valid: true} } var rematchGameID sql.NullString if sg.RematchGameID != nil { rematchGameID = sql.NullString{String: *sg.RematchGameID, Valid: true} } return repository.UpdateSnakeGameParams{ Board: boardJSON, Status: int64(sg.Status), WinnerUserID: winnerUserID, RematchGameID: rematchGameID, Score: int64(sg.Score), ID: sg.ID, } } func snakeGameFromRow(row repository.Game) (*SnakeGame, error) { state, err := GameStateFromJSON(row.Board) if err != nil { state = &GameState{} } if row.GridWidth.Valid { state.Width = int(row.GridWidth.Int64) } if row.GridHeight.Valid { state.Height = int(row.GridHeight.Int64) } sg := &SnakeGame{ ID: row.ID, State: state, Players: make([]*Player, 8), Status: Status(row.Status), Mode: GameMode(row.GameMode), Score: int(row.Score), Speed: int(row.SnakeSpeed), } if row.RematchGameID.Valid { sg.RematchGameID = &row.RematchGameID.String } return sg, nil } func snakePlayersFromRows(rows []repository.GamePlayer) []*Player { players := make([]*Player, 0, len(rows)) for _, row := range rows { player := &Player{ Nickname: row.Nickname, Slot: int(row.Slot), } if row.UserID.Valid { player.UserID = &row.UserID.String player.ID = PlayerID(row.UserID.String) } else if row.GuestPlayerID.Valid { player.ID = PlayerID(row.GuestPlayerID.String) } players = append(players, player) } return players }