Question

This question is fairly large and difficult to solve without looking through my code, and if it's exceedingly so, then the scope is probably too large and I'll delete the question. Basically I have a working Chess game with all of the rules of Chess except Check (and thus also not Checkmate, stalemate, etc.) implemented.

I went about implementing Check by assigning the Squares of my ChessBoard two booleans: protectedByWhite and protectedByBlack. There are two main pieces of logic with check:

  • If a move is made by White that causes his king to be on a square that is protectedByBlack, and vice versa with Black, the move is "undone".
  • If a move is made by White that causes Black's king to be on a square that is protectedByWhite, and vice versa with Black, Black's next move must place his king on a square that is not protectedByWhite.

Thus the logic is fairly simple. In my ChessBoard class, I have testCheckWhite and testCheckBlack functions that are called after every move. Moves are called in my Square class (a simple mouse click function).

The main problem is that the code is buggy... and I'm not sure why/where. The main bug is that:

  • When black or white are in Check, if they make a move where they would still be in Check, the move is not undone. I know that the undo function is working fine, so some how my logic is wrong.

For example, I have labels on the side that alert when Black/White are in check. When I initially "Check" the opponent, the label notifies me of the check. However, when I try to move the king to a square where I would still be in check, the label incorrectly says that there is no Check. I've been working for some time now trying to identify where I've gone wrong, and I could use some direction.


RELEVANT CODE:

ChessBoard.Java

public static void setProtectedSquares() {
        // Reset
        for(Square s : BOARD_SQUARES) {
            s.protectedByWhite = false;
            s.protectedByBlack = false;
        }

        // Now set protections
        for(Square s : BOARD_SQUARES) {
            if(s.hasPiece() && s.getPiece().getTeamColor().equals(TeamColor.WHITE)) {
                Piece p = s.getPiece();
                for(int[] position : p.getLegalMoves(p.getPosition())) {
                    if(hasSquare(position)) {
                        getSquare(position).protectedByWhite = true;
                    }
                }
            }
        }
        for(Square s : BOARD_SQUARES) {
            if(s.hasPiece() && s.getPiece().getTeamColor().equals(TeamColor.BLACK)) {
                Piece p = s.getPiece();
                for(int[] position : p.getLegalMoves(p.getPosition())) {
                    if(hasSquare(position)) {
                        getSquare(position).protectedByBlack = true;
                    }
                }
            }
        }
}

public static boolean testCheckWhite() {

        // Get king position
        int[] whiteKingPosition = new int[]{};
        for(Square s : BOARD_SQUARES) {
            Piece p = s.getPiece();
            if(s.hasPiece() && (p.getPieceType()).equals(PieceType.KING)) {
                if((p.getTeamColor()).equals(TeamColor.WHITE)) {
                    whiteKingPosition = p.getPosition();
                }
            }
        }

        if(hasSquare(whiteKingPosition) && getSquare(whiteKingPosition).protectedByBlack) {
            GameInfoPanel.inCheckWhite.setText("White is in check");
            return true;
        } else {
            GameInfoPanel.inCheckWhite.setText("White is not in check");
            return false;
        }
    }

    public static boolean testCheckBlack() {

        // Get king position
        int[] blackKingPosition = new int[]{};
        for(Square s : BOARD_SQUARES) {
            Piece p = s.getPiece();
            if(s.hasPiece() && (p.getPieceType()).equals(PieceType.KING)) {
                if((p.getTeamColor()).equals(TeamColor.BLACK)) {
                    blackKingPosition = p.getPosition();
                }
            }
        }

        if(hasSquare(blackKingPosition) && getSquare(blackKingPosition).protectedByWhite) {
            GameInfoPanel.inCheckBlack.setText("Black is in check");
            return true;
        } else {
            GameInfoPanel.inCheckBlack.setText("Black is not in check");
            return false;
        }
    }

Square.java

