|
|
@@ -10,15 +10,17 @@ 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;
|
|
|
|
|
|
-// Crate a texture with 32x32 size
|
|
|
-const TEXTURE_SIZE: u32 = 32;
|
|
|
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 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<Vec<u8>>;
|
|
|
type States = Vec<Piece>;
|
|
|
|
|
|
@@ -27,6 +29,7 @@ struct Tetrimino {
|
|
|
x: isize,
|
|
|
y: usize,
|
|
|
current_state: u8,
|
|
|
+ index: i8,
|
|
|
}
|
|
|
|
|
|
impl Tetrimino {
|
|
|
@@ -35,7 +38,6 @@ impl Tetrimino {
|
|
|
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 {
|
|
|
@@ -100,6 +102,7 @@ impl TetriminoGenerator for TetriminoI {
|
|
|
x: 4,
|
|
|
y: 0,
|
|
|
current_state: 0,
|
|
|
+ index: -1,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -137,6 +140,7 @@ impl TetriminoGenerator for TetriminoJ {
|
|
|
x: 4,
|
|
|
y: 0,
|
|
|
current_state: 0,
|
|
|
+ index: -1,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -174,6 +178,7 @@ impl TetriminoGenerator for TetriminoL {
|
|
|
x: 4,
|
|
|
y: 0,
|
|
|
current_state: 0,
|
|
|
+ index: -1,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -191,6 +196,7 @@ impl TetriminoGenerator for TetriminoO {
|
|
|
x: 5,
|
|
|
y: 0,
|
|
|
current_state: 0,
|
|
|
+ index: -1,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -216,6 +222,7 @@ impl TetriminoGenerator for TetriminoS {
|
|
|
x: 4,
|
|
|
y: 0,
|
|
|
current_state: 0,
|
|
|
+ index: -1,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -241,6 +248,7 @@ impl TetriminoGenerator for TetriminoZ {
|
|
|
x: 4,
|
|
|
y: 0,
|
|
|
current_state: 0,
|
|
|
+ index: -1,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -278,6 +286,7 @@ impl TetriminoGenerator for TetriminoT {
|
|
|
x: 4,
|
|
|
y: 0,
|
|
|
current_state: 0,
|
|
|
+ index: -1,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -402,26 +411,20 @@ impl Tetris {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-#[derive(Clone, Copy)]
|
|
|
-enum TextureColor {
|
|
|
- Green,
|
|
|
- Blue,
|
|
|
-}
|
|
|
-
|
|
|
fn create_texture_rect<'a>(
|
|
|
canvas: &mut WindowCanvas,
|
|
|
texture_creator: &'a TextureCreator<WindowContext>,
|
|
|
- color: TextureColor,
|
|
|
- size: u32,
|
|
|
+ r: u8,
|
|
|
+ g: u8,
|
|
|
+ b: u8,
|
|
|
+ width: u32,
|
|
|
+ height: u32,
|
|
|
) -> Option<Texture<'a>> {
|
|
|
- if let Ok(mut square_texture) = texture_creator.create_texture_target(None, size, size) {
|
|
|
+ if let Ok(mut square_texture) = texture_creator.create_texture_target(None, width, height) {
|
|
|
canvas
|
|
|
.with_texture_canvas(&mut square_texture, |texture| {
|
|
|
- match color {
|
|
|
- TextureColor::Green => texture.set_draw_color(Color::RGB(0, 255, 0)),
|
|
|
- TextureColor::Blue => texture.set_draw_color(Color::RGB(0, 0, 255)),
|
|
|
- }
|
|
|
+ // texture.set_draw_color(Color::RGBA(r, g, b,rand::random::<u8>() % 254 + 1));
|
|
|
+ texture.set_draw_color(Color::RGBA(r, g, b,2));
|
|
|
texture.clear();
|
|
|
})
|
|
|
.expect("Failed to color a texture");
|
|
|
@@ -431,6 +434,90 @@ fn create_texture_rect<'a>(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+fn create_texture_from_text<'a>(
|
|
|
+ texture_creator: &'a TextureCreator<WindowContext>,
|
|
|
+ font: &sdl2::ttf::Font,
|
|
|
+ text: &str,
|
|
|
+ r: u8,
|
|
|
+ g: u8,
|
|
|
+ b: u8,
|
|
|
+) -> Option<Texture<'a>> {
|
|
|
+ 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<Rect> {
|
|
|
+ Some(Rect::new(x, y, text.len() as u32 * 10, 20))
|
|
|
+}
|
|
|
+
|
|
|
+fn display_game_information<'a>(
|
|
|
+ tetris: &Tetris,
|
|
|
+ mut canvas: &mut WindowCanvas,
|
|
|
+ texture_creator: &'a TextureCreator<WindowContext>,
|
|
|
+ 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 border_preview = create_texture_rect(
|
|
|
+ &mut canvas,
|
|
|
+ &texture_creator,
|
|
|
+ 255,
|
|
|
+ 255,
|
|
|
+ 255,
|
|
|
+ TETRIS_HEIGHT as u32 * 4 + 20,
|
|
|
+ TETRIS_HEIGHT as u32 * 10 + 20,
|
|
|
+ )
|
|
|
+ .expect("Failed to create a texture");
|
|
|
+
|
|
|
+ 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");
|
|
|
+
|
|
|
+ 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(
|
|
|
+ &border_preview,
|
|
|
+ None,
|
|
|
+ Rect::new(
|
|
|
+ start_x_point,
|
|
|
+ 195,
|
|
|
+ TETRIS_HEIGHT_MINI as u32 * 4 + 20,
|
|
|
+ TETRIS_HEIGHT_MINI as u32 * 10 + 20,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ .expect("Couldn't copy canvas");
|
|
|
+}
|
|
|
+
|
|
|
/// # 写入文件
|
|
|
/// @content 内容
|
|
|
///
|
|
|
@@ -499,52 +586,86 @@ fn load_highscores_and_lines() -> Option<(Vec<u32>, Vec<u32>)> {
|
|
|
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;
|
|
|
|
|
|
- // Parameters are: title, width, height
|
|
|
let window = video_subsystem
|
|
|
- .window("rust-sdl2 demo", 800, 600)
|
|
|
- .position_centered() // to put it in the middle of the screen
|
|
|
+ .window("Tetris", width, height)
|
|
|
+ .position_centered() // top put it in the middle of the screen
|
|
|
.build() // to create the window
|
|
|
- .unwrap();
|
|
|
+ .expect("Failed to create window");
|
|
|
|
|
|
let mut canvas = window
|
|
|
.into_canvas()
|
|
|
.target_texture()
|
|
|
.present_vsync() // To enable v-sync.
|
|
|
.build()
|
|
|
- .expect("Failed to get SDL event pump");
|
|
|
-
|
|
|
- let texture_creator: TextureCreator<_> = canvas.texture_creator();
|
|
|
+ .expect("Couldn't get window's canvas");
|
|
|
|
|
|
- // Web create a texture with a 32x32 size.
|
|
|
- let green_square = create_texture_rect(
|
|
|
+ let texture_creator = canvas.texture_creator();
|
|
|
+ let grid = create_texture_rect(
|
|
|
&mut canvas,
|
|
|
&texture_creator,
|
|
|
- TextureColor::Green,
|
|
|
- TEXTURE_SIZE,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ TETRIS_HEIGHT as u32 * 10,
|
|
|
+ TETRIS_HEIGHT as u32 * 16,
|
|
|
)
|
|
|
.expect("Failed to create a texture");
|
|
|
-
|
|
|
- let blue_square = create_texture_rect(
|
|
|
+ let border = create_texture_rect(
|
|
|
&mut canvas,
|
|
|
&texture_creator,
|
|
|
- TextureColor::Blue,
|
|
|
- TEXTURE_SIZE,
|
|
|
+ 255,
|
|
|
+ 255,
|
|
|
+ 255,
|
|
|
+ TETRIS_HEIGHT as u32 * 10 + 20,
|
|
|
+ TETRIS_HEIGHT as u32 * 16 + 20,
|
|
|
)
|
|
|
.expect("Failed to create a texture");
|
|
|
|
|
|
- let mut event_pump = sdl_context
|
|
|
- .event_pump()
|
|
|
- .expect("Failed to get SDL event pump");
|
|
|
+ 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 mut timer = SystemTime::now();
|
|
|
- let mut tetris = Tetris::new();
|
|
|
+ let textures = [
|
|
|
+ texture!(255, 69, 69),
|
|
|
+ texture!(255, 220, 69),
|
|
|
+ texture!(237, 150, 37),
|
|
|
+ texture!(171, 99, 237),
|
|
|
+ texture!(77, 149, 239),
|
|
|
+ texture!(39, 218, 225),
|
|
|
+ texture!(45, 216, 47),
|
|
|
+ texture!(255, 255, 255),
|
|
|
+ ];
|
|
|
|
|
|
loop {
|
|
|
- if match timer.elapsed() {
|
|
|
- Ok(elapsed) => elapsed.as_secs() >= 1,
|
|
|
- Err(_) => false,
|
|
|
- } {
|
|
|
+ if is_time_over(&tetris, &timer) {
|
|
|
let mut make_permanent = false;
|
|
|
if let Some(ref mut piece) = tetris.current_piece {
|
|
|
let x = piece.x;
|
|
|
@@ -554,12 +675,39 @@ pub fn main() {
|
|
|
if make_permanent {
|
|
|
tetris.make_permanent();
|
|
|
}
|
|
|
-
|
|
|
timer = SystemTime::now();
|
|
|
}
|
|
|
|
|
|
- // We need to draw the tetris "grid" in here.
|
|
|
+ // 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 = tetris.create_new_tetrimino();
|
|
|
if !current_piece.test_current_position(&tetris.game_map) {
|
|
|
@@ -568,32 +716,73 @@ pub fn main() {
|
|
|
}
|
|
|
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!()
|
|
|
+ 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");
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
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();
|
|
|
+ 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");
|
|
|
}
|
|
|
- timer = SystemTime::now();
|
|
|
}
|
|
|
- // We need to draw the game map in here.
|
|
|
|
|
|
+ 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));
|
|
|
}
|
|
|
}
|