Skip to main content

Welcome to a Grasp lesson

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

Lesson 19

Initializing the Chess Board

Recommended resources

Hello! Welcome back to building our chess game in Rust.

In our last lesson, you did a great job setting up the fundamental structure for our game board. We created the Board struct containing an 8x8 grid of Option<Piece>, representing an empty chessboard.

Today, we'll bring that empty board to life. Our goal is to write a function that places all 32 chess pieces in their standard starting positions. This will give us the initial game state, a crucial step towards making our game playable.

1. The Starting Position: Our Target

Before we write any code, let's be crystal clear about what we want to achieve. Here is the standard starting setup for a game of chess:

Standard Chess Board Initial Setup
This is the state we want our `Board` struct to represent after initialization. White is at the bottom (ranks 1 and 2), and Black is at the top (ranks 7 and 8).

To translate this image into our [[Option<Piece>; 8]; 8] array, we need to agree on a coordinate system. Let's establish a convention that we'll use throughout the project:

  • The array will be indexed as pieces[rank][file].
  • Ranks (rows): rank 0 corresponds to the 8th rank (Black's back rank), and rank 7 corresponds to the 1st rank (White's back rank).
  • Files (columns): file 0 corresponds to the 'a'-file, and file 7 corresponds to the 'h'-file.

So, for example:

  • The square a1 is at pieces[7][0].
  • The square h8 is at pieces[0][7].
  • The square e4 is at pieces[4][4].

2. The "Constructor" Pattern in Rust

We need a function that creates and returns a new Board instance, all set up and ready to go. In object-oriented languages like Java, you might use a constructor for this. Rust doesn't have constructors in the same way, but it has a very common and powerful pattern: associated functions.

An associated function is a function that belongs to a type (like a struct or enum). A common convention is to create a function named new() that returns a new instance of the type.

We can see this pattern suggested in the "Data Structures" resource you've seen before, where a Board::new_empty() function was created.

Lecture 2: Data Structures

Let's revisit this resource to see the idiomatic Rust pattern for creating new instances of a struct.

Scroll down to the 'Structures' section. Notice how the initial, verbose struct creation is improved by moving the logic into an impl Board block with an associated function fn new_empty() -> Board. We will use this exact pattern, but our function will create a board with pieces instead of an empty one.

We will create an impl Board block and define a pub fn new() -> Board function. This will be our "constructor" for creating a new, fully initialized chessboard.


3. Hands-On: Initializing the Board

It's time to write the code. We will implement the Board::new() function by manually setting each piece. This approach is straightforward, easy to debug, and directly translates the visual board into code.

  1. Open src/board/mod.rs:
    This is where you defined the Board struct. We'll add the impl block right below it.

  2. Implement Board::new():
    Add the following code to the bottom of src/board/mod.rs. I've included the complete function for you. Read through it carefully and see how the code corresponds to the board image above.

    // In src/board/mod.rs, after the Board struct definition
    
    use crate::pieces::{Piece, PieceColor, PieceType};
    
    impl Board {
        /// Creates a new Board with pieces in their standard starting positions.
        pub fn new() -> Board {
            let mut pieces = [[None; 8]; 8];
    
            // --- Place White Pieces ---
            // Back Rank (Rank 1)
            pieces[7][0] = Some(Piece { color: PieceColor::White, kind: PieceType::Rook });
            pieces[7][1] = Some(Piece { color: PieceColor::White, kind: PieceType::Knight });
            pieces[7][2] = Some(Piece { color: PieceColor::White, kind: PieceType::Bishop });
            pieces[7][3] = Some(Piece { color: PieceColor::White, kind: PieceType::Queen });
            pieces[7][4] = Some(Piece { color: PieceColor::White, kind: PieceType::King });
            pieces[7][5] = Some(Piece { color: PieceColor::White, kind: PieceType::Bishop });
            pieces[7][6] = Some(Piece { color: PieceColor::White, kind: PieceType::Knight });
            pieces[7][7] = Some(Piece { color: PieceColor::White, kind: PieceType::Rook });
    
            // Pawns (Rank 2) - using a loop to keep it DRY (Don't Repeat Yourself)
            for file in 0..8 {
                pieces[6][file] = Some(Piece { color: PieceColor::White, kind: PieceType::Pawn });
            }
    
            // --- Place Black Pieces ---
            // Pawns (Rank 7)
            for file in 0..8 {
                pieces[1][file] = Some(Piece { color: PieceColor::Black, kind: PieceType::Pawn });
            }
    
            // Back Rank (Rank 8)
            pieces[0][0] = Some(Piece { color: PieceColor::Black, kind: PieceType::Rook });
            pieces[0][1] = Some(Piece { color: PieceColor::Black, kind: PieceType::Knight });
            pieces[0][2] = Some(Piece { color: PieceColor::Black, kind: PieceType::Bishop });
            pieces[0][3] = Some(Piece { color: PieceColor::Black, kind: PieceType::Queen });
            pieces[0][4] = Some(Piece { color: PieceColor::Black, kind: PieceType::King });
            pieces[0][5] = Some(Piece { color: PieceColor::Black, kind: PieceType::Bishop });
            pieces[0][6] = Some(Piece { color: PieceColor::Black, kind: PieceType::Knight });
            pieces[0][7] = Some(Piece { color: PieceColor::Black, kind: PieceType::Rook });
    
            Board { pieces }
        }
    }
    

    A quick note: Make sure the use crate::pieces::{...} line is at the top of your src/board/mod.rs file. You may have added a similar line in a previous lesson.

  3. Update main.rs to use the new function:
    Now, let's use our new "constructor". Go to your main function in src/main.rs.

    Replace the line that creates the empty_board with a call to Board::new().

    // In the main() function of src/main.rs
    
    fn main() {
        // ... previous code can be removed or commented out ...
    
        // Create a new board with pieces in their starting positions.
        let board = Board::new();
    
        // Print it to see the result!
        println!("New board with starting pieces: {:?}", board);
    }
    

    Don't forget to keep your use board::Board; line at the top of main.rs!

  4. Run Your Code:
    Execute cargo run in your terminal. This time, instead of seeing an 8x8 grid of None, you'll see a much more interesting output. The back ranks and pawn ranks will be filled with Some(Piece { ... }) structs, while the middle ranks will still be None. You have successfully represented the start of a chess game!


Conclusion

Fantastic work! You've taken a significant step forward today. You now have a function that can produce a Board struct representing a complete, standard chess game setup.

Key Takeaways:

  • A clear coordinate system ([rank][file]) is essential for mapping between the conceptual board and the data structure.
  • Associated functions, often named new(), are the idiomatic Rust pattern for creating new instances of a struct, similar to constructors in other languages.
  • We can initialize our board by creating a mutable 2D array, populating it with Some(Piece) values at the correct indices, and then returning it inside a Board struct.
  • Using loops for repetitive setups, like placing the pawns, can make the initialization code cleaner.

The Debug output you see is functional, but it's not very easy to read. In the next lesson, we will implement a method to print the board state to the console in a nicely formatted, human-readable way, which will be invaluable for debugging as we start to implement piece movement.