.... // If a square is clicked that IS colored...
        } else {
            for(Square s : ChessBoard.BOARD_SQUARES) {
                if(s.hasPiece() && (s.getPiece()).getFocus()) {

                    // Check to make sure that the target square and current
                    // square are not the same
                    if(!this.equals(s)) {
                        movePiece(s);

                        ChessBoard.setProtectedSquares();

                        // Test for check
                        // 1) Find out what color the moved piece is
                        if((ChessBoard.getTurn()) == TeamColor.WHITE) {
                            if(ChessBoard.testCheckWhite()) {
                                // Undo move
                                s.movePiece(ChessBoard.getSquare(STORED_POSITION));
                                GameInfoPanel.gameStatus.setText("Illegal move, white in check");
                            } else if(ChessBoard.testCheckBlack()) {
                                // Move is okay, black is now in check
                                GameInfoPanel.gameStatus.setText("Okay move, black in check");
                                // Switch players' turn
                                ChessBoard.switchTurn();
                            } else {
                                // Move is okay, nothing happened
                                GameInfoPanel.gameStatus.setText("No one in check");
                                // Switch players' turn
                                ChessBoard.switchTurn();
                            }

                        } else {
                            if(ChessBoard.testCheckBlack()) {
                                // Undo move
                                s.movePiece(ChessBoard.getSquare(STORED_POSITION));
                                GameInfoPanel.gameStatus.setText("Illegal move, black in check");
                            } else if(ChessBoard.testCheckWhite()) {
                                // Move is okay, white is now in check
                                GameInfoPanel.gameStatus.setText("Okay move, white in check");
                                // Switch players' turn
                                ChessBoard.switchTurn();
                            } else {
                                // Move is okay, nothing happened
                                GameInfoPanel.gameStatus.setText("No one in check");
                                // Switch players' turn
                                ChessBoard.switchTurn();
                            }
                        }
                    }
                }
            }

            // Clear all color and focus
            ChessBoard.clearFocus();

            ChessBoard.setProtectedSquares();
        }
Was it helpful?

Solution

I quite understand the algorithm of your code. Unfortunately, I don't see anything wrong with the snippet that you posted.

That's why you should always use Unit Tests while you code. :)

  1. Unit test the setProtectedSquares()
  2. Unit test the testCheckWhite()
  3. Unit test the testcCheckBlack()
  4. Unit test THEN REFACTOR the for(Square s : ChessBoard.BOARD_SQUARES) {...}

These will help you in the long run.

However, if you want to solve (hopefully) things quicker, use the debugger mode from your IDE.

OTHER TIPS

Here's a suggested line of investigation. Let's assume this is the scenario: white has checked black and black king can move to another square protected by white.

Based on the code provided and the fact that black (when under check) can move to a square which is protected by white, it must be the case that ChessBoard.testCheckBlack() method is returning false. Now based on implementation ChessBoard.testCheckBlack() method, we can conclude that the text

"Black is in check"

must be output on GameInfoPanel.

If that is correct then following need to be checked, one by one:

hasSquare(blackKingPosition)
getSquare(blackKingPosition).protectedByWhite // just protectedByWhite 

It would be great if you could post GameInfoPanel messages for the scenario above.

Checking to see if any square is threatened is a useful function that you may use not only for direct King attacks but also for castling since a King is not allowed to castle "through" check. This means you have to ensure up to three squares are free from an opponent's direct line of attack to castle the King legally.

With this is mind, you might want to construct a method such as this:

public static final boolean isThreatenedSquare(
          Color threatenedColor, 
          Square threatenedSquare, 
          Chessboard board)

The algorithm for sliding pieces might employ parallel arrays to define the 8 directions (lines of attack or "rays") that radiate out from origin (the threatened square). For example:

int[] rowDirections = {-1, -1, -1, 0, 0, 1, 1, 1};
int[] colDirections = {-1, 0, 1, -1, 1, -1, 0, 1};
  • the first index set (-1, -1) represents a diagoanl "ray" moving in a "northwest" direction
  • the second index set (-1, 0) represents a vertical "ray" moving in a "north" direction
  • the third index set (-1, 1) represents a diagonal "ray" moving in a "northeast" direction
  • the fourth index set (0, -1) represents a horizontal "ray" moving in a "west" direction

