use iced::widget::canvas::{self, Path}; use iced::{ executor, keyboard, time, Application, Color, Command, Element, Length, Point, Rectangle, Settings, Size, Subscription, }; use iced_native::{event, subscription, Event}; use rand::distributions::{Distribution, Standard}; fn main() -> iced::Result { Tetris::run(Settings::default()) } struct Tetris { background_cache: canvas::Cache, blocks: BlockGrid, ticks: usize, paused: bool, } impl Tetris { fn new() -> Tetris { Tetris { background_cache: canvas::Cache::default(), blocks: BlockGrid::new(), ticks: 0, paused: false, } } } impl Application for Tetris { type Executor = executor::Default; type Message = Message; type Flags = (); fn new(_flags: ()) -> (Self, Command) { (Tetris::new(), Command::none()) } fn title(&self) -> String { String::from("Tetris - Iced") } fn update(&mut self, message: Self::Message) -> Command { match message { Message::Pause => self.paused = !self.paused, Message::Tick(_) => { if !self.paused { self.ticks += 1 } } Message::Up => (), Message::Down => { self.blocks.move_active_piece(MoveDirection::HardDrop); } Message::Left => { self.blocks.move_active_piece(MoveDirection::Left); } Message::Right => { self.blocks.move_active_piece(MoveDirection::Right); } }; match self.blocks.active_piece { None => { let piece: Tetromino = rand::random(); self.blocks.drop_piece(piece); } Some(_) => { if self.ticks % 10 == 0 { self.blocks.move_active_piece(MoveDirection::SoftDrop); } } }; Command::none() } fn subscription(&self) -> Subscription { let keyboard_subscription = subscription::events_with(|event, status| { if let event::Status::Captured = status { return None; } match event { Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { handle_keypress(key_code) } _ => None, } }); let time_subscription = time::every(std::time::Duration::from_millis(50)) .map(|_| Message::Tick(chrono::Local::now())); Subscription::batch([time_subscription, keyboard_subscription]) } fn view(&mut self) -> Element { canvas::Canvas::new(self) .width(Length::Fill) .height(Length::Fill) .into() } } impl<'a> canvas::Program for Tetris { fn draw(&self, bounds: Rectangle, _cursor: canvas::Cursor) -> Vec { let game_width = bounds.width / 3.0; let block_length = game_width / 10.0; let center = bounds.center(); let top_left = Point::new(center.x - game_width / 2.0, 0.0); let background = self.background_cache.draw(bounds.size(), |frame| { let game_size = Size::new(block_length * 10.0, block_length * 20.0); let game_bg = Path::rectangle(top_left, game_size); frame.fill(&game_bg, Color::BLACK); }); let block_size = Size::new(block_length, block_length); let mut frame = canvas::Frame::new(bounds.size()); for (i, j, tetronimo) in self.blocks.iter() { let point = Point::new( i as f32 * block_length + top_left.x, j as f32 * block_length + top_left.y, ); let block = Path::rectangle(point, block_size); let color = tetronimo.color(); frame.fill(&block, color); } vec![background, frame.into_geometry()] } } fn handle_keypress(key_code: keyboard::KeyCode) -> Option { use keyboard::KeyCode; Some(match key_code { KeyCode::Up => Message::Up, KeyCode::Down => Message::Down, KeyCode::Right => Message::Right, KeyCode::Left => Message::Left, KeyCode::Space => Message::Pause, _ => return None, }) } #[derive(Debug)] enum Message { Up, Down, Left, Right, Pause, Tick(chrono::DateTime), } struct BlockGrid { state: [[Option; 20]; 10], active_piece: Option, } #[derive(Debug, Copy, Clone)] struct ActivePiece { location: (u8, u8), tetromino: Tetromino, } impl ActivePiece { fn move_piece(&self, direction: &MoveDirection) -> ActivePiece { use MoveDirection::*; let (cur_x, cur_y) = self.location; ActivePiece { tetromino: self.tetromino, location: match direction { Left => (cur_x.checked_sub(1).unwrap_or(0), cur_y), Right => (cur_x + 1, cur_y), SoftDrop => (cur_x, cur_y + 1), HardDrop => (cur_x, cur_y), }, } } } impl BlockGrid { fn new() -> BlockGrid { let mut state = [[None; 20]; 10]; BlockGrid { state, active_piece: None, } } /// If it's impossible to drop a piece, return false fn drop_piece(&mut self, tetromino: Tetromino) -> bool { if let None = self.active_piece { let piece = ActivePiece { location: (4, 0), tetromino, }; let piece_blocks = Self::piece_blocks(&piece); if self.piece_blocks_in_bounds(&piece_blocks) { self.active_piece = Some(piece); true } else { false } } else { false } } fn piece_blocks_in_bounds(&self, piece_blocks: &[(u8, u8); 4]) -> 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() }) } /// If there is an active piece, and the move is legal, move it. Return true if it was possible /// to move an active piece, false otherwise. fn move_active_piece(&mut self, direction: MoveDirection) -> bool { let active = self.active_piece; match (active, direction) { (None, _) => false, (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) { self.active_piece = Some(new_piece); true } else { false } } (Some(ref piece), MoveDirection::HardDrop) => { let mut new_piece = *piece; loop { let p = new_piece.move_piece(&MoveDirection::SoftDrop); let new_blocks = Self::piece_blocks(&p); if self.piece_blocks_in_bounds(&new_blocks) { new_piece = p; } else { break; } } self.active_piece = Some(new_piece); self.place_active_piece(); true } (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) { self.active_piece = Some(new_piece); true } else { self.place_active_piece(); 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 { let cur_blocks = Self::piece_blocks(&piece); for (x, y) in cur_blocks.iter() { self.state[*x as usize][*y as usize] = Some(Block { source: piece.tetromino, }); } self.active_piece = None; } } fn piece_blocks(piece: &ActivePiece) -> [(u8, u8); 4] { use Tetromino::*; let (x, y) = piece.location; 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)], } } 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()) } } struct BlockGridIter<'a> { outer_state: &'a [[Option; 20]; 10], outer_active: Option<(Tetromino, [(u8, u8); 4])>, idx: usize, active_pice_idx: usize, } impl<'a> BlockGridIter<'a> { fn new( state: &'a [[Option; 20]; 10], outer_active: Option<(Tetromino, [(u8, u8); 4])>, ) -> Self { BlockGridIter { outer_state: state, idx: 0, active_pice_idx: 0, outer_active, } } } impl<'a> std::iter::Iterator for BlockGridIter<'a> { type Item = (u8, u8, Tetromino); fn next(&mut self) -> Option { loop { if self.idx >= 200 { return self.outer_active.and_then(|(tetromino, block_array)| { let i = self.active_pice_idx; self.active_pice_idx += 1; block_array.get(i).map(|(x, y)| (*x, *y, tetromino)) }); } let i = self.idx % 10; let j = self.idx / 10; match self.outer_state[i][j] { Some(block) => { self.idx += 1; return Some((i as u8, j as u8, block.source)); } None => { self.idx += 1; } } } } } #[derive(Debug, Clone, Copy)] struct Block { source: Tetromino, } #[derive(Debug, Copy, Clone, PartialEq)] enum MoveDirection { Left, Right, HardDrop, SoftDrop, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum Tetromino { I, J, L, O, S, T, Z, } impl Tetromino { fn color(self) -> Color { use Tetromino::*; match self { I => Color::from_rgb8(0, 255, 255), J => Color::from_rgb8(0, 0, 255), L => Color::from_rgb8(255, 165, 0), O => Color::from_rgb8(255, 255, 0), S => Color::from_rgb8(0, 255, 0), T => Color::from_rgb8(255, 255, 0), Z => Color::from_rgb8(128, 0, 128), } } } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Tetromino { let index: u8 = rng.gen_range(0..=6); match index { 0 => Tetromino::I, 1 => Tetromino::J, 2 => Tetromino::L, 3 => Tetromino::O, 4 => Tetromino::S, 5 => Tetromino::T, 6 => Tetromino::Z, _ => unreachable!(), } } }