Hello! Welcome back to our journey of building a chess game in Rust.
In our last lesson, we laid the crucial groundwork for handling special chess moves by extending our Board struct to include castling_rights and an en_passant_square. This "memory" is essential because these rules depend on the history of the game, not just the current board state.
Today, we'll put that new state to work. Our goal is to implement the logic for castling, including all necessary conditions. We will write the code that reads the castling_rights, checks that all rules are met, and generates the appropriate castling moves.
1. Understanding the Rules of Castling
Castling is a unique move where the king and a rook move simultaneously. It's the only time in chess you can move two pieces in one turn. Before we write any code, we need to be crystal clear on the conditions that allow it.

There are five key conditions that must all be true for castling to be legal. To see a great explanation of these, please watch the first couple of minutes of the following video.
Programming a Chess Game in C# | Part 13 - Castling
This video, although in C#, provides a clear and concise explanation of the five rules of castling. Focus on understanding these conditions, as they are the requirements we'll translate into Rust code.
Watch from the beginning until 02:33. Pay close attention to the five enumerated rules for when castling is allowed.
To summarize, here are the five rules for castling:
- The king must not have previously moved.
- The chosen rook must not have previously moved.
- The squares between the king and the rook must be empty.
- The king cannot be in check.
- The king cannot pass through or end up on a square that is attacked by an enemy piece.
Our task is to check these five conditions in our Rust code.
2. Strategy for Implementation
We will add a method to your Board implementation that generates valid castling moves for the current player. A good approach is to create a method, for example generate_castling_moves, which can be called from your main move generation logic.
This method will:
- Check which player is to move.
- Check the
castling_rightsbitfield we created last lesson to see if the king and rooks are eligible. - Verify that the squares between them are empty by checking the
board.squaresarray. - Use the
is_square_attackedlogic you built in Module 6 to ensure the king isn't in check and doesn't pass through an attacked square. - If all checks pass, it will add a
Moveto a list of legal moves.
For now, we will represent a castling move simply as the king moving two squares (e.g., from e1 to g1). Later, when we implement the execute_move function, we'll add a special case: if a king moves two squares, we'll know it's a castling move and will also move the rook accordingly.
3. Hands-On: Generating Castling Moves
Let's get to the code. We'll build the logic for White's castling moves first. You can then use that as a template to implement Black's moves.
Open the file where your impl Board is located (likely src/main.rs). We'll add our checks inside a new function.
Step 1: Checking Castling Rights and Empty Squares
First, we use the castling_rights field. The bitwise AND operator (&) is perfect for checking if a specific flag is set in our u8.
Let's start by checking White's kingside castling. The conditions we can check right away are:
- Does White have kingside castling rights?
- Are the
f1andg1squares empty?
// Inside your `impl Board` block
// We assume you have a function to generate all moves, which can call this.
// For now, let's create it. If you have a different structure, adapt it.
pub fn generate_legal_moves(&self) -> Vec<Move> {
let mut moves = Vec::new();
// ... here you would call generators for pawns, knights, etc. ...
self.add_castling_moves(&mut moves);
moves
}
// This new function will contain our castling logic.
fn add_castling_moves(&self, moves: &mut Vec<Move>) {
if self.player_to_move == PieceColor::White {
// --- White Kingside Castling ---
// 1. Check if white has kingside castling rights
// 2. Check if the squares between king and rook are empty
if (self.castling_rights.0 & castling::W_KINGSIDE != 0) &&
self.squares[7][5].is_none() && // f1
self.squares[7][6].is_none() // g1
{
// If these conditions are met, we then check the attack rules.
// We'll add that code here in the next step.
}
// --- White Queenside Castling ---
// 1. Check if white has queenside castling rights
// 2. Check if the squares between king and rook are empty
if (self.castling_rights.0 & castling::W_QUEENSIDE != 0) &&
self.squares[7][1].is_none() && // b1
self.squares[7][2].is_none() && // c1
self.squares[7][3].is_none() // d1
{
// If these conditions are met, we then check the attack rules.
// We'll add that code here too.
}
} else {
// TODO: Implement for Black
}
}
Note: Your board coordinates might be different (e.g., squares[0][..] for rank 1). Adjust the indices [7][..] to match your board representation for rank 1.
Step 2: Checking for Attacks
Now for the tricky part: rules #4 and #5. The king cannot be in check, nor can it pass through an attacked square. Fortunately, you should already have a function from Module 6 that can tell you if a square is attacked by the opponent. Let's call it is_square_attacked.
We need to check three squares for kingside castling:
e1(current king square): Must not be attacked.f1(traversal square): Must not be attacked.g1(destination square): Must not be attacked.
Let's integrate this into our logic.
// Inside the `add_castling_moves` method, for White
// --- White Kingside Castling ---
if (self.castling_rights.0 & castling::W_KINGSIDE != 0) &&
self.squares[7][5].is_none() && // f1
self.squares[7][6].is_none() // g1
{
// Check if the king's path is clear of attacks
let king_pos = Position { rank: 7, file: 4 }; // e1
if !self.is_square_attacked(king_pos, PieceColor::Black) &&
!self.is_square_attacked(Position { rank: 7, file: 5 }, PieceColor::Black) && // f1
!self.is_square_attacked(Position { rank: 7, file: 6 }, PieceColor::Black) // g1
{
// ALL CHECKS PASSED! Add the move.
moves.push(Move {
from: king_pos,
to: Position { rank: 7, file: 6 }, // e1 to g1
// You might have other fields like `promotion`, set them to None/default
});
}
}
// --- White Queenside Castling ---
if (self.castling_rights.0 & castling::W_QUEENSIDE != 0) &&
self.squares[7][1].is_none() && // b1
self.squares[7][2].is_none() && // c1
self.squares[7][3].is_none() // d1
{
// Check if the king's path is clear of attacks
let king_pos = Position { rank: 7, file: 4 }; // e1
if !self.is_square_attacked(king_pos, PieceColor::Black) &&
!self.is_square_attacked(Position { rank: 7, file: 3 }, PieceColor::Black) && // d1
!self.is_square_attacked(Position { rank: 7, file: 2 }, PieceColor::Black) // c1
{
// ALL CHECKS PASSED! Add the move.
moves.push(Move {
from: king_pos,
to: Position { rank: 7, file: 2 }, // e1 to c1
});
}
}
Reminder: is_square_attacked is a function you should have from a previous lesson. If you named it differently, use your name. It should take a position and the attacking color and return true if any piece of that color attacks the square.
Your Turn: Implement for Black
Now, it's your turn to apply what we just did. Inside the else block for PieceColor::Black, implement the logic for both kingside and queenside castling for Black. Remember:
- Black's king starts at
e8. - The castling rights are
B_KINGSIDEandB_QUEENSIDE. - The ranks and squares will be different (e.g., rank 0 if you're using a 0-indexed array from the top).
- You will be checking for attacks from
PieceColor::White.
Take your time to adapt the logic. This is a great exercise to solidify your understanding.
Conclusion
Fantastic work! You have now translated the complex rules of castling into concrete Rust code. By combining the castling_rights state with your existing is_square_attacked logic, you've created a robust system for determining when this powerful move is legal. This is a huge step forward for your chess engine.
Key Takeaways:
- Implementing a complex rule like castling can be broken down into a series of smaller, verifiable checks.
- The five main conditions for castling are: king/rook haven't moved, path is clear, king is not in check, and king doesn't pass through or land in check.
- We used the
castling_rightsbitfield from the last lesson to efficiently check the movement history of the king and rooks. - Reusing existing functions like
is_square_attackedis key to building complex logic without duplicating code.
Preview of the Next Lesson
We've now tackled one of the two special moves we prepared for. In the next lesson, we will implement the logic for the en passant capture. We'll use the en_passant_square field from our Board struct to determine when this unique pawn capture is possible.