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
This commit is contained in:
@@ -28,7 +28,7 @@ func NewGameStore() *GameStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gs *GameStore) Create() *GameInstance {
|
func (gs *GameStore) Create() *GameInstance {
|
||||||
id := generateGameID()
|
id := GenerateID(4)
|
||||||
gi := NewGameInstance(id)
|
gi := NewGameInstance(id)
|
||||||
gs.gamesMu.Lock()
|
gs.gamesMu.Lock()
|
||||||
gs.games[id] = gi
|
gs.games[id] = gi
|
||||||
@@ -44,8 +44,8 @@ func (gs *GameStore) Get(id string) (*GameInstance, bool) {
|
|||||||
return gi, ok
|
return gi, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateGameID() string {
|
func GenerateID(size int) string {
|
||||||
b := make([]byte, 4)
|
b := make([]byte, size)
|
||||||
rand.Read(b)
|
rand.Read(b)
|
||||||
return hex.EncodeToString(b)
|
return hex.EncodeToString(b)
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,6 @@ type GameInstance struct {
|
|||||||
gameMu sync.RWMutex
|
gameMu sync.RWMutex
|
||||||
players map[PlayerID]Syncable
|
players map[PlayerID]Syncable
|
||||||
playersMu sync.RWMutex
|
playersMu sync.RWMutex
|
||||||
join chan *PlayerSession
|
|
||||||
leave chan PlayerID
|
leave chan PlayerID
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
dirty bool
|
dirty bool
|
||||||
@@ -65,7 +64,6 @@ func NewGameInstance(id string) *GameInstance {
|
|||||||
return &GameInstance{
|
return &GameInstance{
|
||||||
game: NewGame(id),
|
game: NewGame(id),
|
||||||
players: make(map[PlayerID]Syncable),
|
players: make(map[PlayerID]Syncable),
|
||||||
join: make(chan *PlayerSession, 5),
|
|
||||||
leave: make(chan PlayerID, 5),
|
leave: make(chan PlayerID, 5),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
@@ -101,10 +99,6 @@ func (gi *GameInstance) Join(ps *PlayerSession) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gi *GameInstance) Leave(pid PlayerID) {
|
|
||||||
gi.leave <- pid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gi *GameInstance) GetGame() *Game {
|
func (gi *GameInstance) GetGame() *Game {
|
||||||
gi.gameMu.RLock()
|
gi.gameMu.RLock()
|
||||||
defer gi.gameMu.RUnlock()
|
defer gi.gameMu.RUnlock()
|
||||||
@@ -186,7 +180,3 @@ func (gi *GameInstance) publish() {
|
|||||||
sync.Sync()
|
sync.Sync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gi *GameInstance) Stop() {
|
|
||||||
close(gi.done)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type PlayerID string
|
type PlayerID string
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
@@ -27,7 +25,6 @@ type Game struct {
|
|||||||
Status GameStatus
|
Status GameStatus
|
||||||
Winner *Player
|
Winner *Player
|
||||||
WinningCells [][2]int // Coordinates of winning 4 cells for highlighting
|
WinningCells [][2]int // Coordinates of winning 4 cells for highlighting
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGame(id string) *Game {
|
func NewGame(id string) *Game {
|
||||||
@@ -36,6 +33,5 @@ func NewGame(id string) *Game {
|
|||||||
Board: [6][7]int{},
|
Board: [6][7]int{},
|
||||||
CurrentTurn: 1, // Red goes first
|
CurrentTurn: 1, // Red goes first
|
||||||
Status: StatusWaitingForPlayer,
|
Status: StatusWaitingForPlayer,
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -2,16 +2,12 @@ module github.com/ryanhamamura/c4
|
|||||||
|
|
||||||
go 1.25.4
|
go 1.25.4
|
||||||
|
|
||||||
require (
|
require github.com/ryanhamamura/via v0.2.3
|
||||||
github.com/go-via/via-plugin-picocss v0.1.1
|
|
||||||
github.com/ryanhamamura/via v0.2.3
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/CAFxX/httpcompression v0.0.9 // indirect
|
github.com/CAFxX/httpcompression v0.0.9 // indirect
|
||||||
github.com/alexedwards/scs/v2 v2.9.0 // indirect
|
github.com/alexedwards/scs/v2 v2.9.0 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.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/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/starfederation/datastar-go v1.0.3 // indirect
|
github.com/starfederation/datastar-go v1.0.3 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
|||||||
4
go.sum
4
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k=
|
||||||
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s=
|
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=
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
|
|||||||
61
main.go
61
main.go
@@ -1,9 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/ryanhamamura/c4/game"
|
"github.com/ryanhamamura/c4/game"
|
||||||
"github.com/ryanhamamura/c4/ui"
|
"github.com/ryanhamamura/c4/ui"
|
||||||
"github.com/ryanhamamura/via"
|
"github.com/ryanhamamura/via"
|
||||||
@@ -29,14 +26,6 @@ func main() {
|
|||||||
v.Page("/", func(c *via.Context) {
|
v.Page("/", func(c *via.Context) {
|
||||||
nickname := c.Signal("")
|
nickname := c.Signal("")
|
||||||
|
|
||||||
setNickname := c.Action(func() {
|
|
||||||
name := nickname.String()
|
|
||||||
if name != "" {
|
|
||||||
c.Session().Set("nickname", name)
|
|
||||||
c.Sync()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createGame := c.Action(func() {
|
createGame := c.Action(func() {
|
||||||
name := nickname.String()
|
name := nickname.String()
|
||||||
if name == "" {
|
if name == "" {
|
||||||
@@ -51,7 +40,7 @@ func main() {
|
|||||||
c.View(func() h.H {
|
c.View(func() h.H {
|
||||||
return ui.LobbyView(
|
return ui.LobbyView(
|
||||||
nickname.Bind(),
|
nickname.Bind(),
|
||||||
setNickname.OnKeyDown("Enter"),
|
createGame.OnKeyDown("Enter"),
|
||||||
createGame.OnClick(),
|
createGame.OnClick(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -66,8 +55,6 @@ func main() {
|
|||||||
colSignal := c.Signal(0)
|
colSignal := c.Signal(0)
|
||||||
|
|
||||||
var gi *game.GameInstance
|
var gi *game.GameInstance
|
||||||
var player *game.Player
|
|
||||||
var playerJoined bool
|
|
||||||
var gameExists bool
|
var gameExists bool
|
||||||
|
|
||||||
// Look up game (may not exist during warmup or invalid ID)
|
// Look up game (may not exist during warmup or invalid ID)
|
||||||
@@ -75,6 +62,13 @@ func main() {
|
|||||||
gi, gameExists = store.Get(gameID)
|
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() {
|
setNickname := c.Action(func() {
|
||||||
if gi == nil {
|
if gi == nil {
|
||||||
return
|
return
|
||||||
@@ -85,12 +79,13 @@ func main() {
|
|||||||
}
|
}
|
||||||
c.Session().Set("nickname", name)
|
c.Session().Set("nickname", name)
|
||||||
|
|
||||||
if !playerJoined {
|
// Try to join if not already in game
|
||||||
player = &game.Player{
|
if gi.GetPlayerColor(playerID) == 0 {
|
||||||
ID: game.PlayerID(generatePlayerID()),
|
player := &game.Player{
|
||||||
|
ID: playerID,
|
||||||
Nickname: name,
|
Nickname: name,
|
||||||
}
|
}
|
||||||
playerJoined = gi.Join(&game.PlayerSession{
|
gi.Join(&game.PlayerSession{
|
||||||
Player: player,
|
Player: player,
|
||||||
Sync: c,
|
Sync: c,
|
||||||
})
|
})
|
||||||
@@ -99,20 +94,25 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
dropPiece := c.Action(func() {
|
dropPiece := c.Action(func() {
|
||||||
if gi == nil || player == nil {
|
if gi == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
myColor := gi.GetPlayerColor(playerID)
|
||||||
|
if myColor == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
col := colSignal.Int()
|
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 nickname exists in session and game exists, join immediately
|
||||||
if gameExists && sessionNickname != "" {
|
if gameExists && sessionNickname != "" && gi.GetPlayerColor(playerID) == 0 {
|
||||||
player = &game.Player{
|
player := &game.Player{
|
||||||
ID: game.PlayerID(generatePlayerID()),
|
ID: playerID,
|
||||||
Nickname: sessionNickname,
|
Nickname: sessionNickname,
|
||||||
}
|
}
|
||||||
playerJoined = gi.Join(&game.PlayerSession{
|
gi.Join(&game.PlayerSession{
|
||||||
Player: player,
|
Player: player,
|
||||||
Sync: c,
|
Sync: c,
|
||||||
})
|
})
|
||||||
@@ -125,8 +125,10 @@ func main() {
|
|||||||
return h.Div()
|
return h.Div()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need nickname first
|
myColor := gi.GetPlayerColor(playerID)
|
||||||
if !playerJoined {
|
|
||||||
|
// Need nickname first / not joined yet
|
||||||
|
if myColor == 0 {
|
||||||
return ui.NicknamePrompt(
|
return ui.NicknamePrompt(
|
||||||
nickname.Bind(),
|
nickname.Bind(),
|
||||||
setNickname.OnKeyDown("Enter"),
|
setNickname.OnKeyDown("Enter"),
|
||||||
@@ -135,7 +137,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g := gi.GetGame()
|
g := gi.GetGame()
|
||||||
myColor := player.Color
|
|
||||||
|
|
||||||
// Create column click function
|
// Create column click function
|
||||||
columnClick := func(col int) h.H {
|
columnClick := func(col int) h.H {
|
||||||
@@ -164,12 +165,6 @@ func main() {
|
|||||||
v.Start()
|
v.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func generatePlayerID() string {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
rand.Read(b)
|
|
||||||
return hex.EncodeToString(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
const gameCSS = `
|
const gameCSS = `
|
||||||
body { margin: 0; }
|
body { margin: 0; }
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/ryanhamamura/via/h"
|
"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"),
|
return h.Main(h.Class("container"),
|
||||||
h.Div(h.Class("lobby"),
|
h.Div(h.Class("lobby"),
|
||||||
h.H1(h.Text("Connect 4")),
|
h.H1(h.Text("Connect 4")),
|
||||||
@@ -18,7 +18,7 @@ func LobbyView(nicknameBind, setNicknameKeyDown, createGameClick h.H) h.H {
|
|||||||
h.Placeholder("Enter your nickname"),
|
h.Placeholder("Enter your nickname"),
|
||||||
nicknameBind,
|
nicknameBind,
|
||||||
h.Attr("required"),
|
h.Attr("required"),
|
||||||
setNicknameKeyDown,
|
createGameKeyDown,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h.Button(
|
h.Button(
|
||||||
|
|||||||
Reference in New Issue
Block a user