... and so on. By radiating out one square at a time, you would simply inspect the square (ensuring you are within chessboard boundaries) and see if it is occupied. If it is, determine whether it is a friendly or opponent piece. Just because we have hit an opponent's piece does not necessarily mean our threatened square is under attack.

Bishops, for example can only attack along diagonal rays so finding one along a vertical ray stops our ray from radiating out any further however the Bishop does not threaten the square. We can neatly describe the attacking capabilities for any sliding piece with respect to the parallel directional arrays we defined earlier. For example:

boolean bishopThreats[] = {true, false, true, false, false, true, false, true};
boolean rookThreats[] = {false, true, false, true, true, false, true, false};
boolean queenThreats[] = {true, true, true, true, true, true, true, true};
boolean kingThreats[] = {true, true, true, true, true, true, true, true};

The arrays above show that Bishops can only threaten along diagonals, Rooks along vertical and horizontal lines, Queens and Kings can attack in any direction.

Pawns are somewhat tricky because they attack along diagonals but only in northeast + northwest directions (for white), and southeast + southwest directions (for black).

boolean kill = threatenedColor.equals(Color.black) ? true : false;
boolean pawnThreats[] = {kill, false, kill, false, false, !kill, false, !kill};

With everything in place, all that is required is to use a couple of nested for loops. The outer one to iterate through all directions, the inner one to radiate out one square at a time until we hit the edge of the chessboard or hit a piece, whichever comes first. Here is the algorithm for sliding pieces. Knight are abit different than sliding pieces but the general ideas presented here also apply.

boolean threatDetected = false;
int threatenedRow = threatenedSquare.getRow();
int threatenedCol = threatenedSquare.getCol();

for(int direction = 0; direction < 8 && !threatDetected; direction++) {
    // RESET OUR COORDINATES TO PROCESS CURRENT LINE OF ATTACK. 
    // INCREMENT VALUES ARE SAME AS DIRECTION ARRAY VALUES
    int row = threatenedRow;
    int col = threatenedCol;
    int rowIncrement = rowDirections[direction];
    int colIncrement = colDirections[direction];

    // RADIATE OUTWARDS STARTING FROM ORIGIN UNTIL WE HIT A PIECE OR ARE OUT OF BOUNDS
    for(int step = 0; step < 8; step++) {
        row = row + rowIncrement;
        col = col + colIncrement;

        // IF WE ARE OUT OF BOUNDS, WE STOP RADIATING OUTWARDS FOR 
        // THIS RAY AND TRY THE NEXT ONE
        if(row < 0 || row > 7 || col < 0 || col > 7) {
            break;
        }
        else {
            // LOOK AT CURRENT SQUARE AND SEE IF IT IS OCCUPIED BY A PIECE
            Square square = board.getSquare(row, col);
            IPiece piece = square.getPiece();
            if(piece != null) {
                // RADIATING OUTWARDS MUST STOP SINCE WE HIT A PIECE, ONLY
                // QUESTION IS WHAT DID WE HIT? FRIEND OR FOE?  
                if(!piece.getColor.equals(threatenedColor)) {
                    // WE ARE FACING AN OPPONENT, DOES IT HAVE THE CAPABILITY
                    // TO ATTACK US GIVEN THE DIRECTIONAL LINE OF ATTACK  
                    // WE ARE CURRENTLY ANALYZING     
                    if(piece instanceof Bishop && bishopThreats[direction]) 
                        threatDetected = true;
                    else if(piece instanceof Rook && rookThreats[direction]) 
                        threatDetected = true;
                    else if(piece instanceof Queen && queenThreats[direction]) 
                        threatDetected = true;
                     else {
                        if(step == 0) {  
                            // PAWNS AND KINGS DONT HAVE THE REACH OF OTHER SLIDING
                            // PIECES; THEY CAN ONLY ATTACK SQUARES THAT ARE CLOSEST
                            // TO ORIGIN  
                            if(piece instanceof Pawn && pawnThreats[direction]) 
                                threatDetected = true;
                            if(piece instanceof King && kingThreats[direction]) 
                                threatDetected = true;
                        }  
                    }
                }
                break;
            }
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top