diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7b3552c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "cSpell.words": [ + "Mino", + "PLAYFIELD", + "Renderable", + "Tickable", + "tetromino" + ] +} \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index f089010..796470f 100644 --- a/src/game.rs +++ b/src/game.rs @@ -19,7 +19,7 @@ pub struct Game { playfield: PlayField, rotation_system: SRS, level: u8, - points: u32, + score: u32, tick: u64, next_gravity_tick: u64, next_lock_tick: u64, @@ -29,7 +29,7 @@ pub struct Game { impl fmt::Debug for Game { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "level: {}, points: {}", self.level, self.points)?; + writeln!(f, "level: {}, points: {}", self.level, self.score)?; writeln!(f, "tick: {}", self.tick)?; write!(f, "{:?}", self.playfield) } @@ -41,7 +41,7 @@ impl Default for Game { playfield: PlayField::new(), rotation_system: SRS::default(), level: 1, - points: 0, + score: 0, tick: 0, next_gravity_tick: 60, next_lock_tick: 0, @@ -65,17 +65,7 @@ impl Tickable for Game { match self.tick { t if t == self.next_spawn_tick => self.spawn_tetromino(), t if t == self.next_lock_tick => { - // It's possible that the player moved the piece in the meantime. - if !self.playfield.can_active_piece_move_down() { - let positions = self.playfield.lock_active_piece(); - self.is_game_over = - self.is_game_over || positions.iter().all(|Position { x: _, y }| *y < 20); - if self.clear_lines() { - self.next_spawn_tick = self.tick + LINE_CLEAR_DELAY; - } else { - self.spawn_tetromino(); - } - } + self.try_lock_tetromino(); } t if t == self.next_gravity_tick => { self.playfield.tick_gravity(); @@ -95,7 +85,7 @@ impl Game { } fn update_gravity_tick(&mut self) { - self.next_gravity_tick = self.tick + TICKS_PER_SECOND as u64; + self.next_gravity_tick = (-1 as i64) as u64; //self.tick + TICKS_PER_SECOND as u64; } fn update_lock_tick(&mut self) { @@ -113,6 +103,23 @@ impl Game { // todo: award points based on how lines were cleared return false; } + + fn try_lock_tetromino(&mut self) -> bool { + // It's possible that the player moved the piece in the meantime. + if !self.playfield.can_active_piece_move_down() { + let positions = self.playfield.lock_active_piece(); + self.is_game_over = + self.is_game_over || positions.iter().all(|Position { x: _, y }| *y < 20); + if self.clear_lines() { + self.next_spawn_tick = self.tick + LINE_CLEAR_DELAY; + } else { + self.spawn_tetromino(); + } + true + } else { + false + } + } } impl Renderable for Game { @@ -123,7 +130,6 @@ impl Renderable for Game { pub trait Controllable { fn move_left(&mut self); - fn move_up(&mut self); fn move_right(&mut self); fn move_down(&mut self); fn rotate_left(&mut self); @@ -136,23 +142,49 @@ impl Controllable for Game { fn move_left(&mut self) { self.playfield.move_offset(-1, 0); } - fn move_up(&mut self) { - // self.playfield.move_up(); - } fn move_right(&mut self) { self.playfield.move_offset(1, 0); } fn move_down(&mut self) { - // self.playfield.move_down(); + if self.playfield.move_offset(0, 1) { + self.score += 1; + self.update_gravity_tick(); + self.update_lock_tick(); + } } fn rotate_left(&mut self) { - // self.playfield.rotate_left(); + match self.rotation_system.rotate_left(&self.playfield) { + Ok(Position { x, y }) => { + let mut active_piece = self.playfield.active_piece.unwrap().clone(); + active_piece.position = active_piece.position.offset(x, y); + active_piece.rotate_left(); + self.playfield.active_piece = Some(active_piece); + self.update_lock_tick(); + } + Err(_) => (), + } } fn rotate_right(&mut self) { - // self.playfield.rotate_right(); + match self.rotation_system.rotate_right(&self.playfield) { + Ok(Position { x, y }) => { + let mut active_piece = self.playfield.active_piece.unwrap().clone(); + active_piece.position = active_piece.position.offset(x, y); + active_piece.rotate_right(); + self.playfield.active_piece = Some(active_piece); + self.update_lock_tick(); + } + Err(_) => (), + } } fn hard_drop(&mut self) { - // self.playfield.hard_drop(); + while self.playfield.can_active_piece_move_down() { + self.score += 2; + self.playfield.move_offset(0, 1); + } + + if !self.try_lock_tetromino() { + println!("couldn't lock tetromino despite hard dropping!"); + } } fn hold(&mut self) { diff --git a/src/graphics.rs b/src/graphics.rs index 63771e0..61fa19e 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,6 +1,8 @@ use sdl2::pixels::Color; pub const CELL_SIZE: u32 = 32; +pub const BORDER_RADIUS: u32 = 1; + pub static COLOR_BACKGROUND: Color = Color::RGB(60, 60, 60); pub static COLOR_CYAN: Color = Color::RGB(0, 255, 255); pub static COLOR_YELLOW: Color = Color::RGB(255, 255, 0); diff --git a/src/main.rs b/src/main.rs index d26d502..ea88917 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,10 +50,6 @@ async fn main() -> Result<(), Box> { keycode: Some(Keycode::Right), .. } => game.move_right(), - Event::KeyDown { - keycode: Some(Keycode::Up), - .. - } => game.move_up(), Event::KeyDown { keycode: Some(Keycode::Down), .. @@ -69,6 +65,10 @@ async fn main() -> Result<(), Box> { Event::KeyDown { keycode: Some(Keycode::Space), .. + } + | Event::KeyDown { + keycode: Some(Keycode::Up), + .. } => game.hard_drop(), Event::KeyDown { keycode: Some(Keycode::LShift), @@ -85,5 +85,6 @@ async fn main() -> Result<(), Box> { interval.tick().await; } + dbg!(game); Ok(()) } diff --git a/src/playfield.rs b/src/playfield.rs index d1921b4..c4b5f88 100644 --- a/src/playfield.rs +++ b/src/playfield.rs @@ -1,7 +1,6 @@ -use crate::graphics::{CELL_SIZE, COLOR_BACKGROUND}; +use crate::graphics::{BORDER_RADIUS, CELL_SIZE, COLOR_BACKGROUND}; use crate::random::RandomSystem; -use crate::tetromino::Position; -use crate::tetromino::{MinoColor, Tetromino, TetrominoType}; +use crate::tetromino::{MinoColor, Position, Tetromino, TetrominoType}; use crate::Renderable; use sdl2::{pixels::Color, rect::Rect, render::Canvas, video::Window}; use std::collections::VecDeque; @@ -22,7 +21,7 @@ pub struct PlayField { can_swap_hold: bool, hold_piece: Option, field: Matrix, - active_piece: Option, + pub active_piece: Option, bag: RandomSystem, next_pieces: VecDeque, last_movement: Movement, @@ -53,7 +52,7 @@ impl fmt::Debug for PlayField { ('a' as usize - PLAYFIELD_HEIGHT + y) as u8 as char )?; for x in 0..PLAYFIELD_WIDTH { - if occupied_spaces.contains(&Position::new(x, y)) { + if occupied_spaces.contains(&Position::new(x as isize, y as isize)) { write!(f, "#")?; } else { match self.field[y][x] { @@ -80,10 +79,10 @@ impl fmt::Debug for PlayField { impl PlayField { pub fn new() -> Self { let mut bag = RandomSystem::new(); - let active_piece = Tetromino::from(bag.get_tetrino()); + let active_piece = Tetromino::from(bag.get_tetromino()); let mut next_pieces = VecDeque::with_capacity(3); for _ in 0..next_pieces.capacity() { - next_pieces.push_back(bag.get_tetrino()); + next_pieces.push_back(bag.get_tetromino()); } PlayField { @@ -101,8 +100,8 @@ impl PlayField { if self.can_move_offset(x, y) { match self.active_piece { Some(mut piece) => { - piece.position.x = (x + piece.position.x as isize) as usize; - piece.position.y = (y + piece.position.y as isize) as usize; + piece.position.x += x; + piece.position.y += y; self.active_piece = Some(piece); true } @@ -119,8 +118,8 @@ impl PlayField { .get_occupied_spaces(piece.position.offset(x, y)) .iter() .fold(true, |acc, pos| { - acc && pos.y < self.field.len() - && pos.x < PLAYFIELD_WIDTH + acc && (pos.y as usize) < self.field.len() + && (pos.x as usize) < PLAYFIELD_WIDTH && self.field[pos.y as usize][pos.x as usize].is_none() }), None => false, @@ -133,7 +132,7 @@ impl PlayField { .pop_front() .expect("visible queue to be populated"), )); - self.next_pieces.push_back(self.bag.get_tetrino()); + self.next_pieces.push_back(self.bag.get_tetromino()); self.can_swap_hold = true; } @@ -154,7 +153,7 @@ impl PlayField { p.get_falling_occupied_spaces() .iter() .fold(true, |acc, pos| { - acc && pos.y < self.field.len() + acc && (pos.y as usize) < self.field.len() && self.field[pos.y as usize][pos.x as usize].is_none() }), ) @@ -168,7 +167,7 @@ impl PlayField { let active_color = active_piece.get_color(); let new_pieces = active_piece.get_cur_occupied_spaces(); for Position { x, y } in &new_pieces { - self.field[*y][*x] = Some(active_color); + self.field[*y as usize][*x as usize] = Some(active_color); } self.active_piece = None; @@ -180,10 +179,23 @@ impl PlayField { pub fn is_active_piece_in_valid_position(&self) -> bool { match self.active_piece { - Some(active_piece) => active_piece.get_cur_occupied_spaces().iter().all(|Position {x, y}| self.field[*y][*x].is_none()), + Some(active_piece) => { + self.can_piece_be_at_position(&active_piece) + }, None => panic!("Tried checking if active piece is in a valid position but active piece doesn't exist") } } + + pub fn can_piece_be_at_position(&self, tetromino: &Tetromino) -> bool { + tetromino + .get_cur_occupied_spaces() + .iter() + .all(|Position { x, y }| { + (*y as usize) < self.field.len() + && (*x as usize) < PLAYFIELD_WIDTH + && self.field[*y as usize][*x as usize].is_none() + }) + } } impl Renderable for PlayField { @@ -203,10 +215,10 @@ impl Renderable for PlayField { None => canvas.set_draw_color(COLOR_BACKGROUND), } canvas.fill_rect(Rect::new( - CELL_SIZE as i32 * x as i32 + 2, - CELL_SIZE as i32 * y as i32 + 2, - 28, - 28, + CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32, + CELL_SIZE as i32 * y as i32 + BORDER_RADIUS as i32, + CELL_SIZE - 2 * BORDER_RADIUS, + CELL_SIZE - 2 * BORDER_RADIUS, ))?; } } diff --git a/src/random.rs b/src/random.rs index ffc4001..abbe3a9 100644 --- a/src/random.rs +++ b/src/random.rs @@ -17,7 +17,7 @@ impl RandomSystem { } } - pub fn get_tetrino(&mut self) -> TetrominoType { + pub fn get_tetromino(&mut self) -> TetrominoType { if self.cur_pos == 0 { self.refresh_bag(); } diff --git a/src/srs.rs b/src/srs.rs index 089a95e..f6dff90 100644 --- a/src/srs.rs +++ b/src/srs.rs @@ -1,123 +1,158 @@ -use crate::playfield::PlayField; -use crate::tetromino::{Position, TetrominoType}; - -#[derive(Copy, Clone)] -pub struct Offset { - x: i8, - y: i8, -} - -pub enum RotationDirection { - Clockwise, - AntiClockwise, -} +use crate::playfield::{Matrix, PlayField}; +use crate::tetromino::{Position, RotationState, Tetromino, TetrominoType}; +use std::collections::HashMap; pub trait RotationSystem { fn default() -> Self; - fn get_rotation_offset( - piece: &TetrominoType, - center: &Position, - direction: RotationDirection, - playfield: &PlayField, - ) -> Option; + fn rotate_left(&self, playfield: &PlayField) -> Result; + + fn rotate_right(&self, playfield: &PlayField) -> Result; } -pub struct SRS {} +pub struct SRS { + jlstz_offset_data: HashMap>, + i_offset_data: HashMap>, + o_offset_data: HashMap>, +} + +enum RotationDirection { + Left, + Right, +} impl SRS { - pub fn new() -> Self { - Self {} + fn rotate_common( + &self, + playfield: &PlayField, + rotation: RotationDirection, + ) -> Result { + let active_piece = match playfield.active_piece { + Some(piece) => piece, + None => return Err(()), + }; + let offset_data = match active_piece.piece_type { + TetrominoType::I => &self.i_offset_data, + TetrominoType::O => &self.o_offset_data, + _ => &self.jlstz_offset_data, + }; + let prev_offsets = offset_data.get(&active_piece.rotation_state).unwrap(); + + let mut test_tetromino = active_piece.clone(); + match rotation { + RotationDirection::Left => test_tetromino.rotate_left(), + RotationDirection::Right => test_tetromino.rotate_right(), + } + + let cur_offsets = offset_data.get(&test_tetromino.rotation_state).unwrap(); + + let mut offsets = Vec::with_capacity(cur_offsets.len()); + + for i in 0..cur_offsets.len() { + offsets.push(prev_offsets[i] - cur_offsets[i]); + } + + for offset in offsets { + let x = offset.x; + let y = offset.y; + let test_position = active_piece.position.offset(x, y); + test_tetromino.position = test_position; + if playfield.can_piece_be_at_position(&test_tetromino) { + return Ok(offset); + } + } + + Err(()) } } impl RotationSystem for SRS { fn default() -> Self { - SRS::new() + let mut jlstz_offset_data = HashMap::with_capacity(4); + let mut i_offset_data = HashMap::with_capacity(4); + let mut o_offset_data = HashMap::with_capacity(4); + + jlstz_offset_data.insert(RotationState::O, vec![Position { x: 0, y: 0 }].repeat(5)); + jlstz_offset_data.insert( + RotationState::R, + vec![ + Position { x: 0, y: 0 }, + Position { x: 1, y: 0 }, + Position { x: 1, y: 1 }, + Position { x: 0, y: -2 }, + Position { x: 1, y: -2 }, + ], + ); + jlstz_offset_data.insert(RotationState::U, vec![Position { x: 0, y: 0 }].repeat(5)); + jlstz_offset_data.insert( + RotationState::L, + vec![ + Position { x: 0, y: 0 }, + Position { x: -1, y: 0 }, + Position { x: -1, y: 1 }, + Position { x: 0, y: -2 }, + Position { x: -1, y: -2 }, + ], + ); + + i_offset_data.insert( + RotationState::O, + vec![ + Position { x: 0, y: 0 }, + Position { x: -1, y: 0 }, + Position { x: 2, y: 0 }, + Position { x: -1, y: 0 }, + Position { x: 2, y: 0 }, + ], + ); + i_offset_data.insert( + RotationState::R, + vec![ + Position { x: -1, y: 0 }, + Position { x: 0, y: 0 }, + Position { x: 0, y: 0 }, + Position { x: 0, y: -1 }, + Position { x: 0, y: 2 }, + ], + ); + i_offset_data.insert( + RotationState::U, + vec![ + Position { x: -1, y: -1 }, + Position { x: 1, y: -1 }, + Position { x: -2, y: -1 }, + Position { x: 1, y: 0 }, + Position { x: -2, y: 0 }, + ], + ); + i_offset_data.insert( + RotationState::L, + vec![ + Position { x: 0, y: -1 }, + Position { x: 0, y: -1 }, + Position { x: 0, y: -1 }, + Position { x: 0, y: 1 }, + Position { x: 0, y: -2 }, + ], + ); + + o_offset_data.insert(RotationState::O, vec![Position { x: 0, y: 0 }]); + o_offset_data.insert(RotationState::R, vec![Position { x: 0, y: 1 }]); + o_offset_data.insert(RotationState::U, vec![Position { x: -1, y: 1 }]); + o_offset_data.insert(RotationState::L, vec![Position { x: -1, y: 0 }]); + + SRS { + jlstz_offset_data, + i_offset_data, + o_offset_data, + } } - fn get_rotation_offset( - piece: &TetrominoType, - center: &Position, - direction: RotationDirection, - playfield: &PlayField, - ) -> Option { - None + fn rotate_left(&self, playfield: &PlayField) -> Result { + self.rotate_common(playfield, RotationDirection::Left) + } + + fn rotate_right(&self, playfield: &PlayField) -> Result { + self.rotate_common(playfield, RotationDirection::Right) } } - -#[derive(PartialEq, Eq, Hash)] -enum Rotation { - O, // Spawn state - R, // Right rotation: clockwise rotation from spawn state - U, // Upside-down rotation: rotation after 2 left or right rotations from spawn state - L, // Left rotation: counterclockwise rotation from spawn state -} - -struct OffsetData { - // O: &[Offset], -// R: &[Offset], -// U: &[Offset], -// L: &[Offset], -} - -impl OffsetData { - pub fn apply_right_rotation() {} -} - -// static JLSTZOffsetData: OffsetData = OffsetData { -// O: vec![Offset { x: 0, y: 0 }], -// R: vec![ -// Offset { x: 0, y: 0 }, -// Offset { x: 1, y: 0 }, -// Offset { x: 1, y: -1 }, -// Offset { x: 0, y: 2 }, -// Offset { x: 1, y: 2 }, -// ], -// U: vec![Offset { x: 0, y: 0 }], -// L: vec![ -// Offset { x: 0, y: 0 }, -// Offset { x: -1, y: 0 }, -// Offset { x: -1, y: -1 }, -// Offset { x: 0, y: 2 }, -// Offset { x: -1, y: 2 }, -// ], -// }; - -// static IOffsetData: OffsetData = OffsetData { -// O: vec![ -// Offset { x: 0, y: 0 }, -// Offset { x: -1, y: 0 }, -// Offset { x: 2, y: 0 }, -// Offset { x: -1, y: 0 }, -// Offset { x: 2, y: 0 }, -// ], -// R: vec![ -// Offset { x: -1, y: 0 }, -// Offset { x: 0, y: 0 }, -// Offset { x: 0, y: 0 }, -// Offset { x: 0, y: 1 }, -// Offset { x: 0, y: -2 }, -// ], -// U: vec![ -// Offset { x: -1, y: 1 }, -// Offset { x: 1, y: 1 }, -// Offset { x: -2, y: 1 }, -// Offset { x: 1, y: 0 }, -// Offset { x: -2, y: 0 }, -// ], -// L: vec![ -// Offset { x: 0, y: 1 }, -// Offset { x: 0, y: 1 }, -// Offset { x: 0, y: 1 }, -// Offset { x: 0, y: -1 }, -// Offset { x: 0, y: 2 }, -// ], -// }; - -// static OOffsetData: OffsetData = OffsetData { -// O: vec![Offset { x: 0, y: 0 }], -// R: vec![Offset { x: 0, y: -1 }], -// U: vec![Offset { x: -1, y: -1 }], -// L: vec![Offset { x: -1, y: 0 }], -// }; diff --git a/src/tetromino.rs b/src/tetromino.rs index 05821f3..d2484ab 100644 --- a/src/tetromino.rs +++ b/src/tetromino.rs @@ -63,22 +63,22 @@ pub enum TetrominoType { L, } -impl Into for TetrominoType { - fn into(self) -> Color { +impl Into for TetrominoType { + fn into(self) -> MinoColor { match self { - Self::I => COLOR_CYAN, - Self::O => COLOR_YELLOW, - Self::T => COLOR_PURPLE, - Self::S => COLOR_GREEN, - Self::Z => COLOR_RED, - Self::J => COLOR_BLUE, - Self::L => COLOR_ORANGE, + Self::I => MinoColor::Cyan, + Self::O => MinoColor::Yellow, + Self::T => MinoColor::Purple, + Self::S => MinoColor::Green, + Self::Z => MinoColor::Red, + Self::J => MinoColor::Blue, + Self::L => MinoColor::Orange, } } } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum RotationState { +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum RotationState { O, // initial state R, // clockwise rotation L, // counter-clockwise rotation @@ -93,22 +93,42 @@ impl Default for RotationState { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Position { - pub x: usize, - pub y: usize, + pub x: isize, + pub y: isize, } impl Position { - pub fn new(x: usize, y: usize) -> Position { + pub fn new(x: isize, y: isize) -> Position { Self { - x: x as usize, - y: y as usize, + x: x as isize, + y: y as isize, } } pub fn offset(&self, x: isize, y: isize) -> Position { Self { - x: (x + self.x as isize) as usize, - y: (y + self.y as isize) as usize, + x: x + self.x, + y: y + self.y, + } + } +} + +impl std::ops::Add for Position { + type Output = Self; + fn add(self, other: Self) -> Self { + Self { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl std::ops::Sub for Position { + type Output = Self; + fn sub(self, other: Self) -> Self { + Self { + x: self.x - other.x, + y: self.y - other.y, } } } @@ -117,7 +137,7 @@ impl Position { pub struct Tetromino { pub position: Position, pub piece_type: TetrominoType, - rotation_state: RotationState, + pub rotation_state: RotationState, } impl Tetromino { @@ -143,9 +163,12 @@ impl Tetromino { fn get_start_position(tetromino_type: TetrominoType) -> Position { if tetromino_type == TetrominoType::I { - Position::new(PLAYFIELD_WIDTH / 2 - 1, PLAYFIELD_HEIGHT - 1) + Position::new( + PLAYFIELD_WIDTH as isize / 2 - 1, + PLAYFIELD_HEIGHT as isize - 1, + ) } else { - Position::new(PLAYFIELD_WIDTH / 2 - 1, PLAYFIELD_HEIGHT) + Position::new(PLAYFIELD_WIDTH as isize / 2 - 1, PLAYFIELD_HEIGHT as isize) } } @@ -171,8 +194,16 @@ impl Tetromino { center.offset(0, 1), center.offset(0, 2), ]), - RotationState::L => todo!(), - RotationState::U => todo!(), + RotationState::U => spaces.extend_from_slice(&[ + center.offset(-2, 0), + center.offset(-1, 0), + center.offset(1, 0), + ]), + RotationState::L => spaces.extend_from_slice(&[ + center.offset(0, -2), + center.offset(0, -1), + center.offset(0, 1), + ]), }, TetrominoType::J => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ @@ -180,9 +211,21 @@ impl Tetromino { center.offset(-1, 0), center.offset(1, 0), ]), - RotationState::R => todo!(), - RotationState::L => todo!(), - RotationState::U => todo!(), + RotationState::R => spaces.extend_from_slice(&[ + center.offset(0, -1), + center.offset(1, -1), + center.offset(0, 1), + ]), + RotationState::U => spaces.extend_from_slice(&[ + center.offset(-1, 0), + center.offset(1, 0), + center.offset(1, 1), + ]), + RotationState::L => spaces.extend_from_slice(&[ + center.offset(0, -1), + center.offset(-1, 1), + center.offset(0, 1), + ]), }, TetrominoType::L => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ @@ -190,9 +233,21 @@ impl Tetromino { center.offset(-1, 0), center.offset(1, 0), ]), - RotationState::R => todo!(), - RotationState::L => todo!(), - RotationState::U => todo!(), + RotationState::R => spaces.extend_from_slice(&[ + center.offset(0, -1), + center.offset(0, 1), + center.offset(1, 1), + ]), + RotationState::U => spaces.extend_from_slice(&[ + center.offset(-1, 0), + center.offset(1, 0), + center.offset(-1, 1), + ]), + RotationState::L => spaces.extend_from_slice(&[ + center.offset(-1, -1), + center.offset(0, -1), + center.offset(0, 1), + ]), }, TetrominoType::O => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ @@ -200,9 +255,21 @@ impl Tetromino { center.offset(1, -1), center.offset(1, 0), ]), - RotationState::R => todo!(), - RotationState::L => todo!(), - RotationState::U => todo!(), + RotationState::R => spaces.extend_from_slice(&[ + center.offset(1, 0), + center.offset(0, 1), + center.offset(1, 1), + ]), + RotationState::U => spaces.extend_from_slice(&[ + center.offset(-1, 0), + center.offset(-1, 1), + center.offset(0, 1), + ]), + RotationState::L => spaces.extend_from_slice(&[ + center.offset(-1, -1), + center.offset(0, -1), + center.offset(-1, 0), + ]), }, TetrominoType::S => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ @@ -210,9 +277,21 @@ impl Tetromino { center.offset(1, -1), center.offset(-1, 0), ]), - RotationState::R => todo!(), - RotationState::L => todo!(), - RotationState::U => todo!(), + RotationState::R => spaces.extend_from_slice(&[ + center.offset(0, -1), + center.offset(1, 0), + center.offset(1, 1), + ]), + RotationState::U => spaces.extend_from_slice(&[ + center.offset(0, 1), + center.offset(1, 0), + center.offset(-1, 1), + ]), + RotationState::L => spaces.extend_from_slice(&[ + center.offset(-1, -1), + center.offset(-1, 0), + center.offset(0, 1), + ]), }, TetrominoType::T => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ @@ -220,9 +299,21 @@ impl Tetromino { center.offset(-1, 0), center.offset(1, 0), ]), - RotationState::R => todo!(), - RotationState::L => todo!(), - RotationState::U => todo!(), + RotationState::R => spaces.extend_from_slice(&[ + center.offset(0, -1), + center.offset(0, 1), + center.offset(1, 0), + ]), + RotationState::U => spaces.extend_from_slice(&[ + center.offset(0, 1), + center.offset(-1, 0), + center.offset(1, 0), + ]), + RotationState::L => spaces.extend_from_slice(&[ + center.offset(0, -1), + center.offset(-1, 0), + center.offset(0, 1), + ]), }, TetrominoType::Z => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ @@ -230,14 +321,44 @@ impl Tetromino { center.offset(0, -1), center.offset(1, 0), ]), - RotationState::R => todo!(), - RotationState::L => todo!(), - RotationState::U => todo!(), + RotationState::R => spaces.extend_from_slice(&[ + center.offset(1, -1), + center.offset(1, 0), + center.offset(0, 1), + ]), + RotationState::U => spaces.extend_from_slice(&[ + center.offset(-1, 0), + center.offset(0, 1), + center.offset(1, 1), + ]), + RotationState::L => spaces.extend_from_slice(&[ + center.offset(0, -1), + center.offset(-1, 0), + center.offset(-1, 1), + ]), }, } spaces } + + pub fn rotate_left(&mut self) { + self.rotation_state = match self.rotation_state { + RotationState::O => RotationState::L, + RotationState::L => RotationState::U, + RotationState::U => RotationState::R, + RotationState::R => RotationState::O, + } + } + + pub fn rotate_right(&mut self) { + self.rotation_state = match self.rotation_state { + RotationState::O => RotationState::R, + RotationState::R => RotationState::U, + RotationState::U => RotationState::L, + RotationState::L => RotationState::O, + } + } } impl From for Tetromino { @@ -249,9 +370,14 @@ impl From for Tetromino { impl Renderable for Tetromino { fn render(&self, canvas: &mut Canvas) -> Result<(), String> { for Position { x, y } in self.get_cur_occupied_spaces() { - canvas.set_draw_color(self.piece_type); + canvas.set_draw_color::(self.piece_type.into()); let height = y as isize - PLAYFIELD_HEIGHT as isize; - canvas.fill_rect(Rect::new(32 * x as i32 + 2, 32 * height as i32 + 2, 28, 28))?; + canvas.fill_rect(Rect::new( + CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32, + CELL_SIZE as i32 * height as i32 + BORDER_RADIUS as i32, + CELL_SIZE - 2 * BORDER_RADIUS, + CELL_SIZE - 2 * BORDER_RADIUS, + ))?; } Ok(()) }