]> Skullheadx's Git Forge - monopoly-web.git/commitdiff
lobby backend
authorSkullheadx <admonty1@protonmail.com>
Mon, 8 Jun 2026 02:00:47 +0000 (22:00 -0400)
committerSkullheadx <admonty1@protonmail.com>
Mon, 8 Jun 2026 02:00:47 +0000 (22:00 -0400)
game/config.go
game/game.go
go.mod
go.sum
main.go

index 5644f4fa04fee9f215ee987501a6a22b58cc65fa..b83175b8e306469f9fcecbddb5ff2bd48b184eb5 100644 (file)
@@ -1,5 +1,10 @@
 package game
 
+const (
+       MSG_START uint8 = iota
+       MSG_END
+)
+
 const (
        StartingDiceRolls int32 = 1
 )
index 56a05029a9090a81cdf3a766248cad06bd9a3e32..1da75b3ac36f024cadfe4758461f021682caa5d6 100644 (file)
@@ -4,6 +4,7 @@ import (
        "context"
        "errors"
        "github.com/coder/websocket"
+       "github.com/google/uuid"
        "golang.org/x/time/rate"
        "io"
        "log"
@@ -14,6 +15,189 @@ import (
        "time"
 )
 
+type MonopolyServer struct {
+       subscriberMessageBuffer int
+       publishLimiter          *rate.Limiter
+
+       logf func(f string, v ...any)
+
+       serveMux http.ServeMux
+
+       subscribersMu sync.Mutex
+       subscribers   map[*subscriber]string
+
+       gameCtxMu sync.Mutex
+       gameCtx   *Context
+       randSeed  *rand.PCG
+}
+
+func NewMonopolyServer() *MonopolyServer {
+       ms := &MonopolyServer{
+               subscriberMessageBuffer: 16,
+               logf:                    log.Printf,
+               subscribers:             make(map[*subscriber]string),
+               publishLimiter:          rate.NewLimiter(rate.Every(time.Millisecond*100), 8),
+               gameCtx:                 nil,
+               randSeed:                rand.NewPCG(20, 26),
+       }
+       ms.serveMux.Handle("/", http.FileServer(http.Dir("../public/")))
+       ms.serveMux.HandleFunc("/subscribe", ms.subscribeHandler)
+       ms.serveMux.HandleFunc("/publish", ms.publishHandler)
+
+       return ms
+}
+
+type subscriber struct {
+       msgs      chan []byte
+       closeSlow func()
+}
+
+func (ms *MonopolyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+       ms.serveMux.ServeHTTP(w, r)
+}
+
+func (ms *MonopolyServer) subscribeHandler(w http.ResponseWriter, r *http.Request) {
+       err := ms.subscribe(w, r)
+       if errors.Is(err, context.Canceled) {
+               return
+       }
+
+       if websocket.CloseStatus(err) == websocket.StatusNormalClosure || websocket.CloseStatus(err) == websocket.StatusGoingAway {
+               return
+       }
+
+       if err != nil {
+               ms.logf("%v", err)
+               return
+       }
+}
+
+func (ms *MonopolyServer) publishHandler(w http.ResponseWriter, r *http.Request) {
+       if r.Method != "POST" {
+               http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
+               return
+       }
+
+       body := http.MaxBytesReader(w, r.Body, 8192)
+       msg, err := io.ReadAll(body)
+       if err != nil {
+               http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge)
+               return
+       }
+
+       ms.publish(msg)
+       w.WriteHeader(http.StatusAccepted)
+}
+
+func (ms *MonopolyServer) subscribe(w http.ResponseWriter, r *http.Request) error {
+       var mu sync.Mutex
+       var c *websocket.Conn
+       var closed bool
+
+       s := &subscriber{
+               msgs: make(chan []byte, ms.subscriberMessageBuffer),
+               closeSlow: func() {
+                       mu.Lock()
+                       defer mu.Unlock()
+                       closed = true
+                       if c != nil {
+                               c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages")
+                       }
+               },
+       }
+       ms.addSubscriber(s, uuid.NewString())
+       defer ms.deleteSubscriber(s)
+
+       c2, err := websocket.Accept(w, r, nil)
+       if err != nil {
+               return err
+       }
+
+       mu.Lock()
+       if closed {
+               mu.Unlock()
+               return net.ErrClosed
+       }
+
+       c = c2
+       mu.Unlock()
+       defer c.CloseNow()
+
+       ctx := c.CloseRead(context.Background())
+       for {
+               select {
+               case msg := <-s.msgs:
+                       err := writeTimeout(ctx, time.Second*5, c, msg)
+                       if err != nil {
+                               return err
+                       }
+               case <-ctx.Done():
+                       return ctx.Err()
+               }
+       }
+}
+
+func (ms *MonopolyServer) startGame() {
+       ms.subscribersMu.Lock()
+       defer ms.subscribersMu.Unlock()
+
+       ms.publishLimiter.Wait(context.Background())
+
+       var players []Player
+       for _, uuid := range ms.subscribers {
+               players = append(players, InitPlayer(uuid))
+       }
+
+       ms.gameCtxMu.Lock()
+       ms.gameCtx = InitCtx(ms.randSeed, players)
+       ms.gameCtxMu.Unlock()
+
+       msg := []byte{MSG_START}
+       for s := range ms.subscribers {
+               select {
+               case s.msgs <- msg:
+               default:
+                       go s.closeSlow()
+               }
+       }
+
+}
+
+func (ms *MonopolyServer) publish(msg []byte) {
+       ms.subscribersMu.Lock()
+       defer ms.subscribersMu.Unlock()
+
+       ms.publishLimiter.Wait(context.Background())
+
+       for s := range ms.subscribers {
+               select {
+               case s.msgs <- msg:
+               default:
+                       go s.closeSlow()
+               }
+       }
+
+}
+
+func (ms *MonopolyServer) addSubscriber(s *subscriber, uuid string) {
+       ms.subscribersMu.Lock()
+       ms.subscribers[s] = uuid
+       ms.subscribersMu.Unlock()
+}
+
+func (ms *MonopolyServer) deleteSubscriber(s *subscriber) {
+       ms.subscribersMu.Lock()
+       delete(ms.subscribers, s)
+       ms.subscribersMu.Unlock()
+}
+
+func writeTimeout(ctx context.Context, timeout time.Duration, c *websocket.Conn, msg []byte) error {
+       ctx, cancel := context.WithTimeout(ctx, timeout)
+       defer cancel()
+
+       return c.Write(ctx, websocket.MessageText, msg)
+}
+
 func initTurn(pID PlayerID, inJail bool) Turn {
 
        eS := []EventType{EventEndTurn}
@@ -87,9 +271,9 @@ func InitCtx(randSeed rand.Source, players []Player) *Context {
        }
 }
 
