diff --git a/iced-tetris/src/main.rs b/iced-tetris/src/main.rs index 27ae81d..5ffef32 100644 --- a/iced-tetris/src/main.rs +++ b/iced-tetris/src/main.rs @@ -6,6 +6,9 @@ use iced::{ use iced_native::{event, subscription, Event}; use rand::distributions::{Distribution, Standard}; +const START_LOCATION: (u8, u8) = (5, 1); +type PieceBlocks = [(i8, i8); 4]; + fn main() -> iced::Result { Tetris::run(Settings::default()) } @@ -49,7 +52,9 @@ impl Application for Tetris { self.ticks += 1 } } - Message::Up => (), + Message::Up => { + self.blocks.rotate_active_piece(); + } Message::Down => { self.blocks.move_active_piece(MoveDirection::HardDrop); } @@ -164,6 +169,7 @@ struct BlockGrid { struct ActivePiece { location: (u8, u8), tetromino: Tetromino, + orientation: Orientation, } impl ActivePiece { @@ -172,6 +178,7 @@ impl ActivePiece { let (cur_x, cur_y) = self.location; ActivePiece { tetromino: self.tetromino, + orientation: self.orientation, location: match direction { Left => (cur_x.checked_sub(1).unwrap_or(0), cur_y), Right => (cur_x + 1, cur_y), @@ -180,6 +187,19 @@ impl ActivePiece { }, } } + + fn rotate_piece(&self) -> ActivePiece { + ActivePiece { + tetromino: self.tetromino, + location: self.location, + orientation: match self.orientation { + Orientation::A => Orientation::B, + Orientation::B => Orientation::C, + Orientation::C => Orientation::D, + Orientation::D => Orientation::A, + }, + } + } } impl BlockGrid { @@ -195,11 +215,12 @@ impl BlockGrid { fn drop_piece(&mut self, tetromino: Tetromino) -> bool { if let None = self.active_piece { let piece = ActivePiece { - location: (4, 0), + location: START_LOCATION, tetromino, + orientation: Orientation::A, }; let piece_blocks = Self::piece_blocks(&piece); - if self.piece_blocks_in_bounds(&piece_blocks) { + if self.piece_blocks_in_bounds(piece_blocks) { self.active_piece = Some(piece); true } else { @@ -210,11 +231,11 @@ impl BlockGrid { } } - fn piece_blocks_in_bounds(&self, piece_blocks: &[(u8, u8); 4]) -> bool { + fn piece_blocks_in_bounds(&self, piece_blocks: PieceBlocks) -> bool { piece_blocks.iter().all(|(x, y)| { let x = *x; let y = *y; - x < 10 && y < 20 && self.state[x as usize][y as usize].is_none() + x >= 0 && y >= 0 && x < 10 && y < 20 && self.state[x as usize][y as usize].is_none() }) } @@ -227,7 +248,7 @@ impl BlockGrid { (Some(piece), MoveDirection::Left | MoveDirection::Right) => { let new_piece = piece.move_piece(&direction); let new_blocks = Self::piece_blocks(&new_piece); - if self.piece_blocks_in_bounds(&new_blocks) { + if self.piece_blocks_in_bounds(new_blocks) { self.active_piece = Some(new_piece); true } else { @@ -239,7 +260,7 @@ impl BlockGrid { loop { let p = new_piece.move_piece(&MoveDirection::SoftDrop); let new_blocks = Self::piece_blocks(&p); - if self.piece_blocks_in_bounds(&new_blocks) { + if self.piece_blocks_in_bounds(new_blocks) { new_piece = p; } else { break; @@ -253,7 +274,7 @@ impl BlockGrid { (Some(ref piece), MoveDirection::SoftDrop) => { let new_piece = piece.move_piece(&MoveDirection::SoftDrop); let new_blocks = Self::piece_blocks(&new_piece); - if self.piece_blocks_in_bounds(&new_blocks) { + if self.piece_blocks_in_bounds(new_blocks) { self.active_piece = Some(new_piece); true } else { @@ -264,6 +285,22 @@ impl BlockGrid { } } + fn rotate_active_piece(&mut self) -> bool { + let active = self.active_piece; + if let Some(piece) = active { + let new_piece = piece.rotate_piece(); + let new_blocks = Self::piece_blocks(&new_piece); + if self.piece_blocks_in_bounds(new_blocks) { + self.active_piece = Some(new_piece); + true + } else { + false + } + } else { + false + } + } + /// Remove the currently active piece and place its blocks onto the board. fn place_active_piece(&mut self) { if let Some(piece) = self.active_piece { @@ -277,29 +314,49 @@ impl BlockGrid { } } - fn piece_blocks(piece: &ActivePiece) -> [(u8, u8); 4] { + fn piece_blocks(piece: &ActivePiece) -> PieceBlocks { + use Orientation::*; use Tetromino::*; let (x, y) = piece.location; + let (x, y) = (x as i8, y as i8); - match piece.tetromino { - I => [(x, y), (x, y + 1), (x, y + 2), (x, y + 3)], - J => [(x, y), (x, y + 1), (x, y + 2), (x - 1, y + 2)], - L => [(x, y), (x, y + 1), (x, y + 2), (x + 1, y + 2)], - O => [(x, y), (x + 1, y), (x, y + 1), (x + 1, y + 1)], - S => [(x, y + 1), (x + 1, y + 1), (x + 1, y), (x + 2, y)], - T => [(x, y), (x + 1, y), (x + 2, y), (x + 1, y + 1)], - Z => [(x, y), (x + 1, y), (x + 1, y + 1), (x + 2, y + 1)], + // These use the "Original Rotation System" cf. Tetris wiki + match (piece.tetromino, piece.orientation) { + (I, A | C) => [(x - 2, y), (x - 1, y), (x, y), (x + 1, y)], + (I, B | D) => [(x, y - 1), (x, y), (x, y + 1), (x, y + 2)], + (J, A) => [(x - 1, y), (x, y), (x + 1, y), (x + 1, y + 1)], + (J, B) => [(x, y), (x + 1, y), (x, y + 1), (x, y + 2)], + (J, C) => [(x - 1, y), (x - 1, y + 1), (x, y + 1), (x + 1, y + 1)], + (J, D) => [(x, y), (x, y + 1), (x, y + 2), (x - 1, y + 2)], + (L, A) => [(x - 1, y + 1), (x - 1, y), (x, y), (x + 1, y)], + (L, B) => [(x, y), (x, y + 1), (x, y + 2), (x + 1, y + 2)], + (L, C) => [(x - 1, y + 1), (x, y + 1), (x + 1, y + 1), (x + 1, y)], + (L, D) => [(x - 1, y), (x, y), (x, y + 1), (x, y + 2)], + (O, _) => [(x, y), (x, y + 1), (x - 1, y), (x - 1, y + 1)], + (S, A | C) => [(x - 1, y + 1), (x, y + 1), (x, y), (x + 1, y)], + (S, B | D) => [(x, y), (x, y + 1), (x + 1, y + 1), (x + 1, y + 2)], + (T, A) => [(x - 1, y), (x, y), (x + 1, y), (x, y + 1)], + (T, B) => [(x, y), (x, y + 1), (x + 1, y + 1), (x, y + 2)], + (T, C) => [(x, y), (x - 1, y + 1), (x, y + 1), (x + 1, y + 1)], + (T, D) => [(x - 1, y + 1), (x, y), (x, y + 1), (x, y + 2)], + (Z, A | C) => [(x - 1, y + 1), (x, y + 1), (x, y + 2), (x + 1, y + 2)], + (Z, B | D) => [(x, y), (x, y + 1), (x + 1, y), (x + 1, y - 1)], } } - fn active_piece_blocks(&self) -> Option<(Tetromino, [(u8, u8); 4])> { - self.active_piece - .as_ref() - .map(|piece| (piece.tetromino, Self::piece_blocks(&piece))) - } - fn iter<'a>(&'a self) -> BlockGridIter<'a> { - BlockGridIter::new(&self.state, self.active_piece_blocks()) + let active_piece_blocks = self.active_piece.as_ref().map(|piece| { + let b = Self::piece_blocks(piece); + let blocks = [ + (b[0].0 as u8, b[0].1 as u8), + (b[1].0 as u8, b[1].1 as u8), + (b[2].0 as u8, b[2].1 as u8), + (b[3].0 as u8, b[3].1 as u8), + ]; + + (piece.tetromino, blocks) + }); + BlockGridIter::new(&self.state, active_piece_blocks) } } @@ -355,7 +412,7 @@ struct Block { source: Tetromino, } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] enum MoveDirection { Left, Right, @@ -363,6 +420,14 @@ enum MoveDirection { SoftDrop, } +#[derive(Debug, Copy, Clone)] +enum Orientation { + A, + B, + C, + D, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum Tetromino { I,