diff --git a/features/c4game/handlers.go b/features/c4game/handlers.go index 8b9d0d7..8b8b251 100644 --- a/features/c4game/handlers.go +++ b/features/c4game/handlers.go @@ -18,6 +18,7 @@ import ( "github.com/ryanhamamura/c4/features/c4game/components" "github.com/ryanhamamura/c4/features/c4game/pages" "github.com/ryanhamamura/c4/game" + "github.com/ryanhamamura/c4/player" ) func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc { @@ -30,15 +31,15 @@ func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, queries return } - playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + playerID := player.ID(sessions.GetString(r.Context(), "player_id")) if playerID == "" { - playerID = game.PlayerID(game.GenerateID(8)) + playerID = player.ID(player.GenerateID(8)) sessions.Put(r.Context(), "player_id", string(playerID)) } userID := sessions.GetString(r.Context(), "user_id") if userID != "" { - playerID = game.PlayerID(userID) + playerID = player.ID(userID) } nickname := sessions.GetString(r.Context(), "nickname") @@ -95,10 +96,10 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio return } - playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + playerID := player.ID(sessions.GetString(r.Context(), "player_id")) userID := sessions.GetString(r.Context(), "user_id") if userID != "" { - playerID = game.PlayerID(userID) + playerID = player.ID(userID) } myColor := gi.GetPlayerColor(playerID) @@ -185,10 +186,10 @@ func HandleDropPiece(store *game.GameStore, sessions *scs.SessionManager) http.H return } - playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + playerID := player.ID(sessions.GetString(r.Context(), "player_id")) userID := sessions.GetString(r.Context(), "user_id") if userID != "" { - playerID = game.PlayerID(userID) + playerID = player.ID(userID) } myColor := gi.GetPlayerColor(playerID) @@ -229,10 +230,10 @@ func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionM return } - playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + playerID := player.ID(sessions.GetString(r.Context(), "player_id")) userID := sessions.GetString(r.Context(), "user_id") if userID != "" { - playerID = game.PlayerID(userID) + playerID = player.ID(userID) } myColor := gi.GetPlayerColor(playerID) @@ -298,10 +299,10 @@ func HandleSetNickname(store *game.GameStore, sessions *scs.SessionManager) http sessions.Put(r.Context(), "nickname", signals.Nickname) - playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + playerID := player.ID(sessions.GetString(r.Context(), "player_id")) userID := sessions.GetString(r.Context(), "user_id") if userID != "" { - playerID = game.PlayerID(userID) + playerID = player.ID(userID) } if gi.GetPlayerColor(playerID) == 0 { diff --git a/features/snakegame/handlers.go b/features/snakegame/handlers.go index 03292a6..3fa0143 100644 --- a/features/snakegame/handlers.go +++ b/features/snakegame/handlers.go @@ -13,21 +13,21 @@ import ( "github.com/ryanhamamura/c4/features/snakegame/components" "github.com/ryanhamamura/c4/features/snakegame/pages" - "github.com/ryanhamamura/c4/game" + "github.com/ryanhamamura/c4/player" "github.com/ryanhamamura/c4/snake" ) -func getPlayerID(sessions *scs.SessionManager, r *http.Request) snake.PlayerID { +func getPlayerID(sessions *scs.SessionManager, r *http.Request) player.ID { pid := sessions.GetString(r.Context(), "player_id") if pid == "" { - pid = game.GenerateID(8) + pid = player.GenerateID(8) sessions.Put(r.Context(), "player_id", pid) } userID := sessions.GetString(r.Context(), "user_id") if userID != "" { - return snake.PlayerID(userID) + return player.ID(userID) } - return snake.PlayerID(pid) + return player.ID(pid) } func HandleSnakePage(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { diff --git a/game/persist.go b/game/persist.go index 0b615b7..5efe140 100644 --- a/game/persist.go +++ b/game/persist.go @@ -4,6 +4,7 @@ import ( "context" "github.com/ryanhamamura/c4/db/repository" + "github.com/ryanhamamura/c4/player" "github.com/rs/zerolog/log" ) @@ -109,19 +110,19 @@ func gameFromRow(row *repository.Game) (*Game, error) { func playersFromRows(rows []*repository.GamePlayer) []*Player { players := make([]*Player, 0, len(rows)) for _, row := range rows { - player := &Player{ + p := &Player{ Nickname: row.Nickname, Color: int(row.Color), } if row.UserID != nil { - player.UserID = row.UserID - player.ID = PlayerID(*row.UserID) + p.UserID = row.UserID + p.ID = player.ID(*row.UserID) } else if row.GuestPlayerID != nil { - player.ID = PlayerID(*row.GuestPlayerID) + p.ID = player.ID(*row.GuestPlayerID) } - players = append(players, player) + players = append(players, p) } return players } diff --git a/game/store.go b/game/store.go index d919441..818942c 100644 --- a/game/store.go +++ b/game/store.go @@ -2,11 +2,10 @@ package game import ( "context" - "crypto/rand" - "encoding/hex" "sync" "github.com/ryanhamamura/c4/db/repository" + "github.com/ryanhamamura/c4/player" ) type PlayerSession struct { @@ -40,7 +39,7 @@ func (gs *GameStore) makeNotify(gameID string) func() { } func (gs *GameStore) Create() *GameInstance { - id := GenerateID(4) + id := player.GenerateID(4) gi := NewGameInstance(id) gi.queries = gs.queries gi.notify = gs.makeNotify(id) @@ -107,12 +106,6 @@ func (gs *GameStore) Delete(id string) error { return nil } -func GenerateID(size int) string { - b := make([]byte, size) - _, _ = rand.Read(b) - return hex.EncodeToString(b) -} - type GameInstance struct { game *Game gameMu sync.RWMutex @@ -166,7 +159,7 @@ func (gi *GameInstance) GetGame() *Game { return gi.game } -func (gi *GameInstance) GetPlayerColor(pid PlayerID) int { +func (gi *GameInstance) GetPlayerColor(pid player.ID) int { gi.gameMu.RLock() defer gi.gameMu.RUnlock() for _, p := range gi.game.Players { diff --git a/game/types.go b/game/types.go index 71f0ae8..2eec2b1 100644 --- a/game/types.go +++ b/game/types.go @@ -1,11 +1,13 @@ package game -import "encoding/json" +import ( + "encoding/json" -type PlayerID string + "github.com/ryanhamamura/c4/player" +) type Player struct { - ID PlayerID + ID player.ID UserID *string // UUID for authenticated users, nil for guests Nickname string Color int // 1 = Red, 2 = Yellow diff --git a/player/player.go b/player/player.go new file mode 100644 index 0000000..ca65a3f --- /dev/null +++ b/player/player.go @@ -0,0 +1,18 @@ +// Package player provides shared identity types used across game packages. +package player + +import ( + "crypto/rand" + "encoding/hex" +) + +// ID uniquely identifies a player within a session. For authenticated users +// this is their user UUID; for guests it's a random hex string. +type ID string + +// GenerateID returns a random hex string of 2*size characters. +func GenerateID(size int) string { + b := make([]byte, size) + _, _ = rand.Read(b) + return hex.EncodeToString(b) +} diff --git a/snake/persist.go b/snake/persist.go index dba9da9..741cb40 100644 --- a/snake/persist.go +++ b/snake/persist.go @@ -4,6 +4,7 @@ import ( "context" "github.com/ryanhamamura/c4/db/repository" + "github.com/ryanhamamura/c4/player" "github.com/rs/zerolog/log" ) @@ -122,19 +123,19 @@ func snakeGameFromRow(row *repository.Game) (*SnakeGame, error) { func snakePlayersFromRows(rows []*repository.GamePlayer) []*Player { players := make([]*Player, 0, len(rows)) for _, row := range rows { - player := &Player{ + p := &Player{ Nickname: row.Nickname, Slot: int(row.Slot), } if row.UserID != nil { - player.UserID = row.UserID - player.ID = PlayerID(*row.UserID) + p.UserID = row.UserID + p.ID = player.ID(*row.UserID) } else if row.GuestPlayerID != nil { - player.ID = PlayerID(*row.GuestPlayerID) + p.ID = player.ID(*row.GuestPlayerID) } - players = append(players, player) + players = append(players, p) } return players } diff --git a/snake/store.go b/snake/store.go index 4543a92..2590a4a 100644 --- a/snake/store.go +++ b/snake/store.go @@ -5,7 +5,7 @@ import ( "sync" "github.com/ryanhamamura/c4/db/repository" - "github.com/ryanhamamura/c4/game" + "github.com/ryanhamamura/c4/player" ) type SnakeStore struct { @@ -38,7 +38,7 @@ func (ss *SnakeStore) Create(width, height int, mode GameMode, speed int) *Snake if speed <= 0 { speed = DefaultSpeed } - id := game.GenerateID(4) + id := player.GenerateID(4) sg := &SnakeGame{ ID: id, State: &GameState{ @@ -172,7 +172,7 @@ func (si *SnakeGameInstance) GetGame() *SnakeGame { return si.game.snapshot() } -func (si *SnakeGameInstance) GetPlayerSlot(pid PlayerID) int { +func (si *SnakeGameInstance) GetPlayerSlot(pid player.ID) int { si.gameMu.RLock() defer si.gameMu.RUnlock() for i, p := range si.game.Players { diff --git a/snake/types.go b/snake/types.go index 8272765..bbcb836 100644 --- a/snake/types.go +++ b/snake/types.go @@ -3,6 +3,8 @@ package snake import ( "encoding/json" "time" + + "github.com/ryanhamamura/c4/player" ) type Direction int @@ -78,10 +80,8 @@ const ( StatusFinished ) -type PlayerID string - type Player struct { - ID PlayerID + ID player.ID UserID *string Nickname string Slot int // 0-7