From 063b03ce25a25c37cdec73121f034be310e2d858 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:09:01 -1000 Subject: [PATCH] refactor: extract shared player.ID type and GenerateID to player package Both game and snake packages had identical PlayerID types and the snake package imported game.GenerateID. Now both use player.ID and player.GenerateID from the shared player package. --- features/c4game/handlers.go | 23 ++++++++++++----------- features/snakegame/handlers.go | 10 +++++----- game/persist.go | 11 ++++++----- game/store.go | 13 +++---------- game/types.go | 8 +++++--- player/player.go | 18 ++++++++++++++++++ snake/persist.go | 11 ++++++----- snake/store.go | 6 +++--- snake/types.go | 6 +++--- 9 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 player/player.go 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