refactor: extract shared player, session, and chat packages #5

Merged
ryan merged 14 commits from refactor/shared-player-session-chat into main 2026-03-03 08:50:13 +00:00
9 changed files with 61 additions and 45 deletions
Showing only changes of commit 063b03ce25 - Show all commits

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