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 }