Hello everyone.
My Tetris works ! It's here. It is ugly as hell, but my goal is more about improving what's under the hood, refactoring the data flow, clearing up the functions' purpose, etc.
Now there is this thing I can't wrap my head around. My run() function looks like this:
pub fn run(&mut self) {
loop {
// Termion's async stdin takes the user inputs and this function
// calls the moves like push_right(), turn(), etc.
self.take_directions();
self.display_the_board();
// Those functions manage game-induced events by keeping watch
self.clear_full_rows();
self.game_over();
// The thread sleeps for 700 ms (for a start) and then the tick()
// function brings the shape one row lower, and if it can't, freezes
// it and call the next shape
thread::sleep(time::Duration::from_millis(self.speed));
self.tick();
}
}
The problem is that the player has to wait for 700 ms until his moves are displayed on the screen.
What I would like :
tick() called every 700 ms
a loop that lets take_directions() be used as much as the user wants
I've heard of event loops but the crates I've been looking in look very impressive for what I just want to do.
What should be the better option / crate in your opinion ? Is there anything in the standard library for me ? Something like the Threadpool the Book talks about (I've not followed the book so far)
I'd like to know what I need to learn. If it's still too ambitious for me now, I'll come back to it later, when I'm wiser and older
@Keksoj, I'd recommend reading just the sample code in the page I've linked at the section of that page that I linked to. Then, if you're interested in way more information than you asked for, there's a lot of information on game loops on that page. You don't need all of it for Tetris. Most of it's in case you're making a game that has physics.
The big point of it is you have a tight loop which basically amounts to your own sleep function. In there, collect user input, but don't advance the actual game other than that.
@ricjac Holy cow that works ! I had to change bits to your code but it worked perfectly:
pub fn run(&mut self) {
let mut last_tick = std::time::SystemTime::now();
loop {
self.take_directions(); // moves
self.display_the_board();
let elapsed_since_last_tick = last_tick.elapsed().unwrap().as_millis();
let should_update = elapsed_since_last_tick >= self.speed;
if should_update {
self.clear_full_rows();
self.game_over();
self.tick();
last_tick = std::time::SystemTime::now();
}
}
}
The only thing was that the display_the_board function was called either at each tick (which doesn't allow the player some real_time feedback about the state of the game) or at each iteration of the loop (which is waaaaaaaay to fast), so I gave it its own time loop:
pub fn run(&mut self) {
let mut last_tick = std::time::SystemTime::now();
let mut last_display = std::time::SystemTime::now();
loop {
self.take_directions(); // moves
let elapsed_since_last_display = last_display.elapsed().unwrap().as_millis();
let should_display = elapsed_since_last_display >= 50;
if should_display {
self.display_the_board();
last_display = std::time::SystemTime::now();
}
let elapsed_since_last_tick = last_tick.elapsed().unwrap().as_millis();
let should_update = elapsed_since_last_tick >= self.speed;
if should_update {
self.clear_full_rows(); //
self.game_over(); // checks if game is over
self.tick();
last_tick = std::time::SystemTime::now();
}
}
}
And now I've got a 50 ms fast display of the moves. I won't let the code stay like that, I'll call the display function at each move, that should do the trick. It just goes to show how simple and straigth-forward your solution is. Thank you very much.
One thing yet : the rust book usually writes variable like this: my_variable. Why do you spell it myVariable ? Is there some kind of convention I'm not aware of ?
Now I know what the multi-thread architecture is made for, I'll have some purpose while learning about it.
@missingno: Thanks for the link, it does answer exactly the questions I had while struggling.
The standard Rust formatting guidelines say you should use snake_case (as you have in your code), not camelCase - I believe the compiler will throw a warning if you use the latter.
@Keksoj that is awesome! great work, that's really exciting!
Apologies about the Pascal casing, I spend 6+ hours a day on languages that use Pascal case, and Rust is just a hobby for me, so when replying on online forums, the rust compiler doesn't warn me and I'm still in the old Pascal case mindset.