add game actor
This commit is contained in:
parent
e8786073aa
commit
cc2b54eac3
96
game.go
Normal file
96
game.go
Normal file
@ -0,0 +1,96 @@
|
||||
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")
|
Loading…
Reference in New Issue
Block a user