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.
This commit is contained in:
Ryan Hamamura
2026-03-02 19:09:01 -10:00
parent f47eb4cdf3
commit 063b03ce25
9 changed files with 61 additions and 45 deletions

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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

18
player/player.go Normal file
View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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