From 63d0773ab53be706bdbd6b10668d98107e1b3724 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:10:18 -1000 Subject: [PATCH] Simplify codebase and fix Enter key on home page - Enter key now triggers createGame action on home page - Remove redundant setNickname action from home page - Remove unused code: join channel, Leave(), Stop() methods - Consolidate ID generation into game.GenerateID() - Remove unused CreatedAt field from Game struct --- game/store.go | 16 +++----------- game/types.go | 4 ---- go.mod | 6 +---- go.sum | 4 ---- main.go | 61 +++++++++++++++++++++++---------------------------- ui/lobby.go | 4 ++-- 6 files changed, 34 insertions(+), 61 deletions(-) diff --git a/game/store.go b/game/store.go index b44facc..2f820c3 100644 --- a/game/store.go +++ b/game/store.go @@ -28,7 +28,7 @@ func NewGameStore() *GameStore { } func (gs *GameStore) Create() *GameInstance { - id := generateGameID() + id := GenerateID(4) gi := NewGameInstance(id) gs.gamesMu.Lock() gs.games[id] = gi @@ -44,8 +44,8 @@ func (gs *GameStore) Get(id string) (*GameInstance, bool) { return gi, ok } -func generateGameID() string { - b := make([]byte, 4) +func GenerateID(size int) string { + b := make([]byte, size) rand.Read(b) return hex.EncodeToString(b) } @@ -55,7 +55,6 @@ type GameInstance struct { gameMu sync.RWMutex players map[PlayerID]Syncable playersMu sync.RWMutex - join chan *PlayerSession leave chan PlayerID done chan struct{} dirty bool @@ -65,7 +64,6 @@ func NewGameInstance(id string) *GameInstance { return &GameInstance{ game: NewGame(id), players: make(map[PlayerID]Syncable), - join: make(chan *PlayerSession, 5), leave: make(chan PlayerID, 5), done: make(chan struct{}), } @@ -101,10 +99,6 @@ func (gi *GameInstance) Join(ps *PlayerSession) bool { return true } -func (gi *GameInstance) Leave(pid PlayerID) { - gi.leave <- pid -} - func (gi *GameInstance) GetGame() *Game { gi.gameMu.RLock() defer gi.gameMu.RUnlock() @@ -186,7 +180,3 @@ func (gi *GameInstance) publish() { sync.Sync() } } - -func (gi *GameInstance) Stop() { - close(gi.done) -} diff --git a/game/types.go b/game/types.go index 38fd6a4..d819923 100644 --- a/game/types.go +++ b/game/types.go @@ -1,7 +1,5 @@ package game -import "time" - type PlayerID string type Player struct { @@ -27,7 +25,6 @@ type Game struct { Status GameStatus Winner *Player WinningCells [][2]int // Coordinates of winning 4 cells for highlighting - CreatedAt time.Time } func NewGame(id string) *Game { @@ -36,6 +33,5 @@ func NewGame(id string) *Game { Board: [6][7]int{}, CurrentTurn: 1, // Red goes first Status: StatusWaitingForPlayer, - CreatedAt: time.Now(), } } diff --git a/go.mod b/go.mod index ab8f217..0b25747 100644 --- a/go.mod +++ b/go.mod @@ -2,16 +2,12 @@ module github.com/ryanhamamura/c4 go 1.25.4 -require ( - github.com/go-via/via-plugin-picocss v0.1.1 - github.com/ryanhamamura/via v0.2.3 -) +require github.com/ryanhamamura/via v0.2.3 require ( github.com/CAFxX/httpcompression v0.0.9 // indirect github.com/alexedwards/scs/v2 v2.9.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect - github.com/go-via/via v0.1.4 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/starfederation/datastar-go v1.0.3 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index f7e5d68..49a7451 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,6 @@ github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUS github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-via/via v0.1.4 h1:Fz9fwaT5+TBqcetiVM33SxkuysAeFDOiiASFu3GW7WY= -github.com/go-via/via v0.1.4/go.mod h1:Y8oddRwP6SWX15Xb6UQj4HtLZwxTYI1HbWBmELtB/f8= -github.com/go-via/via-plugin-picocss v0.1.1 h1:rbA9wL9eEanT8HOOfX1b4Mr2L2VjaDrsIrUECDxV73k= -github.com/go-via/via-plugin-picocss v0.1.1/go.mod h1:npvsvG2FWeIPkzHzSSzW+uBGE0m5gnIAdlePqKcfuAQ= github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k= github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= diff --git a/main.go b/main.go index 625ef44..c22b782 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,6 @@ package main import ( - "crypto/rand" - "encoding/hex" - "github.com/ryanhamamura/c4/game" "github.com/ryanhamamura/c4/ui" "github.com/ryanhamamura/via" @@ -29,14 +26,6 @@ func main() { v.Page("/", func(c *via.Context) { nickname := c.Signal("") - setNickname := c.Action(func() { - name := nickname.String() - if name != "" { - c.Session().Set("nickname", name) - c.Sync() - } - }) - createGame := c.Action(func() { name := nickname.String() if name == "" { @@ -51,7 +40,7 @@ func main() { c.View(func() h.H { return ui.LobbyView( nickname.Bind(), - setNickname.OnKeyDown("Enter"), + createGame.OnKeyDown("Enter"), createGame.OnClick(), ) }) @@ -66,8 +55,6 @@ func main() { colSignal := c.Signal(0) var gi *game.GameInstance - var player *game.Player - var playerJoined bool var gameExists bool // Look up game (may not exist during warmup or invalid ID) @@ -75,6 +62,13 @@ func main() { gi, gameExists = store.Get(gameID) } + // Generate a stable player ID for this session + playerID := game.PlayerID(c.Session().GetString("player_id")) + if playerID == "" { + playerID = game.PlayerID(game.GenerateID(8)) + c.Session().Set("player_id", string(playerID)) + } + setNickname := c.Action(func() { if gi == nil { return @@ -85,12 +79,13 @@ func main() { } c.Session().Set("nickname", name) - if !playerJoined { - player = &game.Player{ - ID: game.PlayerID(generatePlayerID()), + // Try to join if not already in game + if gi.GetPlayerColor(playerID) == 0 { + player := &game.Player{ + ID: playerID, Nickname: name, } - playerJoined = gi.Join(&game.PlayerSession{ + gi.Join(&game.PlayerSession{ Player: player, Sync: c, }) @@ -99,20 +94,25 @@ func main() { }) dropPiece := c.Action(func() { - if gi == nil || player == nil { + if gi == nil { + return + } + myColor := gi.GetPlayerColor(playerID) + if myColor == 0 { return } col := colSignal.Int() - gi.DropPiece(col, player.Color) + gi.DropPiece(col, myColor) + c.Sync() }) // If nickname exists in session and game exists, join immediately - if gameExists && sessionNickname != "" { - player = &game.Player{ - ID: game.PlayerID(generatePlayerID()), + if gameExists && sessionNickname != "" && gi.GetPlayerColor(playerID) == 0 { + player := &game.Player{ + ID: playerID, Nickname: sessionNickname, } - playerJoined = gi.Join(&game.PlayerSession{ + gi.Join(&game.PlayerSession{ Player: player, Sync: c, }) @@ -125,8 +125,10 @@ func main() { return h.Div() } - // Need nickname first - if !playerJoined { + myColor := gi.GetPlayerColor(playerID) + + // Need nickname first / not joined yet + if myColor == 0 { return ui.NicknamePrompt( nickname.Bind(), setNickname.OnKeyDown("Enter"), @@ -135,7 +137,6 @@ func main() { } g := gi.GetGame() - myColor := player.Color // Create column click function columnClick := func(col int) h.H { @@ -164,12 +165,6 @@ func main() { v.Start() } -func generatePlayerID() string { - b := make([]byte, 8) - rand.Read(b) - return hex.EncodeToString(b) -} - const gameCSS = ` body { margin: 0; } diff --git a/ui/lobby.go b/ui/lobby.go index 2f92588..8ad87ed 100644 --- a/ui/lobby.go +++ b/ui/lobby.go @@ -4,7 +4,7 @@ import ( "github.com/ryanhamamura/via/h" ) -func LobbyView(nicknameBind, setNicknameKeyDown, createGameClick h.H) h.H { +func LobbyView(nicknameBind, createGameKeyDown, createGameClick h.H) h.H { return h.Main(h.Class("container"), h.Div(h.Class("lobby"), h.H1(h.Text("Connect 4")), @@ -18,7 +18,7 @@ func LobbyView(nicknameBind, setNicknameKeyDown, createGameClick h.H) h.H { h.Placeholder("Enter your nickname"), nicknameBind, h.Attr("required"), - setNicknameKeyDown, + createGameKeyDown, ), ), h.Button(