package chess import ( "context" "log" "math/rand" "mchess_server/api" "mchess_server/types" "github.com/google/uuid" "github.com/samber/lo" "nhooyr.io/websocket" ) type Game struct { id uuid.UUID board Board players []*Player currentTurnPlayer *Player gameState int isBeingHandled bool } const ( Init = iota Prepare PlayerToMove CheckMove CheckPlayerChange ) func NewGame() *Game { var game = Game{ id: uuid.New(), board: newBoard(), gameState: Init, } 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) GetRandomPlayer() *Player { index := rand.Int() % 2 return game.players[index] } func (game Game) GetPlayer2() *Player { return game.players[1] } func (game *Game) prepare() { game.players[0].color = types.White game.players[1].color = types.Black game.players[0].SetDisconnectCallback(game.playerDisconnected) game.players[1].SetDisconnectCallback(game.playerDisconnected) err := game.notifyPlayersAboutGameStart() if err != nil { return } } func (game *Game) StartHandling() { if game.isBeingHandled { return } game.isBeingHandled = true go game.Handle() } func (game *Game) Handle() { defer game.killGame() game.prepare() var receivedMove types.Move var err error for { switch game.gameState { case Init: game.currentTurnPlayer = game.GetPlayer1() game.gameState = Prepare case Prepare: game.prepare() game.gameState = PlayerToMove case PlayerToMove: log.Println("with ", game.currentTurnPlayer.GetPlayerColor(), " 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) 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 } func (game *Game) playerDisconnected(p *Player) { log.Println(string(p.color), " disconnected") playerStillInGame := lo.Filter(game.players, func(player *Player, _ int) bool { return player.color != p.color }) game.players = playerStillInGame } func (game *Game) SetWebsocketConnectionFor(ctx context.Context, p *Player, ws *websocket.Conn) { p.SetWebsocketConnectionAndSendBoardState(ctx, ws, game.board.PGN(), game.board.colorToMove) }