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.
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
- Navigate to the rendering section of your
mainfunction's game loop, right beforenext_frame().await. - Add code to determine the text to display based on
game_state.current_turn. Amatchexpression is perfect for this. - 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
-
Define a
GameStatusEnum:
Add this enum to your project. A good place is near yourGameStatestruct definition.#[derive(Debug, Clone, Copy, PartialEq)] pub enum GameStatus { Ongoing, Check, Checkmate(Color), // The color of the winning player Stalemate, } -
Add
statustoGameState:
Add a field to your mainGameStatestruct to hold the current status. Initialize it toOngoing.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, // ... -
Update the Status in Your Game Logic:
You'll need to update thegame_state.statusfield at the right time. Your move generation logic from Module 6 (specificallyis_in_check,detect_checkmate, anddetect_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.statusaccordingly. 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 justGameStatus::Check. Otherwise, it'sGameStatus::Ongoing.
-
Display the Status:
Finally, back in your main loop's rendering section, use amatchstatement to draw the status. For a more dramatic effect, let's try to center this text. You can usescreen_width()andscreen_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: Theformat!macro creates an ownedString. Thedraw_textfunction takes a&str. The compiler can handle this conversion for you, but it's good to be aware of. When one branch of amatchreturns aStringand 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.
To center text, you calculate its starting x position by taking half the screen's width and subtracting half of the text's own width.
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 amatchstatement is a powerful and clean pattern for managing what gets displayed. - You can precisely position text using
measure_textto 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.