Hello! Welcome back to the final feature lesson for our chess game.
In our last session, you did an excellent job adding visual highlights for the most recent move. By storing the last_move in your GameState and using it in your rendering loop, you made the game much more user-friendly. This reinforced the power of our state-driven design: update the state, and the visuals follow.
Today, we'll add the last crucial piece of functionality to make our project a complete, replayable experience. We will add a 'New Game' button to reset the game state and start a new match. This is the final step in creating a polished user interface and bringing our game to a finished state.
Here's an example of where we're headed, inspired by a real-world chess application:

We will achieve this by combining our knowledge of UI elements with the state management patterns we've been using throughout the course.
The Logic of a "New Game"
At its heart, "starting a new game" simply means throwing away our current, modified GameState (with its mid-game board, turn counter, and status) and replacing it with a fresh, initial one.
You've already written the most important piece of code for this: your GameState::new() function (or whatever you called the function that sets up the initial board and state). All we need to do is call that function again at the right moment.
There are a couple of ways to think about this, and you might see both in different kinds of games.
- Field-by-Field Reset: One way is to create a dedicated
reset()method that manually resets every field in theGameStatestruct. This is explored in the Breakout game tutorial linked below, which uses areset_gamefunction to reset the score, lives, and blocks. This approach is useful if you only want to reset some parts of the state while keeping others (like a high score). - Wholesale Replacement: A cleaner and simpler approach for our case is to replace the entire
GameStateobject with a brand new one. This is a common and powerful pattern in immediate-mode GUI frameworks. For example, theeguitutorial shows a state reset with a single line:*self = MyApp::default();.
For our chess game, the wholesale replacement is perfect. When the "New Game" button is clicked, we'll execute a single, simple line of code:game_state = GameState::new();
Let's put this into practice.
Part 1: Adding the "New Game" Button
First, we need to draw the button on the screen. We'll use macroquad::ui for this, just as we did for the pawn promotion menu. A good place for this button is in a corner of the window, outside the playing area, so it's always accessible but not in the way.
Your Task: Draw the Button
In your main game loop, find your rendering section. You should place the UI code after drawing your game board and pieces, but before next_frame().await. This ensures UI elements render on top of the game world.
We will use macroquad::ui to draw the button. The ui::root_ui().button() function returns true for the single frame in which it is clicked.
// In your main loop, near the end, after drawing pieces but before next_frame()
let ui = &mut ui::root_ui();
// We can create a small window or group to contain our button
// Let's place it at the top right. Adjust coordinates as needed.
ui.window(hash!(), vec2(660.0, 20.0), vec2(150.0, 50.0), |ui| {
if ui.button(None, "New Game") {
// We will add the reset logic here in the next step!
}
});
// ... your existing code ...
next_frame().await;
Note: You may need to adjust the vec2 coordinates to fit your window layout. The first vec2 is the position (x, y) and the second is the size (width, height).
Run your code now. You should see a small window with a "New Game" button appear on your screen. Clicking it doesn't do anything yet, but we've created the trigger.
Part 2: Implementing the Reset
Now for the final, satisfying step. We'll connect the button click to our state-reset logic. As discussed, this is surprisingly simple.
Your Task: Connect the Button to GameState::new()
Inside the if block for your button, you just need to re-assign your game_state variable.
// Inside your main loop...
ui.window(hash!(), vec2(660.0, 20.0), vec2(150.0, 50.0), |ui| {
if ui.button(None, "New Game") {
// This one line resets everything!
game_state = GameState::new();
}
});
That's it! Because our entire drawing and game logic is dependent on the game_state variable, replacing it with a new instance automatically resets everything:
- The board returns to the starting position.
- The
current_turnis set back toColor::White. - The
statusis reset toGameStatus::Ongoing. - The
last_movehighlight is cleared (last_move: None). - Any piece selection is cleared.
Run your application. Play a few moves, maybe even get to a checkmate. Then, click the "New Game" button. The board should instantly snap back to the start, ready for a new match.
Conclusion and Course Wrap-Up
Congratulations! You have successfully added a "New Game" button and, in doing so, completed the final feature of your graphical chess game.
Key Takeaways from this Lesson:
- Resetting a game can be as simple as replacing the entire state object with a new, default instance.
- The state-driven, immediate-mode architecture you've built makes features like this incredibly easy to implement. A single line of logic (
game_state = GameState::new();) is all it takes to trigger a cascade of changes in the next rendered frame. - Placing UI elements like buttons requires thinking about layout and ensuring they are drawn in the correct order (usually on top of the game world).
A Look Back at Your Journey
Take a moment to appreciate what you've accomplished. You started with a goal to build something in a new language, and over the last 25 days, you've built a complete, interactive, graphical chess game from the ground up in Rust.
You've navigated the core concepts that make Rust unique and powerful:
- Setting up your environment with Cargo.
- Mastering ownership, borrowing, and lifetimes to write safe, memory-efficient code.
- Modeling complex data using structs and enums, which were the backbone of your chess logic.
- Leveraging traits and
implblocks to organize behavior. - Handling user input and graphics with an external library,
macroquad. - Implementing a full suite of game logic, from basic piece movement to checkmate detection and advanced rules like pawn promotion.
- Building an interactive and user-friendly graphical interface.
This project was a fantastic hands-on challenge, and you've emerged with not only a finished product but also a deep, practical understanding of Rust.
What's Next?
This course may be over, but your journey as a developer isn't. Here are some ideas for how you could continue to build on this project:
- Add sound effects for moves, captures, and check.
- Implement a save/load feature to continue games later.
- Refactor the move generation to build a simple AI opponent.
- Experiment with a different graphics or UI library, like the
eguiframework you can see in the resource below.
Rust egui: A Step-by-Step Tutorial for Absolute Beginners
If you're curious about other ways to build UIs in Rust, this tutorial on egui provides a great look at a very popular immediate-mode GUI library. Notice how its state management philosophy (e.g., resetting state with *self = MyApp::default();) is conceptually very similar to what we did today.
You don't need to code along, but feel free to skim through the sections "Adding Basic UI Widgets" and "State Management in egui". Focus on how the MyApp struct holds the state and how the "Reset All State" button works by re-assigning self from a default instance.
Thank you for your hard work and dedication throughout this course. I hope you're proud of what you've built and feel inspired to continue creating with Rust. Happy coding