Skip to main content

Welcome to a Grasp lesson

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

Lesson 50

Displaying Game Status and Turns

Recommended resources

Hello! Welcome back to our chess game project.

In our last lesson, we demystified the game loop, understanding the fundamental Input -> Update -> Render cycle that powers our game. You saw that the board updates after a move because the rendering step always draws the latest GameState.

Today, we'll enhance the "Render" stage of our loop. A silent board can be confusing; players need to know whose turn it is and if a king is in danger. Our goal is to display the current turn and game status (e.g., "Check!") as text on the screen, providing clear, real-time feedback to the players.

Part 1: Displaying the Current Player's Turn

First, let's show whose move it is. Your GameState struct should already be tracking the current player, likely with a field like current_turn: Color. We'll now read this field and draw it on the screen.

To draw text in Macroquad, we can use the draw_text() function. It's straightforward and perfect for this task.

Let's watch a short segment that demonstrates this. The example is from a Breakout game, but the principle of drawing dynamic text like a score is identical to how we'll draw the current turn.

Rust breakout game tutorial

In the 'Rust breakout game tutorial' by Tantan, you'll see how to load a font and use draw_text to display game information on the screen. Pay close attention to how a string is formatted and then passed to the drawing function.

Watch from 00:13:11 to 00:14:55. Focus on: How format! is used to create the text string (e.g., format!("Score: {}", score)). The arguments for draw_text_ex: the text itself, X/Y coordinates, and TextParams for size and color. The use of measure_text to center the text, which we'll use later.

Now, let's apply this to our chess game.

Your Task: Display the Current Turn

  1. Navigate to the rendering section of your main function's game loop, right before next_frame().await.
  2. Add code to determine the text to display based on game_state.current_turn. A match expression is perfect for this.
  3. Use draw_text() to render the text. Let's place it in the top-left corner for now.

Here's a code snippet to guide you. You'll need to adapt it to your GameState and Color enum definition.

// In your main loop, after drawing the board and pieces...

// 1. Determine the text based on the current turn
let turn_text = match game_state.current_turn {
    Color::White => "Turn: White",
    Color::Black => "Turn: Black",
};

// 2. Draw the text on the screen
// draw_text(text, x, y, font_size, color)
macroquad::text::draw_text(
    turn_text,
    20.0,      // x-coordinate
    40.0,      // y-coordinate
    40.0,      // font_size
    macroquad::color::BLACK,
);

// ...
next_frame().await;

Run your game after adding this code. You should now see "Turn: White" at the top of the window. When a move is made and the turn switches, this text should update automatically on the next frame!


Part 2: Communicating Game Status (Check and Checkmate)

Just like knowing the turn, players must know if a king is in check. Relying on them to spot it themselves can lead to confusion and illegal moves. We'll now display a status message when the game enters a critical state.

To manage this cleanly, we can introduce a GameStatus enum. This is a common pattern in game development for tracking the overall state of the match.

The blog post "rust breakout part 4" (a text version of the video you just saw) clearly illustrates how to use an enum and a match statement to display different text for different game states.

rust breakout part 4: this is the end, my friend! - The Answer's 42

This article, 'rust breakout part 4', explains how to use a GameState enum to control what is drawn on the screen. We'll focus on the section that shows how different text is rendered for the menu, game, and end screens.

Read the final code block in the article, under the line 'We now need to take care of the menu states...'. Notice how the match game_state { ... } block is used to call draw_title_text with different strings for GameState::Menu, GameState::LevelCompleted, etc. This is the exact pattern we will use.

