use iced::widget::canvas::{self, Path, Stroke, Text}; use iced::{ executor, keyboard, time, Application, Color, Command, Element, Length, Point, Rectangle, Settings, Size, Subscription, }; use iced_native::{event, subscription, Event}; use tetris_logic::{BlockGrid, MoveDirection, Tetromino}; fn main() -> iced::Result { Tetris::run(Settings::default()) } struct Tetris { background_cache: canvas::Cache, blocks: BlockGrid, ticks: usize, paused: bool, lines_removed: u32, } impl Tetris { fn new() -> Tetris { Tetris { background_cache: canvas::Cache::default(), blocks: BlockGrid::new(), ticks: 0, paused: false, lines_removed: 0, } } } 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 => { self.blocks.rotate_active_piece(); } 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); } }; if self.blocks.piece_currently_active() { if self.ticks % 10 == 0 && !self.paused { self.blocks.move_active_piece(MoveDirection::SoftDrop); } } else { let piece: Tetromino = rand::random(); self.blocks.drop_piece(piece); } let lines_removed = self.blocks.clear_pieces(); self.lines_removed += lines_removed; 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(); let fill_color = Color::from_rgb8(color.0, color.1, color.2); frame.fill(&block, fill_color); let stroke_color = Color::from_rgb8( color.0.checked_sub(20).unwrap_or(0), color.1.checked_sub(20).unwrap_or(0), color.2.checked_sub(20).unwrap_or(0), ); let stroke = Stroke { width: 3.0, color: stroke_color, ..Stroke::default() }; frame.stroke(&block, stroke); } let text_color = Color::from_rgb8(255, 30, 30); let text_size = 32.0; let score = Text { content: format!("Lines removed: {}", self.lines_removed), position: Point::new(10.0, 30.0), color: text_color, size: text_size, ..Default::default() }; frame.fill_text(score); if self.paused { let paused = Text { content: format!("PAUSED"), position: Point::new(10.0, 60.0), color: text_color, size: text_size, ..Default::default() }; frame.fill_text(paused); } 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), }