Inline persistence logic directly into game stores and handlers: - game/persist.go: DB mapping methods on GameStore and GameInstance - snake/persist.go: DB mapping methods on SnakeStore and SnakeGameInstance - Chat persistence inlined into c4game handlers - Delete db/persister.go (GamePersister, SnakePersister, ChatPersister) - Stores now take *repository.Queries directly instead of Persister interface
187 lines
4.7 KiB
Go
187 lines
4.7 KiB
Go
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
|
|
}
|