extern crate rand; extern crate sdl2; use std::fs::File; use std::io; use std::io::{Read, Write}; use std::thread::sleep; use std::time::{Duration, SystemTime}; use sdl2::event::Event; use sdl2::keyboard::Keycode; use sdl2::pixels::Color; use sdl2::rect::Rect; use sdl2::render::{Texture, TextureCreator, WindowCanvas}; use sdl2::video::WindowContext; 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] = [10, 40, 60, 80, 100, 120, 140, 160, 180, 200]; // 需要消除多少行才能升级 const NB_HIGHSCORES: usize = 5; const TETRIS_HEIGHT: usize = 42; const TETRIS_HEIGHT_MINI: usize = 26; type Piece = Vec>; type States = Vec; struct Tetrimino { states: States, x: isize, y: usize, current_state: u8, index: u8, } impl Tetrimino { fn rotate(&mut self, game_map: &[Vec]) { let mut tmp_state = self.current_state + 1; if tmp_state as usize >= self.states.len() { tmp_state = 0; } let x_pos = [0, -1, 1, -2, 2, -3]; for x in x_pos.iter() { if self.test_position(game_map, tmp_state as usize, self.x + x, self.y) == true { self.current_state = tmp_state; self.x += *x; break; } } } fn change_position(&mut self, game_map: &[Vec], new_x: isize, new_y: usize) -> bool { if self.test_position(game_map, self.current_state as usize, new_x, new_y) == true { self.x = new_x; self.y = new_y; true } else { false } } fn test_position(&self, game_map: &[Vec], tmp_state: usize, x: isize, y: usize) -> bool { for decal_y in 0..4 { for decal_x in 0..4 { let x = x + decal_x; if self.states[tmp_state][decal_y][decal_x as usize] != 0 && (y + decal_y >= game_map.len() || x < 0 || x as usize >= game_map[y + decal_y].len() || game_map[y + decal_y][x as usize] != 0) { return false; } } } return true; } fn test_current_position(&self, game_map: &[Vec]) -> bool { self.test_position(game_map, self.current_state as usize, self.x, self.y) } } trait TetriminoGenerator { fn new(index: u8) -> Tetrimino; } struct TetriminoI; impl TetriminoGenerator for TetriminoI { fn new(index: u8) -> Tetrimino { Tetrimino { states: vec![ vec![ vec![1, 1, 1, 1], vec![0, 0, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![0, 1, 0, 0], vec![0, 1, 0, 0], vec![0, 1, 0, 0], vec![0, 1, 0, 0], ], ], x: 4, y: 0, current_state: 0, index, } } } struct TetriminoJ; impl TetriminoGenerator for TetriminoJ { fn new(index: u8) -> Tetrimino { Tetrimino { states: vec![ vec![ vec![2, 2, 2, 0], vec![2, 0, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![2, 2, 0, 0], vec![0, 2, 0, 0], vec![0, 2, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![0, 0, 2, 0], vec![2, 2, 2, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![2, 0, 0, 0], vec![2, 0, 0, 0], vec![2, 2, 0, 0], vec![0, 0, 0, 0], ], ], x: 4, y: 0, current_state: 0, index, } } } struct TetriminoL; impl TetriminoGenerator for TetriminoL { fn new(index: u8) -> Tetrimino { Tetrimino { states: vec![ vec![ vec![3, 3, 3, 0], vec![0, 0, 3, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![0, 3, 0, 0], vec![0, 3, 0, 0], vec![3, 3, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![3, 0, 0, 0], vec![3, 3, 3, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![3, 3, 0, 0], vec![3, 0, 0, 0], vec![3, 0, 0, 0], vec![0, 0, 0, 0], ], ], x: 4, y: 0, current_state: 0, index, } } } struct TetriminoO; impl TetriminoGenerator for TetriminoO { fn new(index: u8) -> Tetrimino { Tetrimino { states: vec![vec![ vec![4, 4, 0, 0], vec![4, 4, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ]], x: 5, y: 0, current_state: 0, index, } } } struct TetriminoS; impl TetriminoGenerator for TetriminoS { fn new(index: u8) -> Tetrimino { Tetrimino { states: vec![ vec![ vec![0, 5, 5, 0], vec![5, 5, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![0, 5, 0, 0], vec![0, 5, 5, 0], vec![0, 0, 5, 0], vec![0, 0, 0, 0], ], ], x: 4, y: 0, current_state: 0, index, } } } struct TetriminoZ; impl TetriminoGenerator for TetriminoZ { fn new(index: u8) -> Tetrimino { Tetrimino { states: vec![ vec![ vec![6, 6, 0, 0], vec![0, 6, 6, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![0, 0, 6, 0], vec![0, 6, 6, 0], vec![0, 6, 0, 0], vec![0, 0, 0, 0], ], ], x: 4, y: 0, current_state: 0, index, } } } struct TetriminoT; impl TetriminoGenerator for TetriminoT { fn new(index: u8) -> Tetrimino { Tetrimino { states: vec![ vec![ vec![7, 7, 7, 0], vec![0, 7, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![0, 7, 0, 0], vec![7, 7, 0, 0], vec![0, 7, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![0, 7, 0, 0], vec![7, 7, 7, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0], ], vec![ vec![0, 7, 0, 0], vec![0, 7, 7, 0], vec![0, 7, 0, 0], vec![0, 0, 0, 0], ], ], x: 4, y: 0, current_state: 0, index, } } } struct Tetris { game_map: Vec>, current_level: u32, score: u32, nb_lines: u32, current_piece: Option, next_piece: Option, } impl Tetris { fn new() -> Tetris { let mut game_map = Vec::new(); // 16 行 10 列 for _ in 0..16 { game_map.push(vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); } Tetris { game_map, current_level: 1, score: 0, nb_lines: 0, current_piece: None, next_piece: None, } } /// 随机创建 tetrimino fn create_new_tetrimino(&self, cache_nb: u8) -> Tetrimino { let mut rand_nb: u8; if cache_nb != 255 { rand_nb = cache_nb; } else { static mut PREV: u8 = 7; rand_nb = rand::random::() % 7; if unsafe { PREV } == rand_nb { rand_nb = rand::random::() % 7; } unsafe { PREV = rand_nb; } } match rand_nb { 0 => TetriminoI::new(rand_nb), 1 => TetriminoJ::new(rand_nb), 2 => TetriminoL::new(rand_nb), 3 => TetriminoO::new(rand_nb), 4 => TetriminoS::new(rand_nb), 5 => TetriminoZ::new(rand_nb), 6 => TetriminoT::new(rand_nb), _ => unreachable!(), } } /// Remove lines when they're full fn check_lines(&mut self) { let mut y = 0; let mut score_add = 0u32; while y < self.game_map.len() { let mut complete = true; for x in &self.game_map[y] { if *x == 0 { complete = false; break; } } if complete == true { score_add += self.current_level; self.game_map.remove(y); y -= 1; // increase the number of self.lines } y += 1; } if self.game_map.len() == 0 { // A "tetris"! score_add += 1000; } self.update_score(score_add); 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]); } } fn make_permanent(&mut self) { // println!("不能改变位置,已经到底"); let mut to_add = 0u32; if let Some(ref mut piece) = self.current_piece { let mut shift_y = 0; while shift_y < piece.states[piece.current_state as usize].len() && piece.y + shift_y < self.game_map.len() { let mut shift_x = 0; while shift_x < piece.states[piece.current_state as usize][shift_y].len() && (piece.x + shift_x as isize) < self.game_map[piece.y + shift_y].len() as isize { if piece.states[piece.current_state as usize][shift_y][shift_x] != 0 { let x = piece.x + shift_x as isize; self.game_map[piece.y + shift_y][x as usize] = piece.states[piece.current_state as usize][shift_y][shift_x]; } shift_x += 1; } shift_y += 1; } to_add += self.current_level; } self.update_score(to_add); self.check_lines(); self.current_piece = None; // 生成下一个 self.next_piece = Some(self.create_new_tetrimino(255)); // println!("生成下一个: {}", self.next_piece.as_ref().unwrap().index); } 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; } } } fn create_texture_rect<'a>( canvas: &mut WindowCanvas, texture_creator: &'a TextureCreator, r: u8, g: u8, b: u8, width: u32, height: u32, ) -> Option> { if let Ok(mut square_texture) = texture_creator.create_texture_target(None, width, height) { canvas .with_texture_canvas(&mut square_texture, |texture| { // texture.set_draw_color(Color::RGBA(r, g, b,rand::random::() % 254 + 1)); texture.set_draw_color(Color::RGBA(r, g, b, 255)); texture.clear(); }) .expect("Failed to color a texture"); Some(square_texture) } else { None } } fn create_texture_from_text<'a>( texture_creator: &'a TextureCreator, font: &sdl2::ttf::Font, text: &str, r: u8, g: u8, b: u8, ) -> Option> { if let Ok(surface) = font.render(text).blended(Color::RGB(r, g, b)) { texture_creator.create_texture_from_surface(&surface).ok() } else { None } } fn get_rect_from_text(text: &str, x: i32, y: i32) -> Option { Some(Rect::new(x, y, text.len() as u32 * 10, 20)) } fn display_game_information<'a>( tetris: &Tetris, canvas: &mut WindowCanvas, texture_creator: &'a TextureCreator, font: &sdl2::ttf::Font, start_x_point: i32, ) { let score_text = format!("Score: {}", tetris.score); let lines_sent_text = format!("Lines sent: {}", tetris.nb_lines); let level_text = format!("Level: {}", tetris.current_level); let next_preview = String::from("Next: "); let score = create_texture_from_text(&texture_creator, &font, &score_text, 255, 255, 255) .expect("Cannot render text"); let lines_sent = create_texture_from_text(&texture_creator, &font, &lines_sent_text, 255, 255, 255) .expect("Cannot render text"); let level = create_texture_from_text(&texture_creator, &font, &level_text, 255, 255, 255) .expect("Cannot render text"); let next = create_texture_from_text(&texture_creator, &font, &next_preview, 255, 255, 255) .expect("Cannot render text"); canvas .copy( &score, None, get_rect_from_text(&score_text, start_x_point, 90), ) .expect("Couldn't copy text"); canvas .copy( &lines_sent, None, get_rect_from_text(&lines_sent_text, start_x_point, 125), ) .expect("Couldn't copy text"); canvas .copy( &level, None, get_rect_from_text(&level_text, start_x_point, 160), ) .expect("Couldn't copy text"); canvas .copy( &next, None, get_rect_from_text(&level_text, start_x_point, 195), ) .expect("Couldn't copy text"); } /// # 写入文件 /// @content 内容 /// /// @file_name 文件名 fn write_into_file(content: &str, file_name: &str) -> io::Result<()> { let mut f = File::create(file_name)?; f.write_all(content.as_bytes()) } /// # 读取文件内容 /// file_name 文件名 /// /// Return io::Result fn read_from_file(file_name: &str) -> io::Result { let mut f = File::open(file_name)?; let mut content = String::new(); f.read_to_string(&mut content)?; Ok(content) } /// To keep things simple, we'll have a very simple file format: /// /// 1. On the fire line, we store the best scores /// /// 2. On the second line, we store the highest number of lines. fn slice_to_string(slice: &[u32]) -> String { slice .iter() .map(|highscore| highscore.to_string()) .collect::>() .join(" ") } fn save_highscore_and_lines(highscores: &[u32], number_of_lines: &[u32]) -> bool { let s_highscores = slice_to_string(highscores); let s_number_of_lines = slice_to_string(number_of_lines); write_into_file( &*format!("{}\n{}\n", s_highscores, s_number_of_lines), SCORE_FILE_NAME, ) .is_ok() } /// # Reading formatted data from files fn line_to_slice(line: &str) -> Vec { line.split(" ") .filter_map(|nb| nb.parse::().ok()) .collect() } fn load_highscores_and_lines() -> Option<(Vec, Vec)> { if let Ok(content) = read_from_file(SCORE_FILE_NAME) { let mut lines = content .splitn(2, "\n") .map(|line| line_to_slice(line)) .collect::>(); if lines.len() == 2 { let (number_lines, hightscores) = (lines.pop().unwrap(), lines.pop().unwrap()); Some((hightscores, number_lines)) } else { None } } else { None } } pub fn main() { let sdl_context = sdl2::init().expect("SDL init failed"); let video_subsystem = sdl_context.video().expect("Couldn't get video subsystem"); let ttf_content = sdl2::ttf::init().expect("SDL TTF init failed"); let font = ttf_content .load_font("assets/LucidaGrande.ttf", 30) .expect("couldn't load the font"); // font.set_style(FontStyle::BOLD); let width = 600; let height = 750; let mut timer = SystemTime::now(); let mut tetris = Tetris::new(); let mut event_pump = sdl_context .event_pump() .expect("Failed to get SDL event pump"); let grid_x = 20; let grid_y = (height - TETRIS_HEIGHT as u32 * 16) as i32 / 2; let window = video_subsystem .window("Tetris", width, height) .position_centered() // top put it in the middle of the screen .build() // to create the window .expect("Failed to create window"); let mut canvas = window .into_canvas() .target_texture() .present_vsync() // To enable v-sync. .build() .expect("Couldn't get window's canvas"); let texture_creator = canvas.texture_creator(); let grid = create_texture_rect( &mut canvas, &texture_creator, 0, 0, 0, TETRIS_HEIGHT as u32 * 10, TETRIS_HEIGHT as u32 * 16, ) .expect("Failed to create a texture"); let border = create_texture_rect( &mut canvas, &texture_creator, 255, 255, 255, TETRIS_HEIGHT as u32 * 10 + 20, TETRIS_HEIGHT as u32 * 16 + 20, ) .expect("Failed to create a texture"); macro_rules! texture { ($r:expr, $g:expr, $b:expr) => { create_texture_rect( &mut canvas, &texture_creator, $b, $g, $b, TETRIS_HEIGHT as u32, TETRIS_HEIGHT as u32, ) .unwrap() }; } let textures = [ texture!(255, 215, 0), texture!(34, 139, 34), texture!(135, 206, 235), texture!(201, 105, 30), texture!(77, 149, 239), texture!(39, 218, 225), texture!(45, 216, 47), texture!(255, 255, 255), ]; let next_preview_texture = create_texture_rect( &mut canvas, &texture_creator, 135, 206, 235, TETRIS_HEIGHT_MINI as u32, TETRIS_HEIGHT_MINI as u32, ) .expect("Couldn't create next_texture"); loop { 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. canvas.set_draw_color(Color::RGB(255, 0, 0)); canvas.clear(); // We need to draw the tetris "grid" in here. canvas .copy( &border, None, Rect::new( 10, (height - TETRIS_HEIGHT as u32 * 16) as i32 / 2 - 10, TETRIS_HEIGHT as u32 * 10 + 20, TETRIS_HEIGHT as u32 * 16 + 20, ), ) .expect("Couldn't copy texture into window"); canvas .copy( &grid, None, Rect::new( 20, (height - TETRIS_HEIGHT as u32 * 16) as i32 / 2, TETRIS_HEIGHT as u32 * 10, TETRIS_HEIGHT as u32 * 16, ), ) .expect("Couldn't copy texture into window"); if tetris.current_piece.is_none() { let current_piece; if !tetris.next_piece.is_none() { let i: u8 = tetris.next_piece.as_ref().unwrap().index; current_piece = tetris.create_new_tetrimino(i); } else { current_piece = tetris.create_new_tetrimino(255); } 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. for (line_bn, line) in piece.states[piece.current_state as usize] .iter() .enumerate() { for (case_nb, case) in line.iter().enumerate() { if *case == 0 { continue; } // The new part is here canvas .copy( &textures[*case as usize - 1], None, Rect::new( grid_x + (piece.x + case_nb as isize) as i32 * TETRIS_HEIGHT as i32, grid_y + (piece.y + line_bn) as i32 * TETRIS_HEIGHT as i32, TETRIS_HEIGHT as u32, TETRIS_HEIGHT as u32, ), ) .expect("Couldn't copy texture into window"); } } } // We need to draw our next tetrimino in here. if let Some(ref mut piece) = tetris.next_piece { for (line_bn, line) in piece.states[piece.current_state as usize] .iter() .enumerate() { for (case_nb, case) in line.iter().enumerate() { if *case == 0 { continue; } // The new part is here canvas .copy( &next_preview_texture, None, Rect::new( (TETRIS_HEIGHT as u32 * 8 + grid_x as u32) as i32 + 30 // 每个字符 10(3 个) + (piece.x + case_nb as isize) as i32 * TETRIS_HEIGHT_MINI as i32, (TETRIS_HEIGHT as u32 * 5 + grid_y as u32) as i32 + (piece.y + line_bn) as i32 * TETRIS_HEIGHT_MINI as i32, TETRIS_HEIGHT_MINI as u32, TETRIS_HEIGHT_MINI as u32, ), ) .expect("Couldn't copy texture into window"); } } // println!("grid_y: {}",grid_y); } } if quit { print_game_information(&tetris); break; } for (line_nb, line) in tetris.game_map.iter().enumerate() { for (case_nb, case) in line.iter().enumerate() { if *case == 0 { continue; } canvas .copy( &textures[7], None, Rect::new( grid_x + case_nb as i32 * TETRIS_HEIGHT as i32, grid_y + line_nb as i32 * TETRIS_HEIGHT as i32, TETRIS_HEIGHT as u32, TETRIS_HEIGHT as u32, ), ) .expect("Couldn't copy texture into window"); } } display_game_information( &tetris, &mut canvas, &texture_creator, &font, width as i32 - grid_x - 120, ); canvas.present(); 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. 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() { match event { Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { *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; } } } if make_permanent { tetris.make_permanent(); *timer = SystemTime::now(); } make_permanent } fn update_vec(v: &mut Vec, 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; } } 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); }