package main import ( "context" "errors" "time" "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" "git.sunturtle.xyz/studio/shotgun/game" "git.sunturtle.xyz/studio/shotgun/player" ) // An action is a request to change the game state. type action struct { Action string `json:"action"` Param string `json:"param"` Player player.ID `json:"-"` } // gameActor is an actor that updates a game's state and relays changes to all // observers. func gameActor(ctx context.Context, g *game.Game, dealer, chall person, join chan person) { // Games should generally be on the order of minutes. A four hour game is // definitely expired. ctx, stop := context.WithTimeoutCause(ctx, 4*time.Hour, errGameExpired) defer stop() var obs []person actions := make(chan action, 2) go playerActor(ctx, dealer, actions) go playerActor(ctx, chall, actions) // TODO(zeph): send round start info for { select { case <-ctx.Done(): return case p := <-join: // Deliver the game state just to the new observer. if err := wsjson.Write(ctx, p.conn, g.DTO(p.id)); err != nil { // We don't need to track them as an observer. Just close their // conn and move on. go p.conn.Close(websocket.StatusNormalClosure, "looks like you dropped") continue } obs = append(obs, p) // TODO(zeph): CloseRead returns a context we can use to drop the // observer when they disconnect p.conn.CloseRead(ctx) case a := <-actions: // TODO(zeph): transform the action into a game state change _ = a broadcast(ctx, g, dealer, chall, obs) } } } func playerActor(ctx context.Context, p person, actions chan<- action) { for { a := action{Player: p.id} if err := wsjson.Read(ctx, p.conn, &a); err != nil { // If we fail to read, then we consider them to have quit. a.Action = "quit" select { case <-ctx.Done(): case actions <- a: } return } select { case <-ctx.Done(): return case actions <- a: } } } func broadcast(ctx context.Context, g *game.Game, dealer, chall person, obs []person) { // TODO(zeph): this probably should return an error or some other signal // if a player drops so that the actor knows to quit if err := wsjson.Write(ctx, dealer.conn, g.DTO(dealer.id)); err != nil { // TODO(zeph): concede, but we need to be careful not to recurse // TODO(zeph): log } if err := wsjson.Write(ctx, chall.conn, g.DTO(chall.id)); err != nil { // TODO(zeph): concede, but we need to be careful not to recurse // TODO(zeph): log } for _, p := range obs { if err := wsjson.Write(ctx, p.conn, g.DTO(p.id)); err != nil { // TODO(zeph): log } } } var errGameExpired = errors.New("there is a time limit on games please")