parent
ae1cf18d6d
commit
566de4066b
33
game.go
33
game.go
@ -68,7 +68,10 @@ func gameActor(ctx context.Context, g *game.Match, dealer, chall person, join <-
|
|||||||
go playerActor(ctx, chall, actions)
|
go playerActor(ctx, chall, actions)
|
||||||
|
|
||||||
slog.InfoContext(ctx, "start game", "dealer", dealer.id, "challenger", chall.id)
|
slog.InfoContext(ctx, "start game", "dealer", dealer.id, "challenger", chall.id)
|
||||||
broadcast(ctx, g, dealer, chall, obs)
|
const timeLimit = 15 * time.Second
|
||||||
|
dl := time.Now().Add(timeLimit)
|
||||||
|
broadcast(ctx, g, dealer, chall, obs, dl)
|
||||||
|
tick := time.NewTicker(timeLimit)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@ -76,7 +79,7 @@ func gameActor(ctx context.Context, g *game.Match, dealer, chall person, join <-
|
|||||||
case p := <-join:
|
case p := <-join:
|
||||||
slog.InfoContext(ctx, "observer", "id", p.id)
|
slog.InfoContext(ctx, "observer", "id", p.id)
|
||||||
// Deliver the game state just to the new observer.
|
// Deliver the game state just to the new observer.
|
||||||
if err := wsjson.Write(ctx, p.conn, g.DTO(p.id)); err != nil {
|
if err := wsjson.Write(ctx, p.conn, g.DTO(p.id, dl)); err != nil {
|
||||||
// We don't need to track them as an observer. Just close their
|
// We don't need to track them as an observer. Just close their
|
||||||
// conn and move on.
|
// conn and move on.
|
||||||
go p.conn.Close(websocket.StatusNormalClosure, "looks like you dropped")
|
go p.conn.Close(websocket.StatusNormalClosure, "looks like you dropped")
|
||||||
@ -89,27 +92,35 @@ func gameActor(ctx context.Context, g *game.Match, dealer, chall person, join <-
|
|||||||
err := applyAction(g, a)
|
err := applyAction(g, a)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
broadcast(ctx, g, dealer, chall, obs)
|
broadcast(ctx, g, dealer, chall, obs, dl)
|
||||||
case game.ErrRoundEnded:
|
case game.ErrRoundEnded:
|
||||||
broadcast(ctx, g, dealer, chall, obs)
|
broadcast(ctx, g, dealer, chall, obs, dl)
|
||||||
if g.MatchWinner() != nil {
|
if g.MatchWinner() != nil {
|
||||||
gameOver(ctx, dealer, chall, obs)
|
gameOver(ctx, dealer, chall, obs)
|
||||||
// TODO(zeph): server needs to know the players are gone
|
// TODO(zeph): server needs to know the players are gone
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.NextRound()
|
g.NextRound()
|
||||||
broadcast(ctx, g, dealer, chall, obs)
|
broadcast(ctx, g, dealer, chall, obs, dl)
|
||||||
case game.ErrGameEnded:
|
case game.ErrGameEnded:
|
||||||
broadcast(ctx, g, dealer, chall, obs)
|
broadcast(ctx, g, dealer, chall, obs, dl)
|
||||||
g.NextGame()
|
g.NextGame()
|
||||||
broadcast(ctx, g, dealer, chall, obs)
|
broadcast(ctx, g, dealer, chall, obs, dl)
|
||||||
case game.ErrWrongTurn:
|
case game.ErrWrongTurn:
|
||||||
slog.WarnContext(ctx, "action on wrong turn", "from", a.Player, "action", a.Action)
|
slog.WarnContext(ctx, "action on wrong turn", "from", a.Player, "action", a.Action)
|
||||||
|
continue
|
||||||
case errWeirdAction:
|
case errWeirdAction:
|
||||||
slog.WarnContext(ctx, "nonsense action", "from", a.Player, "action", a.Action)
|
slog.WarnContext(ctx, "nonsense action", "from", a.Player, "action", a.Action)
|
||||||
|
continue
|
||||||
default:
|
default:
|
||||||
slog.ErrorContext(ctx, "action caused a mystery error", "from", a.Player, "action", a.Action, "err", err.Error())
|
slog.ErrorContext(ctx, "action caused a mystery error", "from", a.Player, "action", a.Action, "err", err.Error())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
dl = time.Now().Add(timeLimit)
|
||||||
|
tick.Reset(timeLimit)
|
||||||
|
case <-tick.C:
|
||||||
|
slog.InfoContext(ctx, "out of time")
|
||||||
|
// TODO(zeph): current player concedes
|
||||||
}
|
}
|
||||||
// Clear observers who have left.
|
// Clear observers who have left.
|
||||||
for k := len(obs) - 1; k >= 0; k-- {
|
for k := len(obs) - 1; k >= 0; k-- {
|
||||||
@ -145,21 +156,21 @@ func playerActor(ctx context.Context, p person, actions chan<- action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func broadcast(ctx context.Context, g *game.Match, dealer, chall person, obs []observer) {
|
func broadcast(ctx context.Context, g *game.Match, dealer, chall person, obs []observer, deadline time.Time) {
|
||||||
// TODO(zeph): this probably should return an error or some other signal
|
// TODO(zeph): this probably should return an error or some other signal
|
||||||
// if a player drops so that the actor knows to quit
|
// if a player drops so that the actor knows to quit
|
||||||
if err := wsjson.Write(ctx, dealer.conn, g.DTO(dealer.id)); err != nil {
|
if err := wsjson.Write(ctx, dealer.conn, g.DTO(dealer.id, deadline)); err != nil {
|
||||||
// TODO(zeph): concede, but we need to be careful not to recurse
|
// TODO(zeph): concede, but we need to be careful not to recurse
|
||||||
slog.WarnContext(ctx, "lost dealer", "player", dealer.id, "err", err.Error())
|
slog.WarnContext(ctx, "lost dealer", "player", dealer.id, "err", err.Error())
|
||||||
}
|
}
|
||||||
if err := wsjson.Write(ctx, chall.conn, g.DTO(chall.id)); err != nil {
|
if err := wsjson.Write(ctx, chall.conn, g.DTO(chall.id, deadline)); err != nil {
|
||||||
// TODO(zeph): concede, but we need to be careful not to recurse
|
// TODO(zeph): concede, but we need to be careful not to recurse
|
||||||
slog.WarnContext(ctx, "lost challenger", "player", chall.id, "err", err.Error())
|
slog.WarnContext(ctx, "lost challenger", "player", chall.id, "err", err.Error())
|
||||||
}
|
}
|
||||||
if len(obs) == 0 {
|
if len(obs) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d := g.DTO(player.ID{})
|
d := g.DTO(player.ID{}, deadline)
|
||||||
for _, p := range obs {
|
for _, p := range obs {
|
||||||
if err := wsjson.Write(ctx, p.conn, &d); err != nil {
|
if err := wsjson.Write(ctx, p.conn, &d); err != nil {
|
||||||
slog.WarnContext(ctx, "lost observer", "err", err.Error())
|
slog.WarnContext(ctx, "lost observer", "err", err.Error())
|
||||||
|
@ -11,6 +11,7 @@ package game
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.sunturtle.xyz/studio/shotgun/player"
|
"git.sunturtle.xyz/studio/shotgun/player"
|
||||||
"git.sunturtle.xyz/studio/shotgun/serve"
|
"git.sunturtle.xyz/studio/shotgun/serve"
|
||||||
@ -239,8 +240,9 @@ func (g *Match) Concede(id player.ID) error {
|
|||||||
return ErrRoundEnded
|
return ErrRoundEnded
|
||||||
}
|
}
|
||||||
|
|
||||||
// DTO returns the current match state as viewed by the given player.
|
// DTO returns the current match state as viewed by the given player and with
|
||||||
func (g *Match) DTO(id player.ID) serve.Game {
|
// the given deadline on the current player's next move.
|
||||||
|
func (g *Match) DTO(id player.ID, deadline time.Time) serve.Game {
|
||||||
var live, blank int
|
var live, blank int
|
||||||
if g.action.gameStart() {
|
if g.action.gameStart() {
|
||||||
live = len(g.shells) / 2
|
live = len(g.shells) / 2
|
||||||
@ -265,6 +267,7 @@ func (g *Match) DTO(id player.ID) serve.Game {
|
|||||||
Damage: g.damage,
|
Damage: g.damage,
|
||||||
Shell: g.Peek(id),
|
Shell: g.Peek(id),
|
||||||
Previous: g.prev,
|
Previous: g.prev,
|
||||||
|
Deadline: time.Now().UnixMilli(),
|
||||||
Live: live,
|
Live: live,
|
||||||
Blank: blank,
|
Blank: blank,
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package game
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
@ -455,13 +456,13 @@ func TestGameDTO(t *testing.T) {
|
|||||||
Live: len(g.shells) / 2,
|
Live: len(g.shells) / 2,
|
||||||
Blank: (len(g.shells) + 1) / 2,
|
Blank: (len(g.shells) + 1) / 2,
|
||||||
}
|
}
|
||||||
if got := g.DTO(dealer); want != got {
|
if got := g.DTO(dealer, time.UnixMilli(0)); want != got {
|
||||||
t.Errorf("dealer sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
t.Errorf("dealer sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
||||||
}
|
}
|
||||||
if got := g.DTO(chall); want != got {
|
if got := g.DTO(chall, time.UnixMilli(0)); want != got {
|
||||||
t.Errorf("challenger sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
t.Errorf("challenger sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
||||||
}
|
}
|
||||||
if got := g.DTO(player.ID{}); want != got {
|
if got := g.DTO(player.ID{}, time.UnixMilli(0)); want != got {
|
||||||
t.Errorf("observer sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
t.Errorf("observer sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -481,13 +482,13 @@ func TestGameDTO(t *testing.T) {
|
|||||||
Live: 0,
|
Live: 0,
|
||||||
Blank: 0,
|
Blank: 0,
|
||||||
}
|
}
|
||||||
if got := g.DTO(dealer); want != got {
|
if got := g.DTO(dealer, time.UnixMilli(0)); want != got {
|
||||||
t.Errorf("dealer sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
t.Errorf("dealer sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
||||||
}
|
}
|
||||||
if got := g.DTO(chall); want != got {
|
if got := g.DTO(chall, time.UnixMilli(0)); want != got {
|
||||||
t.Errorf("challenger sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
t.Errorf("challenger sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
||||||
}
|
}
|
||||||
if got := g.DTO(player.ID{}); want != got {
|
if got := g.DTO(player.ID{}, time.UnixMilli(0)); want != got {
|
||||||
t.Errorf("observer sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
t.Errorf("observer sees the wrong thing:\nwant %+v\ngot %+v", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,9 @@ type Game struct {
|
|||||||
Shell *bool `json:"shell,omitempty"`
|
Shell *bool `json:"shell,omitempty"`
|
||||||
// Previous gives whether the previously discharged shell was live.
|
// Previous gives whether the previously discharged shell was live.
|
||||||
Previous *bool `json:"previous"`
|
Previous *bool `json:"previous"`
|
||||||
|
// Deadline is the deadline on the current player's move in milliseconds
|
||||||
|
// since the Unix epoch.
|
||||||
|
Deadline int64 `json:"deadline"`
|
||||||
// Live is the number of live shells this round, if it is the first turn
|
// Live is the number of live shells this round, if it is the first turn
|
||||||
// of the round.
|
// of the round.
|
||||||
Live int `json:"live,omitempty"`
|
Live int `json:"live,omitempty"`
|
||||||
|
Loading…
Reference in New Issue
Block a user