Replace polling loop with NATS pub/sub for game updates
Use via's embedded NATS server to notify players of state changes instead of a 100ms polling ticker. Each player subscribes to "game.<id>" on page load; via auto-cleans subscriptions on disconnect, eliminating the need for manual player tracking and RegisterSync.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
c4
|
c4
|
||||||
c4.db
|
c4.db
|
||||||
|
data/
|
||||||
|
|||||||
108
game/store.go
108
game/store.go
@@ -4,16 +4,14 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Syncable interface {
|
type PubSub interface {
|
||||||
Sync()
|
Publish(subject string, data []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerSession struct {
|
type PlayerSession struct {
|
||||||
Player *Player
|
Player *Player
|
||||||
Sync Syncable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Persister interface {
|
type Persister interface {
|
||||||
@@ -28,6 +26,7 @@ type GameStore struct {
|
|||||||
games map[string]*GameInstance
|
games map[string]*GameInstance
|
||||||
gamesMu sync.RWMutex
|
gamesMu sync.RWMutex
|
||||||
persister Persister
|
persister Persister
|
||||||
|
pubsub PubSub
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGameStore() *GameStore {
|
func NewGameStore() *GameStore {
|
||||||
@@ -40,10 +39,23 @@ func (gs *GameStore) SetPersister(p Persister) {
|
|||||||
gs.persister = p
|
gs.persister = p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gs *GameStore) SetPubSub(ps PubSub) {
|
||||||
|
gs.pubsub = ps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs *GameStore) makeNotify(gameID string) func() {
|
||||||
|
return func() {
|
||||||
|
if gs.pubsub != nil {
|
||||||
|
gs.pubsub.Publish("game."+gameID, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (gs *GameStore) Create() *GameInstance {
|
func (gs *GameStore) Create() *GameInstance {
|
||||||
id := GenerateID(4)
|
id := GenerateID(4)
|
||||||
gi := NewGameInstance(id)
|
gi := NewGameInstance(id)
|
||||||
gi.persister = gs.persister
|
gi.persister = gs.persister
|
||||||
|
gi.notify = gs.makeNotify(id)
|
||||||
gs.gamesMu.Lock()
|
gs.gamesMu.Lock()
|
||||||
gs.games[id] = gi
|
gs.games[id] = gi
|
||||||
gs.gamesMu.Unlock()
|
gs.gamesMu.Unlock()
|
||||||
@@ -52,7 +64,6 @@ func (gs *GameStore) Create() *GameInstance {
|
|||||||
gs.persister.SaveGame(gi.game)
|
gs.persister.SaveGame(gi.game)
|
||||||
}
|
}
|
||||||
|
|
||||||
go gi.run()
|
|
||||||
return gi
|
return gi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +76,6 @@ func (gs *GameStore) Get(id string) (*GameInstance, bool) {
|
|||||||
return gi, true
|
return gi, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load from database
|
|
||||||
if gs.persister == nil {
|
if gs.persister == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -86,27 +96,20 @@ func (gs *GameStore) Get(id string) (*GameInstance, bool) {
|
|||||||
|
|
||||||
gi = &GameInstance{
|
gi = &GameInstance{
|
||||||
game: game,
|
game: game,
|
||||||
players: make(map[PlayerID]Syncable),
|
|
||||||
leave: make(chan PlayerID, 5),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
persister: gs.persister,
|
persister: gs.persister,
|
||||||
|
notify: gs.makeNotify(id),
|
||||||
}
|
}
|
||||||
|
|
||||||
gs.gamesMu.Lock()
|
gs.gamesMu.Lock()
|
||||||
gs.games[id] = gi
|
gs.games[id] = gi
|
||||||
gs.gamesMu.Unlock()
|
gs.gamesMu.Unlock()
|
||||||
|
|
||||||
go gi.run()
|
|
||||||
return gi, true
|
return gi, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gs *GameStore) Delete(id string) error {
|
func (gs *GameStore) Delete(id string) error {
|
||||||
gs.gamesMu.Lock()
|
gs.gamesMu.Lock()
|
||||||
gi, ok := gs.games[id]
|
delete(gs.games, id)
|
||||||
if ok {
|
|
||||||
delete(gs.games, id)
|
|
||||||
close(gi.done)
|
|
||||||
}
|
|
||||||
gs.gamesMu.Unlock()
|
gs.gamesMu.Unlock()
|
||||||
|
|
||||||
if gs.persister != nil {
|
if gs.persister != nil {
|
||||||
@@ -124,20 +127,14 @@ func GenerateID(size int) string {
|
|||||||
type GameInstance struct {
|
type GameInstance struct {
|
||||||
game *Game
|
game *Game
|
||||||
gameMu sync.RWMutex
|
gameMu sync.RWMutex
|
||||||
players map[PlayerID]Syncable
|
notify func()
|
||||||
playersMu sync.RWMutex
|
|
||||||
leave chan PlayerID
|
|
||||||
done chan struct{}
|
|
||||||
dirty bool
|
|
||||||
persister Persister
|
persister Persister
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGameInstance(id string) *GameInstance {
|
func NewGameInstance(id string) *GameInstance {
|
||||||
return &GameInstance{
|
return &GameInstance{
|
||||||
game: NewGame(id),
|
game: NewGame(id),
|
||||||
players: make(map[PlayerID]Syncable),
|
notify: func() {},
|
||||||
leave: make(chan PlayerID, 5),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,30 +149,25 @@ func (gi *GameInstance) Join(ps *PlayerSession) bool {
|
|||||||
defer gi.gameMu.Unlock()
|
defer gi.gameMu.Unlock()
|
||||||
|
|
||||||
var slot int
|
var slot int
|
||||||
// Assign player to an open slot
|
|
||||||
if gi.game.Players[0] == nil {
|
if gi.game.Players[0] == nil {
|
||||||
ps.Player.Color = 1 // Red
|
ps.Player.Color = 1
|
||||||
gi.game.Players[0] = ps.Player
|
gi.game.Players[0] = ps.Player
|
||||||
slot = 0
|
slot = 0
|
||||||
} else if gi.game.Players[1] == nil {
|
} else if gi.game.Players[1] == nil {
|
||||||
ps.Player.Color = 2 // Yellow
|
ps.Player.Color = 2
|
||||||
gi.game.Players[1] = ps.Player
|
gi.game.Players[1] = ps.Player
|
||||||
gi.game.Status = StatusInProgress
|
gi.game.Status = StatusInProgress
|
||||||
slot = 1
|
slot = 1
|
||||||
} else {
|
} else {
|
||||||
return false // Game is full
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
gi.playersMu.Lock()
|
|
||||||
gi.players[ps.Player.ID] = ps.Sync
|
|
||||||
gi.playersMu.Unlock()
|
|
||||||
|
|
||||||
if gi.persister != nil {
|
if gi.persister != nil {
|
||||||
gi.persister.SaveGamePlayer(gi.game.ID, ps.Player, slot)
|
gi.persister.SaveGamePlayer(gi.game.ID, ps.Player, slot)
|
||||||
gi.persister.SaveGame(gi.game)
|
gi.persister.SaveGame(gi.game)
|
||||||
}
|
}
|
||||||
|
|
||||||
gi.dirty = true
|
gi.notify()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,13 +188,6 @@ func (gi *GameInstance) GetPlayerColor(pid PlayerID) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gi *GameInstance) RegisterSync(playerID PlayerID, sync Syncable) {
|
|
||||||
gi.playersMu.Lock()
|
|
||||||
gi.players[playerID] = sync
|
|
||||||
gi.playersMu.Unlock()
|
|
||||||
gi.dirty = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gi *GameInstance) CreateRematch(gs *GameStore) *GameInstance {
|
func (gi *GameInstance) CreateRematch(gs *GameStore) *GameInstance {
|
||||||
gi.gameMu.Lock()
|
gi.gameMu.Lock()
|
||||||
defer gi.gameMu.Unlock()
|
defer gi.gameMu.Unlock()
|
||||||
@@ -223,7 +208,7 @@ func (gi *GameInstance) CreateRematch(gs *GameStore) *GameInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gi.dirty = true
|
gi.notify()
|
||||||
return newGI
|
return newGI
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,45 +238,6 @@ func (gi *GameInstance) DropPiece(col int, playerColor int) bool {
|
|||||||
gi.persister.SaveGame(gi.game)
|
gi.persister.SaveGame(gi.game)
|
||||||
}
|
}
|
||||||
|
|
||||||
gi.dirty = true
|
gi.notify()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gi *GameInstance) run() {
|
|
||||||
ticker := time.NewTicker(100 * time.Millisecond)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case pid := <-gi.leave:
|
|
||||||
gi.playersMu.Lock()
|
|
||||||
delete(gi.players, pid)
|
|
||||||
gi.playersMu.Unlock()
|
|
||||||
case <-ticker.C:
|
|
||||||
gi.publish()
|
|
||||||
case <-gi.done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gi *GameInstance) publish() {
|
|
||||||
gi.gameMu.Lock()
|
|
||||||
if !gi.dirty {
|
|
||||||
gi.gameMu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gi.dirty = false
|
|
||||||
|
|
||||||
gi.playersMu.RLock()
|
|
||||||
syncers := make([]Syncable, 0, len(gi.players))
|
|
||||||
for _, sync := range gi.players {
|
|
||||||
syncers = append(syncers, sync)
|
|
||||||
}
|
|
||||||
gi.playersMu.RUnlock()
|
|
||||||
gi.gameMu.Unlock()
|
|
||||||
|
|
||||||
for _, sync := range syncers {
|
|
||||||
sync.Sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
15
go.mod
15
go.mod
@@ -15,12 +15,22 @@ require (
|
|||||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de // indirect
|
github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de // 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/antithesishq/antithesis-sdk-go v0.5.0 // indirect
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||||
|
github.com/delaneyj/toolbelt v0.9.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/google/go-tpm v0.9.7 // indirect
|
||||||
github.com/hookenz/gotailwind/v4 v4.1.18 // indirect
|
github.com/hookenz/gotailwind/v4 v4.1.18 // indirect
|
||||||
github.com/klauspost/compress v1.18.2 // indirect
|
github.com/klauspost/compress v1.18.2 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||||
|
github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect
|
||||||
|
github.com/nats-io/jwt/v2 v2.8.0 // indirect
|
||||||
|
github.com/nats-io/nats-server/v2 v2.12.2 // indirect
|
||||||
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rs/zerolog v1.34.0 // indirect
|
github.com/rs/zerolog v1.34.0 // indirect
|
||||||
@@ -28,9 +38,10 @@ require (
|
|||||||
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
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
|
golang.org/x/time v0.14.0 // indirect
|
||||||
maragu.dev/gomponents v1.2.0 // indirect
|
maragu.dev/gomponents v1.2.0 // indirect
|
||||||
modernc.org/libc v1.67.4 // indirect
|
modernc.org/libc v1.67.4 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
|||||||
39
go.sum
39
go.sum
@@ -7,16 +7,24 @@ github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gv
|
|||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
|
github.com/antithesishq/antithesis-sdk-go v0.5.0 h1:cudCFF83pDDANcXFzkQPUHHedfnnIbUO3JMr9fqwFJs=
|
||||||
|
github.com/antithesishq/antithesis-sdk-go v0.5.0/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/delaneyj/toolbelt v0.9.1 h1:QJComn2qoaQ4azl5uRkGpdHSO9e+JtoxDTXCiQHvH8o=
|
||||||
|
github.com/delaneyj/toolbelt v0.9.1/go.mod h1:eNXpPuThjTD4tpRNCBl4JEz9jdg9LpyzNuyG+stnIbs=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
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/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA=
|
||||||
|
github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -40,6 +48,18 @@ github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuE
|
|||||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||||
|
github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk=
|
||||||
|
github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
||||||
|
github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
|
||||||
|
github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
|
||||||
|
github.com/nats-io/nats-server/v2 v2.12.2 h1:4TEQd0Y4zvcW0IsVxjlXnRso1hBkQl3TS0BI+SxgPhE=
|
||||||
|
github.com/nats-io/nats-server/v2 v2.12.2/go.mod h1:j1AAttYeu7WnvD8HLJ+WWKNMSyxsqmZ160pNtCQRMyE=
|
||||||
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
@@ -81,19 +101,22 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
|
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||||
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
19
main.go
19
main.go
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/ryanhamamura/c4/ui"
|
"github.com/ryanhamamura/c4/ui"
|
||||||
"github.com/ryanhamamura/via"
|
"github.com/ryanhamamura/via"
|
||||||
"github.com/ryanhamamura/via/h"
|
"github.com/ryanhamamura/via/h"
|
||||||
|
"github.com/ryanhamamura/via/vianats"
|
||||||
)
|
)
|
||||||
|
|
||||||
var store = game.NewGameStore()
|
var store = game.NewGameStore()
|
||||||
@@ -43,12 +44,20 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ns, err := vianats.New(ctx, "./data/nats")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
store.SetPubSub(ns)
|
||||||
|
|
||||||
v := via.New()
|
v := via.New()
|
||||||
v.Config(via.Options{
|
v.Config(via.Options{
|
||||||
LogLevel: via.LogLevelDebug,
|
LogLevel: via.LogLevelDebug,
|
||||||
DocumentTitle: "Connect 4",
|
DocumentTitle: "Connect 4",
|
||||||
ServerAddress: ":7331",
|
ServerAddress: ":7331",
|
||||||
SessionManager: sessionManager,
|
SessionManager: sessionManager,
|
||||||
|
PubSub: ns,
|
||||||
Plugins: []via.Plugin{DaisyUIPlugin},
|
Plugins: []via.Plugin{DaisyUIPlugin},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -307,7 +316,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
gi.Join(&game.PlayerSession{
|
gi.Join(&game.PlayerSession{
|
||||||
Player: player,
|
Player: player,
|
||||||
Sync: c,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
c.Sync()
|
c.Sync()
|
||||||
@@ -336,6 +344,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Subscribe to game updates so the opponent's moves trigger a re-render
|
||||||
|
if gameExists {
|
||||||
|
c.Subscribe("game."+gameID, func(data []byte) { 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 != "" && gi.GetPlayerColor(playerID) == 0 {
|
if gameExists && sessionNickname != "" && gi.GetPlayerColor(playerID) == 0 {
|
||||||
player := &game.Player{
|
player := &game.Player{
|
||||||
@@ -347,11 +360,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
gi.Join(&game.PlayerSession{
|
gi.Join(&game.PlayerSession{
|
||||||
Player: player,
|
Player: player,
|
||||||
Sync: c,
|
|
||||||
})
|
})
|
||||||
} else if gameExists && gi.GetPlayerColor(playerID) != 0 {
|
|
||||||
// Re-register sync context for existing players on reconnect
|
|
||||||
gi.RegisterSync(playerID, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.View(func() h.H {
|
c.View(func() h.H {
|
||||||
|
|||||||
Reference in New Issue
Block a user