Your Task: Implement and Display GameStatus

  1. Define a GameStatus Enum:
    Add this enum to your project. A good place is near your GameState struct definition.

    
    
    
    #[derive(Debug, Clone, Copy, PartialEq)]
    pub enum GameStatus {
        Ongoing,
        Check,
        Checkmate(Color), // The color of the winning player
        Stalemate,
    }
    
  2. Add status to GameState:
    Add a field to your main GameState struct to hold the current status. Initialize it to Ongoing.

    pub struct GameState {
        pub board: [[Option<Piece>; 8]; 8],
        pub current_turn: Color,
        pub status: GameStatus, // Add this line!
        // ... other fields like castling rights if you have them
    }
    
    // In your GameState::new() or initialization function:
    // ...
    status: GameStatus::Ongoing,
    // ...
    
  3. Update the Status in Your Game Logic:
    You'll need to update the game_state.status field at the right time. Your move generation logic from Module 6 (specifically is_in_check, detect_checkmate, and detect_stalemate) is the key.

    • After a move is made, call your function to check if the new current player is in check.
    • If they are, check for checkmate or stalemate.
    • Update game_state.status accordingly. If there's no check, but no legal moves, it's a stalemate. If there's check and no legal moves, it's checkmate. If there's check but there are legal moves, it's just GameStatus::Check. Otherwise, it's GameStatus::Ongoing.
  4. Display the Status:
    Finally, back in your main loop's rendering section, use a match statement to draw the status. For a more dramatic effect, let's try to center this text. You can use screen_width() and screen_height() from Macroquad to help with positioning.

    // In your main loop, after drawing the turn text...
    let status_text = match game_state.status {
        GameStatus::Check => "Check!",
        GameStatus::Checkmate(winner) => {
            // format! returns a String, which we need to use
            // We'll see how to handle this better in a moment
            match winner {
                Color::White => "Checkmate! White wins.",
                Color::Black => "Checkmate! Black wins.",
            }
        },
        GameStatus::Stalemate => "Stalemate!",
        GameStatus::Ongoing => "", // Don't draw anything if the game is just ongoing
    };
    
    // For now, let's just put it below the board
    // We'll refine the positioning next
    if !status_text.is_empty() {
        macroquad::text::draw_text(
            status_text,
            20.0,
            (SQUARE_SIZE * 8.0) + 60.0, // Position it below the board
            60.0,
            macroquad::color::RED,
        );
    }
    

    A note on the format! macro: The format! macro creates an owned String. The draw_text function takes a &str. The compiler can handle this conversion for you, but it's good to be aware of. When one branch of a match returns a String and others return &str, the compiler might ask you to be more explicit. We'll keep it simple for now.

After these changes, try to put a king in check. A "Check!" message should appear!


Part 3: Polishing with Centered Text

Hard-coding coordinates like 20.0 is fine, but important messages like "Checkmate!" look better when centered. As you saw in the video, Macroquad provides the measure_text function for this. It tells you the dimensions a string will have before you draw it.

Let's update our status display code to properly center the text.

// Replace the previous status drawing block with this
if !status_text.is_empty() {
    let font_size = 60.0;
    let text_dims = macroquad::text::measure_text(status_text, None, font_size as u16, 1.0);

    macroquad::text::draw_text(
        status_text,
        (macroquad::window::screen_width() / 2.0) - (text_dims.width / 2.0),
        (macroquad::window::screen_height() / 2.0) - (text_dims.height / 2.0),
        font_size,
        macroquad::color::RED,
    );
}

Now, your game status alerts will appear right in the middle of the screen, making them impossible to miss.


Conclusion

Great work! You've significantly improved the user experience of your game by providing essential feedback. What was once a silent digital board now communicates its state clearly to the players.

Key Takeaways:

  • You can render dynamic information using macroquad::text::draw_text.
  • The format! macro is invaluable for creating strings from game variables.
  • A state enum (like our GameStatus) paired with a match statement is a powerful and clean pattern for managing what gets displayed.
  • You can precisely position text using measure_text to calculate dimensions before drawing, allowing for alignment like centering.

Preview of the Next Lesson

We've added static text, but what about interactive UI elements? When a pawn reaches the other side of the board, the player needs to choose which piece to promote it to. In the next lesson, we will tackle this by implementing a graphical menu for pawn promotion, taking our UI skills to the next level.