|
@@ -1,25 +1,29 @@
|
|
|
extern crate rand;
|
|
extern crate rand;
|
|
|
extern crate sdl2;
|
|
extern crate sdl2;
|
|
|
|
|
|
|
|
-use std::{io, thread};
|
|
|
|
|
use std::fs::File;
|
|
use std::fs::File;
|
|
|
|
|
+use std::io;
|
|
|
use std::io::{Read, Write};
|
|
use std::io::{Read, Write};
|
|
|
|
|
+use std::thread::sleep;
|
|
|
use std::time::{Duration, SystemTime};
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
|
|
|
|
use sdl2::event::Event;
|
|
use sdl2::event::Event;
|
|
|
use sdl2::keyboard::Keycode;
|
|
use sdl2::keyboard::Keycode;
|
|
|
use sdl2::pixels::Color;
|
|
use sdl2::pixels::Color;
|
|
|
-use sdl2::rect::Rect;
|
|
|
|
|
use sdl2::render::{Texture, TextureCreator, WindowCanvas};
|
|
use sdl2::render::{Texture, TextureCreator, WindowCanvas};
|
|
|
use sdl2::video::WindowContext;
|
|
use sdl2::video::WindowContext;
|
|
|
|
|
|
|
|
// Crate a texture with 32x32 size
|
|
// Crate a texture with 32x32 size
|
|
|
const TEXTURE_SIZE: u32 = 32;
|
|
const TEXTURE_SIZE: u32 = 32;
|
|
|
const SCORE_FILE_NAME: &str = "scores.txt";
|
|
const SCORE_FILE_NAME: &str = "scores.txt";
|
|
|
|
|
+const LEVEL_TIMES: [u32; 10] = [1000, 850, 700, 600, 500, 400, 300, 250, 221, 190];
|
|
|
|
|
+const LEVEL_LINES: [u32; 10] = [20, 40, 60, 80, 100, 120, 140, 160, 180, 200]; // 需要消除多少行才能升级
|
|
|
|
|
+const NB_HIGHSCORES: usize = 5;
|
|
|
type Piece = Vec<Vec<u8>>;
|
|
type Piece = Vec<Vec<u8>>;
|
|
|
|
|
+type States = Vec<Piece>;
|
|
|
|
|
|
|
|
struct Tetrimino {
|
|
struct Tetrimino {
|
|
|
- states: Vec<Piece>,
|
|
|
|
|
|
|
+ states: States,
|
|
|
x: isize,
|
|
x: isize,
|
|
|
y: usize,
|
|
y: usize,
|
|
|
current_state: u8,
|
|
current_state: u8,
|
|
@@ -325,8 +329,9 @@ impl Tetris {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
/// Remove lines when they're full
|
|
/// Remove lines when they're full
|
|
|
- fn check_line(&mut self) {
|
|
|
|
|
|
|
+ fn check_lines(&mut self) {
|
|
|
let mut y = 0;
|
|
let mut y = 0;
|
|
|
|
|
+ let mut score_add = 0u32;
|
|
|
|
|
|
|
|
while y < self.game_map.len() {
|
|
while y < self.game_map.len() {
|
|
|
let mut complete = true;
|
|
let mut complete = true;
|
|
@@ -339,17 +344,25 @@ impl Tetris {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if complete == true {
|
|
if complete == true {
|
|
|
|
|
+ score_add += self.current_level;
|
|
|
self.game_map.remove(y);
|
|
self.game_map.remove(y);
|
|
|
y -= 1;
|
|
y -= 1;
|
|
|
// increase the number of self.lines
|
|
// increase the number of self.lines
|
|
|
}
|
|
}
|
|
|
y += 1;
|
|
y += 1;
|
|
|
}
|
|
}
|
|
|
|
|
+ if self.game_map.len() == 0 {
|
|
|
|
|
+ // A "tetris"!
|
|
|
|
|
+ score_add += 1000;
|
|
|
|
|
+ }
|
|
|
|
|
+ self.update_score(score_add);
|
|
|
while self.game_map.len() < 16 {
|
|
while self.game_map.len() < 16 {
|
|
|
|
|
+ self.increase_line();
|
|
|
self.game_map.insert(0, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
self.game_map.insert(0, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
fn make_permanent(&mut self) {
|
|
fn make_permanent(&mut self) {
|
|
|
|
|
+ let mut to_add = 0u32;
|
|
|
if let Some(ref mut piece) = self.current_piece {
|
|
if let Some(ref mut piece) = self.current_piece {
|
|
|
let mut shift_y = 0;
|
|
let mut shift_y = 0;
|
|
|
|
|
|
|
@@ -373,10 +386,21 @@ impl Tetris {
|
|
|
|
|
|
|
|
shift_y += 1;
|
|
shift_y += 1;
|
|
|
}
|
|
}
|
|
|
|
|
+ to_add += self.current_level;
|
|
|
}
|
|
}
|
|
|
- self.check_line();
|
|
|
|
|
|
|
+ self.update_score(to_add);
|
|
|
|
|
+ self.check_lines();
|
|
|
self.current_piece = None;
|
|
self.current_piece = None;
|
|
|
}
|
|
}
|
|
|
|
|
+ fn update_score(&mut self, to_add: u32) {
|
|
|
|
|
+ self.score += to_add;
|
|
|
|
|
+ }
|
|
|
|
|
+ fn increase_line(&mut self) {
|
|
|
|
|
+ self.nb_lines += 1;
|
|
|
|
|
+ if self.nb_lines > LEVEL_LINES[self.current_level as usize - 1] {
|
|
|
|
|
+ self.current_level += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[derive(Clone, Copy)]
|
|
@@ -509,55 +533,199 @@ pub fn main() {
|
|
|
)
|
|
)
|
|
|
.expect("Failed to create a texture");
|
|
.expect("Failed to create a texture");
|
|
|
|
|
|
|
|
- let timer = SystemTime::now();
|
|
|
|
|
-
|
|
|
|
|
let mut event_pump = sdl_context
|
|
let mut event_pump = sdl_context
|
|
|
.event_pump()
|
|
.event_pump()
|
|
|
.expect("Failed to get SDL event pump");
|
|
.expect("Failed to get SDL event pump");
|
|
|
- 'running: loop {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ let mut timer = SystemTime::now();
|
|
|
|
|
+ let mut tetris = Tetris::new();
|
|
|
|
|
+
|
|
|
|
|
+ loop {
|
|
|
|
|
+ if match timer.elapsed() {
|
|
|
|
|
+ Ok(elapsed) => elapsed.as_secs() >= 1,
|
|
|
|
|
+ Err(_) => false,
|
|
|
|
|
+ } {
|
|
|
|
|
+ let mut make_permanent = false;
|
|
|
|
|
+ if let Some(ref mut piece) = tetris.current_piece {
|
|
|
|
|
+ let x = piece.x;
|
|
|
|
|
+ let y = piece.y + 1;
|
|
|
|
|
+ make_permanent = !piece.change_position(&tetris.game_map, x, y);
|
|
|
|
|
+ }
|
|
|
|
|
+ if make_permanent {
|
|
|
|
|
+ tetris.make_permanent();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ timer = SystemTime::now();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // We need to draw the tetris "grid" in here.
|
|
|
|
|
+
|
|
|
|
|
+ if tetris.current_piece.is_none() {
|
|
|
|
|
+ let current_piece = tetris.create_new_tetrimino();
|
|
|
|
|
+ if !current_piece.test_current_position(&tetris.game_map) {
|
|
|
|
|
+ print_game_information(&tetris);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ tetris.current_piece = Some(current_piece);
|
|
|
|
|
+ }
|
|
|
|
|
+ let mut quit = false;
|
|
|
|
|
+ if !handle_events(&mut tetris, &mut quit, &mut timer, &mut event_pump) {
|
|
|
|
|
+ if let Some(ref mut piece) = tetris.current_piece {
|
|
|
|
|
+ // We need to draw our current tetrimino in here.
|
|
|
|
|
+ todo!()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if quit {
|
|
|
|
|
+ print_game_information(&tetris);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if is_time_over(&tetris, &timer) {
|
|
|
|
|
+ let mut make_permanent = false;
|
|
|
|
|
+ if let Some(ref mut piece) = tetris.current_piece {
|
|
|
|
|
+ let x = piece.x;
|
|
|
|
|
+ let y = piece.y + 1;
|
|
|
|
|
+ make_permanent = !piece.change_position(&tetris.game_map, x, y);
|
|
|
|
|
+ }
|
|
|
|
|
+ if make_permanent {
|
|
|
|
|
+ tetris.make_permanent();
|
|
|
|
|
+ }
|
|
|
|
|
+ timer = SystemTime::now();
|
|
|
|
|
+ }
|
|
|
|
|
+ // We need to draw the game map in here.
|
|
|
|
|
+
|
|
|
|
|
+ sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+fn is_time_over(tetris: &Tetris, timer: &SystemTime) -> bool {
|
|
|
|
|
+ match timer.elapsed() {
|
|
|
|
|
+ Ok(elapsed) => {
|
|
|
|
|
+ let millis = elapsed.as_secs() as u32 * 1000 + elapsed.subsec_nanos() / 1_000_000;
|
|
|
|
|
+ millis > LEVEL_TIMES[tetris.current_level as usize - 1]
|
|
|
|
|
+ }
|
|
|
|
|
+ Err(_) => false,
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+fn handle_events(
|
|
|
|
|
+ tetris: &mut Tetris,
|
|
|
|
|
+ quit: &mut bool,
|
|
|
|
|
+ timer: &mut SystemTime,
|
|
|
|
|
+ event_pump: &mut sdl2::EventPump,
|
|
|
|
|
+) -> bool {
|
|
|
|
|
+ // The current tetrimino is still falling.
|
|
|
|
|
+ // If not, then it becomes true, the tetrimino is then put into
|
|
|
|
|
+ // the game map and we generate a new one.
|
|
|
|
|
+ let mut make_permanent = false;
|
|
|
|
|
+ if let Some(ref mut piece) = tetris.current_piece {
|
|
|
|
|
+ let mut tmp_x = piece.x;
|
|
|
|
|
+ let mut tmp_y = piece.y;
|
|
|
|
|
+
|
|
|
for event in event_pump.poll_iter() {
|
|
for event in event_pump.poll_iter() {
|
|
|
match event {
|
|
match event {
|
|
|
- // If we receive a 'quit' event or if the user press the
|
|
|
|
|
- // 'ESC' key, we quit.
|
|
|
|
|
Event::Quit { .. }
|
|
Event::Quit { .. }
|
|
|
| Event::KeyDown {
|
|
| Event::KeyDown {
|
|
|
keycode: Some(Keycode::Escape),
|
|
keycode: Some(Keycode::Escape),
|
|
|
..
|
|
..
|
|
|
- } => break 'running,
|
|
|
|
|
|
|
+ } => {
|
|
|
|
|
+ *quit = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ Event::KeyDown {
|
|
|
|
|
+ keycode: Some(Keycode::Down),
|
|
|
|
|
+ ..
|
|
|
|
|
+ } => {
|
|
|
|
|
+ *timer = SystemTime::now();
|
|
|
|
|
+ tmp_y += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ Event::KeyDown {
|
|
|
|
|
+ keycode: Some(Keycode::Right),
|
|
|
|
|
+ ..
|
|
|
|
|
+ } => {
|
|
|
|
|
+ tmp_x += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ Event::KeyDown {
|
|
|
|
|
+ keycode: Some(Keycode::Left),
|
|
|
|
|
+ ..
|
|
|
|
|
+ } => {
|
|
|
|
|
+ tmp_x -= 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ Event::KeyDown {
|
|
|
|
|
+ keycode: Some(Keycode::Up),
|
|
|
|
|
+ ..
|
|
|
|
|
+ } => {
|
|
|
|
|
+ piece.rotate(&tetris.game_map);
|
|
|
|
|
+ }
|
|
|
|
|
+ Event::KeyDown {
|
|
|
|
|
+ keycode: Some(Keycode::Space),
|
|
|
|
|
+ ..
|
|
|
|
|
+ } => {
|
|
|
|
|
+ let x = piece.x;
|
|
|
|
|
+ let mut y = piece.y;
|
|
|
|
|
+ while piece.change_position(&tetris.game_map, x, y + 1) == true {
|
|
|
|
|
+ y += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ make_permanent = true;
|
|
|
|
|
+ }
|
|
|
_ => {}
|
|
_ => {}
|
|
|
}
|
|
}
|
|
|
|
|
+ } // end for
|
|
|
|
|
+ if !make_permanent {
|
|
|
|
|
+ if piece.change_position(&tetris.game_map, tmp_x, tmp_y) == false && tmp_y != piece.y {
|
|
|
|
|
+ make_permanent = true;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- // The rest of the game loop goes here...
|
|
|
|
|
- canvas.set_draw_color(Color::RGB(255, 0, 0));
|
|
|
|
|
- // We draw it
|
|
|
|
|
- canvas.clear();
|
|
|
|
|
-
|
|
|
|
|
- // The rectangle switch happens here:
|
|
|
|
|
- let display_green = match timer.elapsed() {
|
|
|
|
|
- Ok(elapsed) => elapsed.as_secs() % 2 == 0,
|
|
|
|
|
- Err(_) => {
|
|
|
|
|
- // In case of error, we do nothing
|
|
|
|
|
- true
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ if make_permanent {
|
|
|
|
|
+ tetris.make_permanent();
|
|
|
|
|
+ *timer = SystemTime::now();
|
|
|
|
|
+ }
|
|
|
|
|
+ make_permanent
|
|
|
|
|
+}
|
|
|
|
|
+fn update_vec(v: &mut Vec<u32>, value: u32) -> bool {
|
|
|
|
|
+ if v.len() < NB_HIGHSCORES {
|
|
|
|
|
+ v.push(value);
|
|
|
|
|
+ v.sort();
|
|
|
|
|
+ true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ for entry in v.iter_mut() {
|
|
|
|
|
+ if value > *entry {
|
|
|
|
|
+ *entry = value;
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
- };
|
|
|
|
|
- let square_texture = if display_green {
|
|
|
|
|
- &green_square
|
|
|
|
|
- } else {
|
|
|
|
|
- &blue_square
|
|
|
|
|
- };
|
|
|
|
|
- // Copy our texture into the window.
|
|
|
|
|
- canvas
|
|
|
|
|
- .copy(
|
|
|
|
|
- &square_texture,
|
|
|
|
|
- None,
|
|
|
|
|
- Rect::new(0, 0, TEXTURE_SIZE, TEXTURE_SIZE),
|
|
|
|
|
- )
|
|
|
|
|
- .expect("Couldn't copy texture in to window");
|
|
|
|
|
- // We update window's display
|
|
|
|
|
- canvas.present();
|
|
|
|
|
-
|
|
|
|
|
- // We sleep enough to get ~60 fps. If we don't call this,
|
|
|
|
|
- // the program will take 100% of a CPU time.
|
|
|
|
|
- thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+fn print_game_information(tetris: &Tetris) {
|
|
|
|
|
+ let mut new_highest_highscore = true;
|
|
|
|
|
+ let mut new_highest_lines_sent = true;
|
|
|
|
|
+ if let Some((mut highscores, mut lines_sent)) = load_highscores_and_lines() {
|
|
|
|
|
+ new_highest_highscore = update_vec(&mut highscores, tetris.score);
|
|
|
|
|
+ new_highest_lines_sent = update_vec(&mut lines_sent, tetris.nb_lines);
|
|
|
|
|
+ if new_highest_lines_sent || new_highest_highscore {
|
|
|
|
|
+ save_highscore_and_lines(&highscores, &lines_sent);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ save_highscore_and_lines(&[tetris.score], &[tetris.nb_lines]);
|
|
|
}
|
|
}
|
|
|
|
|
+ println!("Game over...");
|
|
|
|
|
+ println!(
|
|
|
|
|
+ "Score: {}{}",
|
|
|
|
|
+ tetris.score,
|
|
|
|
|
+ if new_highest_highscore {
|
|
|
|
|
+ " [NEW HIGHSCORE]"
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ""
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+ println!(
|
|
|
|
|
+ "Number of lines: {}{}",
|
|
|
|
|
+ tetris.nb_lines,
|
|
|
|
|
+ if new_highest_lines_sent {
|
|
|
|
|
+ " [NEW HIGHSCORE]"
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ""
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+ print!("Current level: {}", tetris.current_level);
|
|
|
}
|
|
}
|