package chess import ( "errors" "mchess_server/types" "github.com/samber/lo" ) type Position map[types.Coordinate]Piece type Board struct { position Position history []types.Move colorToMove types.ChessColor state types.AdditionalState } func newBoard() Board { return Board{ position: make(Position), history: make([]types.Move, 0), colorToMove: types.White, state: types.AdditionalState{}, } } func (b *Board) Init() { var coord types.Coordinate for i := 1; i <= 8; i++ { coord.Row = 2 coord.Col = i b.position[coord] = Pawn{Color: types.White} coord.Row = 7 coord.Col = i b.position[coord] = Pawn{Color: types.Black} } b.position[types.Coordinate{Row: 1, Col: 1}] = Rook{Color: types.White} b.position[types.Coordinate{Row: 1, Col: 2}] = Knight{Color: types.White} b.position[types.Coordinate{Row: 1, Col: 3}] = Bishop{Color: types.White} b.position[types.Coordinate{Row: 1, Col: 4}] = Queen{Color: types.White} b.position[types.Coordinate{Row: 1, Col: 5}] = King{Color: types.White} b.position[types.Coordinate{Row: 1, Col: 6}] = Bishop{Color: types.White} b.position[types.Coordinate{Row: 1, Col: 7}] = Knight{Color: types.White} b.position[types.Coordinate{Row: 1, Col: 8}] = Rook{Color: types.White} b.position[types.Coordinate{Row: 8, Col: 1}] = Rook{Color: types.Black} b.position[types.Coordinate{Row: 8, Col: 2}] = Knight{Color: types.Black} b.position[types.Coordinate{Row: 8, Col: 3}] = Bishop{Color: types.Black} b.position[types.Coordinate{Row: 8, Col: 4}] = Queen{Color: types.Black} b.position[types.Coordinate{Row: 8, Col: 5}] = King{Color: types.Black} b.position[types.Coordinate{Row: 8, Col: 6}] = Bishop{Color: types.Black} b.position[types.Coordinate{Row: 8, Col: 7}] = Knight{Color: types.Black} b.position[types.Coordinate{Row: 8, Col: 8}] = Rook{Color: types.Black} } func (b *Board) CheckAndPlay(move *types.Move) (bool, Violation) { tempBoard := b.getCopyOfBoard() //Check start square of move pieceAtStartSquare := tempBoard.getPieceAt(move.StartSquare) if pieceAtStartSquare == nil { return false, NoPieceAtStartSquare } move.ColorMoved = pieceAtStartSquare.GetColor() if move.ColorMoved != tempBoard.colorToMove { return false, WrongColorMoved } move.PieceMoved = GetShortNameForPiece(pieceAtStartSquare) //Check end square of move pieceAtEndSquare := tempBoard.getPieceAt(move.EndSquare) if pieceAtEndSquare != nil { if pieceAtEndSquare.GetColor() == pieceAtStartSquare.GetColor() { return false, TargetSquareIsOccupied } } wasSpecialMove, violation := tempBoard.handleSpecialMove(*move) if violation != "" { return false, violation } if !wasSpecialMove { allMovesExceptBlocked := pieceAtStartSquare.GetAllNonBlockedSquares(tempBoard, move.StartSquare) legal := lo.Contains(allMovesExceptBlocked, move.EndSquare) if !legal { return false, InvalidMove } //We play the move on the temporary board delete(tempBoard.position, move.StartSquare) tempBoard.position[move.EndSquare] = pieceAtStartSquare } kingAttacked, err := tempBoard.isKingOfMovingColorInCheck(move.ColorMoved) if err != nil { return false, SomethingWentWrong } if kingAttacked { return false, KingInCheck } //We play the move on the real board b.position = tempBoard.position b.history = tempBoard.history b.colorToMove = b.colorToMove.Opposite() b.appendMoveToHistory(*move) pieceAtStartSquare.AfterMoveAction(b, move.StartSquare) return true, "" } func (b Board) isKingOfMovingColorInCheck(color types.ChessColor) (bool, error) { //Check if king of moving color is in check -> move not allowed //Do that by checking if the king is in a square attacked by the other color. ownKingCoordinate := b.getSquareOfPiece(King{Color: color}) if ownKingCoordinate == nil { return false, errors.New("no king found") } kingIsAttacked := b.isSquareAttacked(*ownKingCoordinate, color.Opposite()) if kingIsAttacked { return true, nil } return false, nil } func (b Board) getSquareOfPiece(piece Piece) *types.Coordinate { for k, v := range b.position { if v == piece { return &k } } return nil } func (b Board) isSquareAttacked(square types.Coordinate, byColor types.ChessColor) bool { var attackedSquares []types.Coordinate for square, piece := range b.position { if piece.GetColor() == byColor { attackedSquares = append(attackedSquares, piece.GetAllAttackedSquares(b, square)...) } } return lo.Contains(attackedSquares, square) } func (b Board) getPieceAt(coord types.Coordinate) Piece { piece, found := b.position[coord] if !found { return nil } return piece } func (b *Board) appendMoveToHistory(move types.Move) { b.history = append(b.history, move) } func (b Board) getLastMove() types.Move { if len(b.history) < 1 { return types.Move{} } return b.history[len(b.history)-1] } func (b Board) getCopyOfBoard() Board { return Board{ position: b.position.getCopyOfPosition(), history: b.history, colorToMove: b.colorToMove, state: b.state, } } func (p Position) getCopyOfPosition() Position { position := make(Position) for coord, piece := range p { position[coord] = piece } return position } func (b *Board) handleSpecialMove(move types.Move) (bool, Violation) { var was bool var violation Violation var pieceAtStartSquare = b.getPieceAt(move.StartSquare) switch piece := pieceAtStartSquare.(type) { case Pawn: was, violation = piece.HandlePossiblePromotion(b, move) if !was { was, violation = piece.HandleEnPassant(b, move, b.getLastMove()) } case King: was, violation = piece.HandleCastling(b, move) } return was, violation } type GameEndedReason string const ( NoReason GameEndedReason = "noReason" WhiteIsCheckmated GameEndedReason = "whiteIsCheckmated" BlackIsCheckmated GameEndedReason = "blackIsCheckmated" StalemateReason GameEndedReason = "stalemate" ) func (r GameEndedReason) String() string { return string(r) } func (b *Board) HasGameEnded(lastMove types.Move) (bool, GameEndedReason) { checkForColor := lastMove.ColorMoved.Opposite() if checkmate := b.isColorCheckmated(checkForColor); checkmate { switch checkForColor { case types.White: return true, WhiteIsCheckmated case types.Black: return true, BlackIsCheckmated } } if b.isStalemate() { return true, StalemateReason } return false, NoReason } func (b *Board) isColorCheckmated(color types.ChessColor) bool { inCheck, _ := b.isKingOfMovingColorInCheck(color) if !inCheck { return false } copyOfBoard := b.getCopyOfBoard() var movesToCheck []types.Move for startSquare, piece := range b.position { if piece.GetColor() == color { for _, endSquare := range piece.GetAllNonBlockedSquares(*b, startSquare) { move := types.Move{StartSquare: startSquare, EndSquare: endSquare} movesToCheck = append(movesToCheck, move) } } } for _, move := range movesToCheck { valid, _ := copyOfBoard.CheckAndPlay(&move) if valid { return false } } return true } func (b *Board) isStalemate() bool { return false }