Implementing "Check" in a Chess Game
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. :)
- Unit test the setProtectedSquares()
- Unit test the testCheckWhite()
- Unit test the testcCheckBlack()
- 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.
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;
}
}
}
}