mchess-server/chess/game.go
Marco 26242424ed Implement thread-safe ringbuffer for websocket messages
This implements a ringbuffer that is used to decouple the raw websocket
connection from the messages that the game handler handles.
2023-11-26 21:37:14 +01:00

182 lines
4.1 KiB
Go

package chess
import (
"log"
"mchess_server/api"
"mchess_server/types"
"time"
"github.com/google/uuid"
)
type Game struct {
id uuid.UUID
board Board
players []*Player
currentTurnPlayer *Player
gameState int
}
const (
Init = iota
Prepare
PlayerToMove
CheckMove
CheckPlayerChange
)
func NewGame() *Game {
var game = Game{
id: uuid.New(),
board: newBoard(),
gameState: PlayerToMove,
}
game.board.Init()
return &game
}
func (game Game) GetPlayers() []*Player {
return game.players
}
func (game Game) GetPlayer1() *Player {
return game.players[0]
}
func (game Game) GetPlayer2() *Player {
return game.players[1]
}
func (game *Game) prepare() {
ok := game.waitForWebsocketConnections()
if !ok {
return
}
err := game.notifyPlayersAboutGameStart()
if err != nil {
return
}
}
//CHANGES:
/*
Do not wait for the players websocket connection before the game.
Let Connection do it.
Then here in game handler, just read from Connection (aka messagebuffer) and when data arrives, we read it
Question: how do we handle connection losses, should game handler observe state of connection and send a notification to the other player when one player is disconnected
Question: How would reconnect work in this scenario?
*/
func (game *Game) Handle() {
defer game.killGame()
var receivedMove types.Move
var err error
for {
switch game.gameState {
case Init:
game.currentTurnPlayer = game.GetPlayer1()
case Prepare:
game.prepare()
case PlayerToMove:
log.Println("with player ", game.currentTurnPlayer, " to move")
receivedMove, err = game.currentTurnPlayer.ReadMove()
if err != nil {
log.Println("Error while reading message:", err)
return
}
log.Println("Player ", game.currentTurnPlayer, " moved:\n", receivedMove)
game.gameState = CheckMove
case CheckMove:
valid, ruleViolation := game.board.CheckAndPlay(receivedMove)
if valid {
game.gameState = CheckPlayerChange
} else {
invalidMoveMessage, err := api.GetInvalidMoveMessage(receivedMove, ruleViolation.String())
if err != nil {
log.Println("Error marshalling 'colorDetermined' message for player 1", err)
return
}
game.currentTurnPlayer.writeMessage(invalidMoveMessage)
game.gameState = PlayerToMove
}
case CheckPlayerChange:
if game.currentTurnPlayer.Uuid == game.players[0].Uuid {
game.currentTurnPlayer = game.players[1]
} else {
game.currentTurnPlayer = game.players[0]
}
err := game.broadcastMove(receivedMove)
if err != nil {
log.Println("Error broadcasting move ", err)
return
}
game.gameState = PlayerToMove
}
log.Println("GameState = ", game.gameState)
}
}
func (game *Game) AddPlayersToGame(player *Player) {
game.players = append(game.players, player)
}
func (game *Game) killGame() {
log.Println("Game should be killed")
}
func (game *Game) waitForWebsocketConnections() bool {
timer := time.NewTimer(5 * time.Second)
numberOfConnections := 0
waitingForPlayers := make(chan bool)
go game.GetPlayer1().WaitForWebsocketConnection(waitingForPlayers)
go game.GetPlayer2().WaitForWebsocketConnection(waitingForPlayers)
for numberOfConnections < 2 {
select {
case <-waitingForPlayers:
numberOfConnections++
case <-timer.C:
return false
}
}
return true
}
func (game Game) notifyPlayersAboutGameStart() error {
colorDeterminedPlayer1, err := api.GetColorDeterminedMessage(types.White)
if err != nil {
log.Println("Error marshalling 'colorDetermined' message for player 1", err)
return err
}
colorDeterminedPlayer2, err := api.GetColorDeterminedMessage(types.Black)
if err != nil {
log.Println("Error marshalling 'colorDetermined' message for player 2", err)
return err
}
game.GetPlayer1().writeMessage(colorDeterminedPlayer1)
game.GetPlayer2().writeMessage(colorDeterminedPlayer2)
return nil
}
func (game Game) broadcastMove(move types.Move) error {
err := game.GetPlayer1().SendMoveAndPosition(move, game.board.PGN())
if err != nil {
return err
}
err = game.GetPlayer2().SendMoveAndPosition(move, game.board.PGN())
if err != nil {
return err
}
return nil
}