package db import ( "context" "database/sql" "github.com/ryanhamamura/c4/db/gen" "github.com/ryanhamamura/c4/game" "github.com/ryanhamamura/c4/snake" ) type GamePersister struct { queries *gen.Queries } func NewGamePersister(q *gen.Queries) *GamePersister { return &GamePersister{queries: q} } func (p *GamePersister) SaveGame(g *game.Game) error { ctx := context.Background() _, err := p.queries.GetGame(ctx, g.ID) if err == sql.ErrNoRows { _, err = p.queries.CreateGame(ctx, gen.CreateGameParams{ ID: g.ID, Board: g.BoardToJSON(), CurrentTurn: int64(g.CurrentTurn), Status: int64(g.Status), }) return err } if err != nil { return err } var winnerUserID sql.NullString if g.Winner != nil && g.Winner.UserID != nil { winnerUserID = sql.NullString{String: *g.Winner.UserID, Valid: true} } winningCells := sql.NullString{} if wc := g.WinningCellsToJSON(); wc != "" { winningCells = sql.NullString{String: wc, Valid: true} } rematchGameID := sql.NullString{} if g.RematchGameID != nil { rematchGameID = sql.NullString{String: *g.RematchGameID, Valid: true} } return p.queries.UpdateGame(ctx, gen.UpdateGameParams{ Board: g.BoardToJSON(), CurrentTurn: int64(g.CurrentTurn), Status: int64(g.Status), WinnerUserID: winnerUserID, WinningCells: winningCells, RematchGameID: rematchGameID, ID: g.ID, }) } func (p *GamePersister) LoadGame(id string) (*game.Game, error) { ctx := context.Background() row, err := p.queries.GetGame(ctx, id) if err != nil { return nil, err } g := &game.Game{ ID: row.ID, CurrentTurn: int(row.CurrentTurn), Status: game.GameStatus(row.Status), } if err := g.BoardFromJSON(row.Board); err != nil { return nil, err } if row.WinningCells.Valid { g.WinningCellsFromJSON(row.WinningCells.String) } if row.RematchGameID.Valid { g.RematchGameID = &row.RematchGameID.String } return g, nil } func (p *GamePersister) SaveGamePlayer(gameID string, player *game.Player, slot int) error { ctx := context.Background() 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 p.queries.CreateGamePlayer(ctx, gen.CreateGamePlayerParams{ GameID: gameID, UserID: userID, GuestPlayerID: guestPlayerID, Nickname: player.Nickname, Color: int64(player.Color), Slot: int64(slot), }) } func (p *GamePersister) LoadGamePlayers(gameID string) ([]*game.Player, error) { ctx := context.Background() rows, err := p.queries.GetGamePlayers(ctx, gameID) if err != nil { return nil, err } players := make([]*game.Player, 0, len(rows)) for _, row := range rows { player := &game.Player{ Nickname: row.Nickname, Color: int(row.Color), } if row.UserID.Valid { player.UserID = &row.UserID.String player.ID = game.PlayerID(row.UserID.String) } else if row.GuestPlayerID.Valid { player.ID = game.PlayerID(row.GuestPlayerID.String) } players = append(players, player) } return players, nil } func (p *GamePersister) DeleteGame(id string) error { ctx := context.Background() return p.queries.DeleteGame(ctx, id) } // SnakePersister implements snake.Persister type SnakePersister struct { queries *gen.Queries } func NewSnakePersister(q *gen.Queries) *SnakePersister { return &SnakePersister{queries: q} } func (p *SnakePersister) SaveSnakeGame(sg *snake.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 := p.queries.GetSnakeGame(ctx, sg.ID) if err == sql.ErrNoRows { _, err = p.queries.CreateSnakeGame(ctx, gen.CreateSnakeGameParams{ ID: sg.ID, Board: boardJSON, Status: int64(sg.Status), GridWidth: gridWidth, GridHeight: gridHeight, }) return err } if err != nil { return err } var winnerUserID sql.NullString if sg.Winner != nil && sg.Winner.UserID != nil { winnerUserID = sql.NullString{String: *sg.Winner.UserID, Valid: true} } rematchGameID := sql.NullString{} if sg.RematchGameID != nil { rematchGameID = sql.NullString{String: *sg.RematchGameID, Valid: true} } return p.queries.UpdateSnakeGame(ctx, gen.UpdateSnakeGameParams{ Board: boardJSON, Status: int64(sg.Status), WinnerUserID: winnerUserID, RematchGameID: rematchGameID, ID: sg.ID, }) } func (p *SnakePersister) LoadSnakeGame(id string) (*snake.SnakeGame, error) { ctx := context.Background() row, err := p.queries.GetSnakeGame(ctx, id) if err != nil { return nil, err } state, err := snake.GameStateFromJSON(row.Board) if err != nil { state = &snake.GameState{} } if row.GridWidth.Valid { state.Width = int(row.GridWidth.Int64) } if row.GridHeight.Valid { state.Height = int(row.GridHeight.Int64) } sg := &snake.SnakeGame{ ID: row.ID, State: state, Players: make([]*snake.Player, 8), Status: snake.Status(row.Status), } if row.RematchGameID.Valid { sg.RematchGameID = &row.RematchGameID.String } return sg, nil } func (p *SnakePersister) SaveSnakePlayer(gameID string, player *snake.Player) error { ctx := context.Background() 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 p.queries.CreateSnakePlayer(ctx, gen.CreateSnakePlayerParams{ GameID: gameID, UserID: userID, GuestPlayerID: guestPlayerID, Nickname: player.Nickname, Color: int64(player.Slot + 1), Slot: int64(player.Slot), }) } func (p *SnakePersister) LoadSnakePlayers(gameID string) ([]*snake.Player, error) { ctx := context.Background() rows, err := p.queries.GetSnakePlayers(ctx, gameID) if err != nil { return nil, err } players := make([]*snake.Player, 0, len(rows)) for _, row := range rows { player := &snake.Player{ Nickname: row.Nickname, Slot: int(row.Slot), } if row.UserID.Valid { player.UserID = &row.UserID.String player.ID = snake.PlayerID(row.UserID.String) } else if row.GuestPlayerID.Valid { player.ID = snake.PlayerID(row.GuestPlayerID.String) } players = append(players, player) } return players, nil } func (p *SnakePersister) DeleteSnakeGame(id string) error { ctx := context.Background() return p.queries.DeleteSnakeGame(ctx, id) }