diff --git a/game.go b/game.go index bf3b84b..5ecc63c 100644 --- a/game.go +++ b/game.go @@ -16,16 +16,48 @@ import ( // 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:"-"` } +func applyAction(g *game.Match, a action) error { + switch a.Action { + case "quit": + return g.Concede(a.Player) + case "across", "self": + if err := g.Shoot(a.Player, a.Action == "self"); err != nil { + return err + } + if g.RoundWinner() != nil { + return errRoundEnded + } + return nil + case "0": + return g.Apply(a.Player, 0) + case "1": + return g.Apply(a.Player, 1) + case "2": + return g.Apply(a.Player, 2) + case "3": + return g.Apply(a.Player, 3) + case "4": + return g.Apply(a.Player, 4) + case "5": + return g.Apply(a.Player, 5) + case "6": + return g.Apply(a.Player, 6) + case "7": + return g.Apply(a.Player, 7) + default: + return errWeirdAction + } +} + // gameActor is an actor that updates a game's state and relays changes to all // observers. func gameActor(ctx context.Context, g *game.Match, 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) + ctx, stop := context.WithTimeoutCause(ctx, 4*time.Hour, errMatchExpired) defer stop() var obs []person actions := make(chan action, 2) @@ -51,9 +83,27 @@ func gameActor(ctx context.Context, g *game.Match, dealer, chall person, join <- // observer when they disconnect p.conn.CloseRead(ctx) case a := <-actions: - // TODO(zeph): transform the action into a game state change - slog.DebugContext(ctx, "got action", "from", a.Player, "action", a) - broadcast(ctx, g, dealer, chall, obs) + slog.DebugContext(ctx, "got action", "from", a.Player, "action", a.Action) + err := applyAction(g, a) + switch err { + case nil: + broadcast(ctx, g, dealer, chall, obs) + case errRoundEnded: + broadcast(ctx, g, dealer, chall, obs) + if g.MatchWinner() != nil { + gameOver(ctx, dealer, chall, obs) + // TODO(zeph): server needs to know the players are gone + return + } + g.NextRound() + broadcast(ctx, g, dealer, chall, obs) + // TODO(zeph): broadcast shell counts? + case game.ErrWrongTurn: // do nothing + case errWeirdAction: + slog.WarnContext(ctx, "nonsense action", "from", a.Player, "action", a.Action) + default: + slog.ErrorContext(ctx, "action caused a mystery error", "from", a.Player, "action", a.Action, "err", err.Error()) + } } } } @@ -98,4 +148,18 @@ func broadcast(ctx context.Context, g *game.Match, dealer, chall person, obs []p } } -var errGameExpired = errors.New("there is a time limit on games please") +func gameOver(ctx context.Context, dealer, chall person, obs []person) { + // TODO(zeph): need to communicate to the server that these have gone away + go dealer.conn.Close(websocket.StatusNormalClosure, "match ended") + go chall.conn.Close(websocket.StatusNormalClosure, "match ended") + for _, p := range obs { + c := p.conn + go c.Close(websocket.StatusNormalClosure, "match ended") + } +} + +var ( + errRoundEnded = errors.New("someone h*ckin died") + errWeirdAction = errors.New("unknown action") + errMatchExpired = errors.New("there is a time limit on matches please") +)