Support spectating matches

Resolves #3.
This commit is contained in:
Trevor Slocum 2023-11-16 16:50:11 -08:00
parent d0f8b45b0f
commit 28ba643bf9
3 changed files with 72 additions and 32 deletions

View file

@ -16,6 +16,7 @@ type serverGame struct {
password []byte
client1 *serverClient
client2 *serverClient
spectators []*serverClient
allowed1 []byte
allowed2 []byte
rematch int
@ -79,6 +80,9 @@ func (g *serverGame) sendBoard(client *serverClient) {
Available: g.LegalMoves(false),
},
}
if g.client1 != client && g.client2 != client {
ev.Spectating = true
}
// Reverse spaces for white.
if client.playerNumber == 2 {
@ -160,11 +164,33 @@ func (g *serverGame) eachClient(f func(client *serverClient)) {
if g.client2 != nil {
f(g.client2)
}
for _, spectator := range g.spectators {
f(spectator)
}
}
func (g *serverGame) addClient(client *serverClient) (bool, string) {
func (g *serverGame) addClient(client *serverClient) (spectator bool) {
if g.allowed1 != nil && !bytes.Equal(client.name, g.allowed1) && !bytes.Equal(client.name, g.allowed2) {
return false, "Match has already started."
spectator = true
} else if g.client1 != nil && g.client2 != nil {
spectator = true
}
if spectator {
for _, spec := range g.spectators {
if spec == client {
return true
}
}
client.playerNumber = 1
g.spectators = append(g.spectators, client)
ev := &bgammon.EventJoined{
GameID: g.id,
PlayerNumber: 1,
}
ev.Player = string(client.name)
client.sendEvent(ev)
g.sendBoard(client)
return spectator
}
var playerNumber int
@ -201,8 +227,6 @@ func (g *serverGame) addClient(client *serverClient) (bool, string) {
}
}()
switch {
case g.client1 != nil && g.client2 != nil:
// Do not assign player number.
case g.client1 != nil:
g.client2 = client
g.Player2.Name = string(client.name)
@ -226,13 +250,7 @@ func (g *serverGame) addClient(client *serverClient) (bool, string) {
playerNumber = 2
}
}
ok := playerNumber != 0
var reason string
if !ok {
reason = "Match is full."
}
return ok, reason
return spectator
}
func (g *serverGame) removeClient(client *serverClient) {
@ -275,6 +293,22 @@ func (g *serverGame) removeClient(client *serverClient) {
g.Player2.Name = ""
playerNumber = 2
default:
for i, spectator := range g.spectators {
if spectator == client {
g.spectators = append(g.spectators[:i], g.spectators[i+1:]...)
ev := &bgammon.EventLeft{}
ev.Player = string(client.name)
client.sendEvent(ev)
if !client.json {
g.sendBoard(client)
}
client.playerNumber = 0
return
}
}
return
}
}

View file

@ -334,6 +334,11 @@ func (s *server) gameByClient(c *serverClient) *serverGame {
if g.client1 == c || g.client2 == c {
return g
}
for _, spec := range g.spectators {
if spec == c {
return g
}
}
}
return nil
}
@ -440,10 +445,8 @@ COMMANDS:
rejoin = g.rejoin2
}
if rejoin {
ok, _ := g.addClient(cmd.client)
if ok {
cmd.client.sendNotice(fmt.Sprintf("Rejoined match: %s", g.name))
}
g.addClient(cmd.client)
cmd.client.sendNotice(fmt.Sprintf("Rejoined match: %s", g.name))
}
}
s.gamesLock.RUnlock()
@ -455,6 +458,15 @@ COMMANDS:
}
clientGame := s.gameByClient(cmd.client)
if clientGame != nil && clientGame.client1 != cmd.client && clientGame.client2 != cmd.client {
switch keyword {
case bgammon.CommandHelp, "h", bgammon.CommandJSON, bgammon.CommandList, "ls", bgammon.CommandBoard, "b", bgammon.CommandLeave, "l", bgammon.CommandDisconnect, bgammon.CommandPong:
// These commands are allowed to be used by spectators.
default:
cmd.client.sendNotice("Command ignored: You are spectating this match.")
continue
}
}
switch keyword {
case bgammon.CommandHelp, "h":
@ -573,10 +585,7 @@ COMMANDS:
g.name = gameName
g.Points = points
g.password = gamePassword
ok, reason := g.addClient(cmd.client)
if !ok {
log.Panicf("failed to add client to newly created game %+v %+v: %s", g, cmd.client, reason)
}
g.addClient(cmd.client)
s.gamesLock.Lock()
s.games = append(s.games, g)
@ -651,15 +660,11 @@ COMMANDS:
s.gamesLock.Unlock()
continue COMMANDS
}
ok, reason := g.addClient(cmd.client)
spectator := g.addClient(cmd.client)
s.gamesLock.Unlock()
if !ok {
cmd.client.sendEvent(&bgammon.EventFailedJoin{
Reason: reason,
})
} else {
cmd.client.sendNotice(fmt.Sprintf("Joined match: %s", g.name))
cmd.client.sendNotice(fmt.Sprintf("Joined match: %s", g.name))
if spectator {
cmd.client.sendNotice("You are spectating this match. Chat messages are not relayed.")
}
continue COMMANDS
}

View file

@ -8,6 +8,7 @@ type GameState struct {
*Game
PlayerNumber int
Available [][]int // Legal moves.
Spectating bool
}
func (g *GameState) OpponentPlayer() Player {
@ -75,7 +76,7 @@ func (g *GameState) Pips(player int) int {
// MayDouble returns whether the player may send the 'double' command.
func (g *GameState) MayDouble() bool {
if g.Winner != 0 {
if g.Spectating || g.Winner != 0 {
return false
}
return g.Points != 1 && g.Turn != 0 && g.Turn == g.PlayerNumber && g.Roll1 == 0 && !g.DoubleOffered && (g.DoublePlayer == 0 || g.DoublePlayer == g.PlayerNumber)
@ -83,7 +84,7 @@ func (g *GameState) MayDouble() bool {
// MayRoll returns whether the player may send the 'roll' command.
func (g *GameState) MayRoll() bool {
if g.Winner != 0 || g.DoubleOffered {
if g.Spectating || g.Winner != 0 || g.DoubleOffered {
return false
}
switch g.Turn {
@ -106,7 +107,7 @@ func (g *GameState) MayRoll() bool {
// MayOK returns whether the player may send the 'ok' command.
func (g *GameState) MayOK() bool {
if g.Winner != 0 {
if g.Spectating || g.Winner != 0 {
return false
} else if g.Turn != 0 && g.Turn != g.PlayerNumber && g.DoubleOffered {
return true
@ -116,7 +117,7 @@ func (g *GameState) MayOK() bool {
// MayResign returns whether the player may send the 'resign' command.
func (g *GameState) MayResign() bool {
if g.Winner != 0 {
if g.Spectating || g.Winner != 0 {
return false
}
return g.Turn != 0 && g.Turn != g.PlayerNumber && g.DoubleOffered
@ -124,7 +125,7 @@ func (g *GameState) MayResign() bool {
// MayReset returns whether the player may send the 'reset' command.
func (g *GameState) MayReset() bool {
if g.Winner != 0 {
if g.Spectating || g.Winner != 0 {
return false
}
return g.Turn != 0 && g.Turn == g.PlayerNumber && len(g.Moves) > 0