refactor: rename game package to connect4, drop Game prefix from types
All checks were successful
CI / Deploy / test (pull_request) Successful in 16s
CI / Deploy / lint (pull_request) Successful in 25s
CI / Deploy / deploy (pull_request) Has been skipped

Rename game/ -> connect4/ to avoid c4/game stutter. Drop redundant
Game prefix from exported types (GameStore -> Store, GameInstance ->
Instance, GameStatus -> Status). Rename NATS subjects from game.{id}
to connect4.{id}. URL routes unchanged.
This commit is contained in:
Ryan Hamamura
2026-03-02 20:31:00 -10:00
parent f71acfc73e
commit 38eb9ee398
14 changed files with 125 additions and 127 deletions

View File

@@ -3,11 +3,11 @@ package components
import (
"fmt"
"github.com/ryanhamamura/c4/game"
"github.com/ryanhamamura/c4/connect4"
"github.com/starfederation/datastar-go/datastar"
)
templ Board(g *game.Game, myColor int) {
templ Board(g *connect4.Game, myColor int) {
<div id="c4-board" class="board">
for col := 0; col < 7; col++ {
@column(g, col, myColor)
@@ -15,8 +15,8 @@ templ Board(g *game.Game, myColor int) {
</div>
}
templ column(g *game.Game, colIdx int, myColor int) {
if g.Status == game.StatusInProgress && myColor == g.CurrentTurn {
templ column(g *connect4.Game, colIdx int, myColor int) {
if g.Status == connect4.StatusInProgress && myColor == g.CurrentTurn {
<div
class="column clickable"
data-on:click={ datastar.PostSSE("/games/%s/drop?col=%d", g.ID, colIdx) }
@@ -34,14 +34,14 @@ templ column(g *game.Game, colIdx int, myColor int) {
}
}
templ cell(g *game.Game, row int, col int) {
templ cell(g *connect4.Game, row int, col int) {
<div class={ cellClass(g, row, col) }></div>
}
func cellClass(g *game.Game, row, col int) string {
func cellClass(g *connect4.Game, row, col int) string {
color := g.Board[row][col]
activeTurn := 0
if g.Status == game.StatusInProgress {
if g.Status == connect4.StatusInProgress {
activeTurn = g.CurrentTurn
}

View File

@@ -2,11 +2,11 @@ package components
import (
"github.com/ryanhamamura/c4/config"
"github.com/ryanhamamura/c4/game"
"github.com/ryanhamamura/c4/connect4"
"github.com/starfederation/datastar-go/datastar"
)
templ StatusBanner(g *game.Game, myColor int) {
templ StatusBanner(g *connect4.Game, myColor int) {
<div id="c4-status" class={ statusClass(g, myColor) }>
{ statusMessage(g, myColor) }
if g.IsFinished() {
@@ -30,7 +30,7 @@ templ StatusBanner(g *game.Game, myColor int) {
</div>
}
templ PlayerInfo(g *game.Game, myColor int) {
templ PlayerInfo(g *connect4.Game, myColor int) {
<div id="c4-players" class="flex gap-8 mb-2">
for _, info := range playerInfoPairs(g, myColor) {
<div class="flex items-center gap-2">
@@ -61,36 +61,36 @@ script copyToClipboard(url string) {
navigator.clipboard.writeText(url)
}
func statusClass(g *game.Game, myColor int) string {
func statusClass(g *connect4.Game, myColor int) string {
switch g.Status {
case game.StatusWaitingForPlayer:
case connect4.StatusWaitingForPlayer:
return "alert bg-base-200 text-xl font-bold"
case game.StatusInProgress:
case connect4.StatusInProgress:
if g.CurrentTurn == myColor {
return "alert alert-success text-xl font-bold"
}
return "alert bg-base-200 text-xl font-bold"
case game.StatusWon:
case connect4.StatusWon:
if g.Winner != nil && g.Winner.Color == myColor {
return "alert alert-success text-xl font-bold"
}
return "alert alert-error text-xl font-bold"
case game.StatusDraw:
case connect4.StatusDraw:
return "alert alert-warning text-xl font-bold"
}
return "alert bg-base-200 text-xl font-bold"
}
func statusMessage(g *game.Game, myColor int) string {
func statusMessage(g *connect4.Game, myColor int) string {
switch g.Status {
case game.StatusWaitingForPlayer:
case connect4.StatusWaitingForPlayer:
return "Waiting for opponent..."
case game.StatusInProgress:
case connect4.StatusInProgress:
if g.CurrentTurn == myColor {
return "Your turn!"
}
return opponentName(g, myColor) + "'s turn"
case game.StatusWon:
case connect4.StatusWon:
if g.Winner != nil && g.Winner.Color == myColor {
return "You win!"
}
@@ -98,13 +98,13 @@ func statusMessage(g *game.Game, myColor int) string {
return g.Winner.Nickname + " wins!"
}
return "Game over"
case game.StatusDraw:
case connect4.StatusDraw:
return "It's a draw!"
}
return ""
}
func opponentName(g *game.Game, myColor int) string {
func opponentName(g *connect4.Game, myColor int) string {
for _, p := range g.Players {
if p != nil && p.Color != myColor {
return p.Nickname
@@ -118,7 +118,7 @@ type playerInfoData struct {
Label string
}
func playerInfoPairs(g *game.Game, myColor int) []playerInfoData {
func playerInfoPairs(g *connect4.Game, myColor int) []playerInfoData {
var result []playerInfoData
var myName, oppName string

View File

@@ -13,10 +13,10 @@ import (
"github.com/ryanhamamura/c4/chat"
chatcomponents "github.com/ryanhamamura/c4/chat/components"
"github.com/ryanhamamura/c4/connect4"
"github.com/ryanhamamura/c4/db/repository"
"github.com/ryanhamamura/c4/features/c4game/components"
"github.com/ryanhamamura/c4/features/c4game/pages"
"github.com/ryanhamamura/c4/game"
"github.com/ryanhamamura/c4/sessions"
)
@@ -41,7 +41,7 @@ func c4ChatConfig(gameID string) chatcomponents.Config {
}
}
func HandleGamePage(store *game.GameStore, sm *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
func HandleGamePage(store *connect4.Store, sm *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "id")
@@ -57,14 +57,14 @@ func HandleGamePage(store *game.GameStore, sm *scs.SessionManager, queries *repo
// Auto-join if player has a nickname but isn't in the game yet
if nickname != "" && gi.GetPlayerColor(playerID) == 0 {
p := &game.Player{
p := &connect4.Player{
ID: playerID,
Nickname: nickname,
}
if userID != "" {
p.UserID = &userID
}
gi.Join(&game.PlayerSession{Player: p})
gi.Join(&connect4.PlayerSession{Player: p})
}
myColor := gi.GetPlayerColor(playerID)
@@ -93,7 +93,7 @@ func HandleGamePage(store *game.GameStore, sm *scs.SessionManager, queries *repo
}
}
func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sm *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
func HandleGameEvents(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "id")
@@ -111,14 +111,14 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sm *scs.SessionManag
))
chatCfg := c4ChatConfig(gameID)
room := chat.NewRoom(nc, "game.chat."+gameID, chat.LoadMessages(queries, gameID))
room := chat.NewRoom(nc, "connect4.chat."+gameID, chat.LoadMessages(queries, gameID))
// Send initial render
sendGameComponents(sse, gi, myColor, room, chatCfg)
// Subscribe to game state updates
gameCh := make(chan *nats.Msg, 64)
gameSub, err := nc.ChanSubscribe("game."+gameID, gameCh)
gameSub, err := nc.ChanSubscribe("connect4."+gameID, gameCh)
if err != nil {
return
}
@@ -152,7 +152,7 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sm *scs.SessionManag
}
}
func HandleDropPiece(store *game.GameStore, sm *scs.SessionManager) http.HandlerFunc {
func HandleDropPiece(store *connect4.Store, sm *scs.SessionManager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "id")
@@ -181,7 +181,7 @@ func HandleDropPiece(store *game.GameStore, sm *scs.SessionManager) http.Handler
}
}
func HandleSendChat(store *game.GameStore, nc *nats.Conn, sm *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
func HandleSendChat(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "id")
@@ -230,7 +230,7 @@ func HandleSendChat(store *game.GameStore, nc *nats.Conn, sm *scs.SessionManager
}
chat.SaveMessage(queries, gameID, msg)
room := chat.NewRoom(nc, "game.chat."+gameID, nil)
room := chat.NewRoom(nc, "connect4.chat."+gameID, nil)
room.Send(msg)
sse := datastar.NewSSE(w, r)
@@ -238,7 +238,7 @@ func HandleSendChat(store *game.GameStore, nc *nats.Conn, sm *scs.SessionManager
}
}
func HandleSetNickname(store *game.GameStore, sm *scs.SessionManager) http.HandlerFunc {
func HandleSetNickname(store *connect4.Store, sm *scs.SessionManager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "id")
@@ -269,14 +269,14 @@ func HandleSetNickname(store *game.GameStore, sm *scs.SessionManager) http.Handl
userID := sessions.GetUserID(sm, r)
if gi.GetPlayerColor(playerID) == 0 {
p := &game.Player{
p := &connect4.Player{
ID: playerID,
Nickname: signals.Nickname,
}
if userID != "" {
p.UserID = &userID
}
gi.Join(&game.PlayerSession{Player: p})
gi.Join(&connect4.PlayerSession{Player: p})
}
sse := datastar.NewSSE(w, r)
@@ -284,7 +284,7 @@ func HandleSetNickname(store *game.GameStore, sm *scs.SessionManager) http.Handl
}
}
func HandleRematch(store *game.GameStore, sm *scs.SessionManager) http.HandlerFunc {
func HandleRematch(store *connect4.Store, sm *scs.SessionManager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "id")
@@ -304,7 +304,7 @@ func HandleRematch(store *game.GameStore, sm *scs.SessionManager) http.HandlerFu
}
// sendGameComponents patches all game-related SSE components.
func sendGameComponents(sse *datastar.ServerSentEventGenerator, gi *game.GameInstance, myColor int, room *chat.Room, chatCfg chatcomponents.Config) {
func sendGameComponents(sse *datastar.ServerSentEventGenerator, gi *connect4.Instance, myColor int, room *chat.Room, chatCfg chatcomponents.Config) {
g := gi.GetGame()
sse.PatchElementTempl(components.Board(g, myColor), datastar.WithSelectorID("c4-board")) //nolint:errcheck

View File

@@ -3,14 +3,14 @@ package pages
import (
"github.com/ryanhamamura/c4/chat"
chatcomponents "github.com/ryanhamamura/c4/chat/components"
"github.com/ryanhamamura/c4/connect4"
"github.com/ryanhamamura/c4/features/c4game/components"
sharedcomponents "github.com/ryanhamamura/c4/features/common/components"
"github.com/ryanhamamura/c4/features/common/layouts"
"github.com/ryanhamamura/c4/game"
"github.com/starfederation/datastar-go/datastar"
)
templ GamePage(g *game.Game, myColor int, messages []chat.Message, chatCfg chatcomponents.Config) {
templ GamePage(g *connect4.Game, myColor int, messages []chat.Message, chatCfg chatcomponents.Config) {
@layouts.Base("Connect 4") {
<main
class="flex flex-col items-center gap-4 p-4"
@@ -25,7 +25,7 @@ templ GamePage(g *game.Game, myColor int, messages []chat.Message, chatCfg chatc
@components.Board(g, myColor)
@chatcomponents.Chat(messages, chatCfg)
</div>
if g.Status == game.StatusWaitingForPlayer {
if g.Status == connect4.StatusWaitingForPlayer {
@components.InviteLink(g.ID)
}
</main>

View File

@@ -6,13 +6,13 @@ import (
"github.com/go-chi/chi/v5"
"github.com/nats-io/nats.go"
"github.com/ryanhamamura/c4/connect4"
"github.com/ryanhamamura/c4/db/repository"
"github.com/ryanhamamura/c4/game"
)
func SetupRoutes(
router chi.Router,
store *game.GameStore,
store *connect4.Store,
nc *nats.Conn,
sessions *scs.SessionManager,
queries *repository.Queries,

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"time"
"github.com/ryanhamamura/c4/game"
"github.com/ryanhamamura/c4/connect4"
"github.com/starfederation/datastar-go/datastar"
)
@@ -46,10 +46,10 @@ templ gameListEntry(g GameListItem) {
}
func statusText(g GameListItem) string {
switch game.GameStatus(g.Status) {
case game.StatusWaitingForPlayer:
switch connect4.Status(g.Status) {
case connect4.StatusWaitingForPlayer:
return "Waiting for opponent"
case game.StatusInProgress:
case connect4.StatusInProgress:
if g.IsMyTurn {
return "Your turn!"
}
@@ -59,10 +59,10 @@ func statusText(g GameListItem) string {
}
func statusClass(g GameListItem) string {
switch game.GameStatus(g.Status) {
case game.StatusWaitingForPlayer:
switch connect4.Status(g.Status) {
case connect4.StatusWaitingForPlayer:
return "text-sm opacity-60"
case game.StatusInProgress:
case connect4.StatusInProgress:
if g.IsMyTurn {
return "text-sm text-success font-bold"
}

View File

@@ -7,10 +7,10 @@ import (
"strconv"
"time"
"github.com/ryanhamamura/c4/connect4"
"github.com/ryanhamamura/c4/db/repository"
lobbycomponents "github.com/ryanhamamura/c4/features/lobby/components"
"github.com/ryanhamamura/c4/features/lobby/pages"
"github.com/ryanhamamura/c4/game"
"github.com/ryanhamamura/c4/snake"
"github.com/alexedwards/scs/v2"
@@ -80,7 +80,7 @@ func HandleLobbyPage(queries *repository.Queries, sessions *scs.SessionManager,
}
// HandleCreateGame reads the nickname signal, creates a connect4 game, and redirects via SSE.
func HandleCreateGame(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc {
func HandleCreateGame(store *connect4.Store, sessions *scs.SessionManager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
type Signals struct {
Nickname string `json:"nickname"`
@@ -104,7 +104,7 @@ func HandleCreateGame(store *game.GameStore, sessions *scs.SessionManager) http.
}
// HandleDeleteGame deletes a connect4 game and redirects to the lobby.
func HandleDeleteGame(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc {
func HandleDeleteGame(store *connect4.Store, sessions *scs.SessionManager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "id")
if gameID == "" {

View File

@@ -2,8 +2,8 @@
package lobby
import (
"github.com/ryanhamamura/c4/connect4"
"github.com/ryanhamamura/c4/db/repository"
"github.com/ryanhamamura/c4/game"
"github.com/ryanhamamura/c4/snake"
"github.com/alexedwards/scs/v2"
@@ -14,7 +14,7 @@ func SetupRoutes(
router chi.Router,
queries *repository.Queries,
sessions *scs.SessionManager,
store *game.GameStore,
store *connect4.Store,
snakeStore *snake.SnakeStore,
) {
router.Get("/", HandleLobbyPage(queries, sessions, snakeStore))