"context"
"errors"
"github.com/coder/websocket"
+ "github.com/google/uuid"
"golang.org/x/time/rate"
"io"
"log"
"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}
}
}
-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,
}
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)
-}