-func InitPlayer() Player {
+func InitPlayer(uuid string) Player {
        return Player{
-               UUID:              "abc", // TODO: Generate proper UUID
+               UUID:              uuid,
                Money:             StartingMoney,
                CurrentSpaceID:    SpecialSpaces.Go,
                GetOutOfJailCards: StartingGetOutOfJailFreeCards,
@@ -214,153 +398,3 @@ func (ctx *Context) getPropID(spaceID SpaceID) PropertyID {
        }
        panic("Space is not an ownable property")
 }
-
-type MonopolyServer struct {
-       subscriberMessageBuffer int
-       publishLimiter          *rate.Limiter
-
-       logf func(f string, v ...any)
-
-       serveMux http.ServeMux
-
-       subscribersMu sync.Mutex
-       subscribers   map[*subscriber]struct{}
-}
-
-func NewMonopolyServer() *MonopolyServer {
-       ms := &MonopolyServer{
-               subscriberMessageBuffer: 16,
-               logf:                    log.Printf,
-               subscribers:             make(map[*subscriber]struct{}),
-               publishLimiter:          rate.NewLimiter(rate.Every(time.Millisecond*100), 8),
-       }
-       ms.serveMux.Handle("/", http.FileServer(http.Dir("../public/")))
-       ms.serveMux.HandleFunc("/subscribe", ms.subscribeHandler)
-       ms.serveMux.HandleFunc("/publish", ms.publishHandler)
-
-       return ms
-}
-
-type subscriber struct {
-       msgs      chan []byte
-       closeSlow func()
-}
-
-func (ms *MonopolyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-       ms.serveMux.ServeHTTP(w, r)
-}
-
-func (ms *MonopolyServer) subscribeHandler(w http.ResponseWriter, r *http.Request) {
-       err := ms.subscribe(w, r)
-       if errors.Is(err, context.Canceled) {
-               return
-       }
-
-       if websocket.CloseStatus(err) == websocket.StatusNormalClosure || websocket.CloseStatus(err) == websocket.StatusGoingAway {
-               return
-       }
-
-       if err != nil {
-               ms.logf("%v", err)
-               return
-       }
-}
-
-func (ms *MonopolyServer) publishHandler(w http.ResponseWriter, r *http.Request) {
-       if r.Method != "POST" {
-               http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
-               return
-       }
-
-       body := http.MaxBytesReader(w, r.Body, 8192)
-       msg, err := io.ReadAll(body)
-       if err != nil {
-               http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge)
-               return
-       }
-
-       ms.publish(msg)
-       w.WriteHeader(http.StatusAccepted)
-}
-
-func (ms *MonopolyServer) subscribe(w http.ResponseWriter, r *http.Request) error {
-       var mu sync.Mutex
-       var c *websocket.Conn
-       var closed bool
-       s := &subscriber{
-               msgs: make(chan []byte, ms.subscriberMessageBuffer),
-               closeSlow: func() {
-                       mu.Lock()
-                       defer mu.Unlock()
-                       closed = true
-                       if c != nil {
-                               c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages")
-                       }
-               },
-       }
-       ms.addSubscriber(s)
-       defer ms.deleteSubscriber(s)
-
-       c2, err := websocket.Accept(w, r, nil)
-       if err != nil {
-               return err
-       }
-
-       mu.Lock()
-       if closed {
-               mu.Unlock()
-               return net.ErrClosed
-       }
-
-       c = c2
-       mu.Unlock()
-       defer c.CloseNow()
-
-       ctx := c.CloseRead(context.Background())
-       for {
-               select {
-               case msg := <-s.msgs:
-                       err := writeTimeout(ctx, time.Second*5, c, msg)
-                       if err != nil {
-                               return err
-                       }
-               case <-ctx.Done():
-                       return ctx.Err()
-               }
-       }
-}
-
-func (ms *MonopolyServer) publish(msg []byte) {
-       ms.subscribersMu.Lock()
-       defer ms.subscribersMu.Unlock()
-
-       ms.publishLimiter.Wait(context.Background())
-
-       for s := range ms.subscribers {
-               select {
-               case s.msgs <- msg:
-               default:
-                       go s.closeSlow()
-               }
-       }
-
-}
-
-func (ms *MonopolyServer) addSubscriber(s *subscriber) {
-       ms.subscribersMu.Lock()
-       ms.subscribers[s] = struct{}{}
-       ms.subscribersMu.Unlock()
-}
-
-func (ms *MonopolyServer) deleteSubscriber(s *subscriber) {
-       ms.subscribersMu.Lock()
-       delete(ms.subscribers, s)
-       ms.subscribersMu.Unlock()
-}
-
-func writeTimeout(ctx context.Context, timeout time.Duration, c *websocket.Conn, msg []byte) error {
-       ctx, cancel := context.WithTimeout(ctx, timeout)
-       defer cancel()
-
-       return c.Write(ctx, websocket.MessageText, msg)
-}
diff --git a/go.mod b/go.mod
index f5d43a31964aa3824ba05a1b83496fb58bf78564..f178e3e15b92bc5a2dfaae37bfe9e845e399e79a 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -6,3 +6,5 @@ require (
        github.com/coder/websocket v1.8.14
        golang.org/x/time v0.15.0
 )
+
+require github.com/google/uuid v1.6.0 // indirect
diff --git a/go.sum b/go.sum
index aa6ad6f0d40a9a1e49d59d7053eede1c2a754e32..dfd6467a58ba9d3ec26a9d57dc4fdccb96473f4e 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,6 @@
 github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
 github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
 golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
diff --git a/main.go b/main.go
index a1f05b6ae69ffdc997d421f6c5ce8a80e3df0812..dbc722c19323fa498d9223687c5d204f32e5ba90 100644 (file)
--- a/main.go
+++ b/main.go
@@ -29,25 +29,6 @@ func initRoom() Room {
        }
 }
 
-// func main() {
-//     fmt.Println("monopoly-web backend")
-//
-//     room := initRoom()
-//
-//     // register routes
-//     http.HandleFunc("/health", healthHandler)
-//     http.HandleFunc("/api/v1/roll", room.rollDiceHandler)
-//     http.HandleFunc("POST /api/v1/turn", room.endTurnHandler)
-//     http.HandleFunc("POST /api/v1/exit-jail", room.exitJailHandler)
-//
-//     // listen and serve
-//     log.Fatal(http.ListenAndServe(":8080", nil))
-// }
-
-func healthHandler(w http.ResponseWriter, req *http.Request) {
-       io.WriteString(w, "Status: healthy\n")
-}
-
 const UUID = "abc" // TODO: UUID in cookie
 
 func (r *Room) rollDiceHandler(w http.ResponseWriter, req *http.Request) {
@@ -115,7 +96,7 @@ func run() error {
        }
        log.Printf("listening on ws://%v", l.Addr())
 
-       cs := newMonopolyServer()
+       cs := game.NewMonopolyServer()
        s := &http.Server{
                Handler:      cs,
                ReadTimeout:  time.Second * 10,