Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 26

King Movement: One-Square Radius

Recommended resources

Hello! In our last lesson, we tackled the knight's tricky L-shaped jumps. We learned a new and powerful strategy: using a fixed array of move offsets to generate all possible moves. This was a departure from the ray-casting logic we used for sliding pieces.

Today, we'll complete the move generation for all piece types by focusing on the most important piece on the board: the king. This lesson will cover how to generate valid moves for the king within a one-square radius.

The great news is that the king's movement is a simpler version of the fixed-offset strategy we just used for the knight. We'll be able to reuse the same pattern, making this a straightforward but crucial step in building our chess engine's brain.

1. Understanding the King's Movement

The king's basic movement rule is very simple: it can move one square in any direction—horizontally, vertically, or diagonally.

To see this in action, watch the first part of this short video.

How does the King move in chess?

The video "How does the King move in chess?" from the Epic Chess channel provides a quick and clear visual of the king's basic movement.

Watch the first 33 seconds of the video, which explains and demonstrates the king's ability to move one square in any direction.

This core rule can be visualized easily:

Chess King Movements
The king can move to any of the eight squares immediately surrounding it, provided the move is legal.

Now, the video also mentioned an important exception: a king cannot move to a square where it would be under attack (in "check"). This brings up an important concept in chess programming: pseudo-legal vs. legal moves.

  • Pseudo-legal moves: All moves a piece can make based on its fundamental movement pattern, ignoring whether the move would put its own king in check.
  • Legal moves: A subset of pseudo-legal moves that are valid in the current game context (i.e., they don't result in your own king being in check).

For now, our goal is to generate all pseudo-legal moves for the king. We will tackle the complex logic of check detection and generating fully legal moves in Module 6.


2. Strategy: Reusing the Fixed Offset Pattern

Just as we did with the knight, we can represent the king's movement with a fixed array of (dx, dy) offsets. Since the king moves one square in every direction, the eight offsets are:

  • Horizontal: (0, 1), (0, -1)
  • Vertical: (1, 0), (-1, 0)
  • Diagonal: (1, 1), (1, -1), (-1, 1), (-1, -1)

Our implementation will follow the exact same logic as the generate_knight_moves function:

  1. Define an array of the 8 move offsets.
  2. Loop through each offset and calculate the potential destination square.
  3. Check if the destination is on the board.
  4. Check if the destination is occupied by a friendly piece.
  5. If the destination is valid (empty or occupied by an enemy), add it to our list of moves.

3. Hands-On: Implementing King Moves

Let's add the final move generation function to our project.

  1. Define the generate_king_moves function. Open your move generation file (e.g., src/move_generation.rs) and add the following function. Notice how similar it is to the knight's implementation.

    // In your move generation file (e.g., src/move_generation.rs)
    
    use crate::{Board, Move, Piece, PieceColor, PieceType}; // Make sure you have necessary imports
    
    pub fn generate_king_moves(piece: &Piece, position: (u8, u8), board: &Board) -> Vec<Move> {
        let mut moves = Vec::new();
        let (x, y) = position;
    
        // All 8 possible one-square move offsets for a king
        let move_offsets: [(i8, i8); 8] = [
            (0, 1), (0, -1), (1, 0), (-1, 0), // Horizontal & Vertical
            (1, 1), (1, -1), (-1, 1), (-1, -1), // Diagonal
        ];
    
        for (dx, dy) in move_offsets.iter() {
            // Calculate the potential new position
            let new_x = x as i8 + dx;
            let new_y = y as i8 + dy;
    
            // Check 1: Is the new position on the board?
            if new_x >= 0 && new_x < 8 && new_y >= 0 && new_y < 8 {
                let target_pos = (new_x as u8, new_y as u8);
    
                // Check 2: Is the destination square occupied?
                match board.get_piece_at(target_pos) {
                    Some(target_piece) => {
                        // Check 3: Is it an enemy piece? If so, it's a valid capture.
                        if target_piece.color != piece.color {
                            moves.push(Move { from: position, to: target_pos });
                        }
                        // If it's a friendly piece, we do nothing.
                    }
                    None => {
                        // The square is empty, so it's a valid move.
                        moves.push(Move { from: position, to: target_pos });
                    }
                }
            }
        }
    
        moves
    }
    
  2. Compare with another implementation. The logic for checking a valid king move is fundamental. Let's see how the Bevy chess tutorial implements this.

Chess game in Rust using Bevy - lmao

The article "Chess game in Rust using Bevy" checks for valid moves inside an is_move_valid method. Find the PieceType::King case to see the same core logic we just implemented.

In the code block for the is_move_valid function, find the match self.piece_type statement. The PieceType::King arm checks if the absolute difference in coordinates is 1 for horizontal, vertical, or diagonal moves. This condition perfectly encapsulates the one-square movement rule.

This confirms our approach is solid. Our generate_king_moves function simply applies this check for all 8 adjacent squares.


4. Why Generating Legal Moves is Tricky

We've successfully generated pseudo-legal moves. To give you a taste of why the next step—filtering for fully legal moves—is more complex, let's look at an expert's take on it.

Generating Legal Chess Moves Efficiently - Peter Ellis Jones

The article "Generating Legal Chess Moves Efficiently" by Peter Ellis Jones discusses the challenges of king move generation. It introduces the idea of "king danger squares" and highlights a subtle but critical issue.

Read the section "Part 1: King moves". Pay close attention to the "Gotcha" subsection. It explains why a king on the board can inadvertently block the attack path of an enemy piece, making it seem like a square is safe when it isn't. This is the core problem we will need to solve in Module 6.

Understanding this "gotcha" is excellent preparation for our next module, where we will build the logic to correctly identify checks and generate only fully legal moves.


5. Testing Your King Logic

Finally, let's add a quick test to main.rs to verify our function. We'll place a king in a corner to test board boundaries.

First, import the new function at the top of main.rs:

// At the top of src/main.rs
use move_generation::{... , generate_king_moves}; // Add generate_king_moves to your imports

Next, add this test block to the end of your main function.

// In src/main.rs, at the end of main()

println!("\nTesting King Move Generation:");

let mut test_board = Board::new(); // Clears the board
let king_pos = (0, 0); // King at a1 (a corner)
test_board.pieces[king_pos.0 as usize][king_pos.1 as usize] = Some(Piece {
    piece_type: PieceType::King,
    color: PieceColor::White,
});
test_board.pieces[1][0] = Some(Piece { // Friendly piece at a2
    piece_type: PieceType::Pawn,
    color: PieceColor::White,
});

if let Some(king) = test_board.get_piece_at(king_pos) {
    let king_moves = generate_king_moves(king, king_pos, &test_board);
    println!("\nMoves for test king at a1: {:?}", king_moves);
    println!("Total moves: {}", king_moves.len());

    // Expected moves from a1 (0,0):
    // b1 (0,1)
    // b2 (1,1)
    // a2 (1,0) -> blocked by friendly pawn
    // Total should be 2
    assert_eq!(king_moves.len(), 2);
}

Run cargo run. The assertion should pass, confirming your function correctly generates the 2 valid pseudo-legal moves for a king in the corner next to a friendly piece.


Conclusion

Congratulations! You have now implemented pseudo-legal move generation for every piece in chess. This marks the completion of a major part of our engine's logic.

Key Takeaways:

  • The king's basic move is one square in any direction, which can be implemented using the same fixed-offset strategy as the knight.
  • We've distinguished between pseudo-legal moves (what we generate now) and legal moves (which account for checks), a crucial concept for what comes next.
  • You've seen a preview of why legal move generation requires more advanced logic, such as calculating "danger squares".

In our next lesson, we will move from planning moves to doing them. You'll write a function to execute a move by updating the board state, which is the first step toward creating an interactive game loop. See you then