package chess import ( "mchess_server/types" "github.com/samber/lo" ) type Position map[types.Coordinate]Piece type Board struct { position Position history []types.Move } func newBoard() Board { return Board{ position: make(Position), history: make([]types.Move, 0), } } 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, string) { // We make a copy of the original board to play moves on it, // We can play the move on it and then check if it is invalid tempBoard := b.getCopyOfBoard() //Check start square of move pieceAtStartSquare := tempBoard.getPieceAt(move.StartSquare) if pieceAtStartSquare == nil { return false, "no piece at start square" } move.ColorMoved = pieceAtStartSquare.GetColor() move.PieceMoved = GetShortNameForPiece(pieceAtStartSquare) //Check end square of move pieceAtEndSquare := tempBoard.getPieceAt(move.EndSquare) if pieceAtEndSquare != nil { if pieceAtEndSquare.GetColor() == pieceAtStartSquare.GetColor() { return false, "same-coloured piece at end square" } } wasSpecialMove := tempBoard.handleSpecialMove(move) if !wasSpecialMove { // At the moment, we do not need to check if the correct color is moving, //since we are only reading moves from the player whose turn it is. allMovesExceptBlocked := pieceAtStartSquare.GetAllMovesButBlocked(tempBoard, move.StartSquare) legal := lo.Contains(allMovesExceptBlocked, move.EndSquare) if !legal { return false, "not a legal square" } //We play the move on the temporary board delete(tempBoard.position, move.StartSquare) tempBoard.position[move.EndSquare] = pieceAtStartSquare } //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 := tempBoard.getSquareOfPiece(King{Color: move.ColorMoved}) if ownKingCoordinate == nil { return false, string(move.ColorMoved) + " king not found" } kingIsAttacked := tempBoard.isSquareAttacked(*ownKingCoordinate, move.ColorMoved.Opposite()) if kingIsAttacked { return false, "king is attacked after move" } //Check for checkmat&e //Is every square that the king can move to attacked? And can no other //piece block? -> checkmate //only check if paths of attacking pieces can be blocked //Maybe for checking checkmate, we have to check the 'path' in which the //checkmate is given // |K| | | | |Q| // in this scenaria the path are all the squares between queen and king. // If a piece can be moved into the path, it is no checkmate //We play the move on the real board b.position = tempBoard.position b.history = tempBoard.history b.appendMoveToHistory(move) return true, "" } 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 { attackedSquares = append(attackedSquares, piece.GetAllMovesButBlocked(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, } } 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 { var was bool var pieceAtStartSquare = b.getPieceAt(move.StartSquare) switch pieceAtStartSquare.(type) { case Pawn: was = b.handlePossiblePromotion(move) if !was { was = b.handleEnPassant(move, b.getLastMove()) } } return was } func (b *Board) handlePossiblePromotion(move types.Move) bool { var isPromotionMove bool var promotionToPiece types.PieceShortName //TODO(m): What if message does not contain a promotion, but should be because a pawn is moved to the end square messageContainsPromotion := move.IsPromotionMove() if messageContainsPromotion { promotionToPiece = *move.PromotionToPiece } switch move.ColorMoved { case types.White: if move.StartSquare.Row == types.RangeLastValid-1 && move.EndSquare.Row == types.RangeLastValid { isPromotionMove = true } case types.Black: if move.StartSquare.Row == types.RangeFirstValid+1 && move.EndSquare.Row == types.RangeFirstValid { isPromotionMove = true } } if isPromotionMove { delete(b.position, move.StartSquare) b.position[move.EndSquare] = GetPieceForShortName(promotionToPiece, move.ColorMoved) } return isPromotionMove } func (b *Board) handleEnPassant(move, lastMove types.Move) bool { var wasEnPassant bool if lastMove.PieceMoved != types.PawnShortName { return false } switch move.ColorMoved { case types.White: if lastMove.StartSquare.Row != 7 || lastMove.EndSquare.Row != 5 { wasEnPassant = false } if move.StartSquare.Row != 5 { wasEnPassant = false } if move.EndSquare.Row != 6 { wasEnPassant = false } if move.StartSquare.Col == lastMove.EndSquare.Col+1 && move.EndSquare.Col == lastMove.EndSquare.Col { wasEnPassant = true } if move.StartSquare.Col == lastMove.EndSquare.Col-1 && move.EndSquare.Col == lastMove.EndSquare.Col { wasEnPassant = true } case types.Black: if lastMove.StartSquare.Row != 2 || lastMove.EndSquare.Row != 4 { wasEnPassant = false } if move.StartSquare.Row != 4 { wasEnPassant = false } if move.EndSquare.Row != 3 { wasEnPassant = false } if move.StartSquare.Col == lastMove.EndSquare.Col+1 && move.EndSquare.Col == lastMove.EndSquare.Col { wasEnPassant = true } if move.StartSquare.Col == lastMove.EndSquare.Col-1 && move.EndSquare.Col == lastMove.EndSquare.Col { wasEnPassant = true } } if wasEnPassant { //play the move delete(b.position, lastMove.EndSquare) b.position[move.EndSquare] = GetPieceForShortName(move.PieceMoved, move.ColorMoved) } return wasEnPassant }