diff --git a/iced-tetris/src/lib.rs b/iced-tetris/src/lib.rs new file mode 100644 index 0000000..2202b5b --- /dev/null +++ b/iced-tetris/src/lib.rs @@ -0,0 +1,186 @@ +/* +use iced::widget::canvas::{self, Path, Stroke, Text}; +use iced::{ + executor, keyboard, time, Application, Color, Command, Element, Length, Point, Rectangle, + Settings, Size, Subscription, +}; +use tetris_logic::{BlockGrid, MoveDirection, Tetromino}; +*/ + +use iced::{ + widget::canvas::{self, Path, Stroke, Text}, + Color, Element, Length, Pixels, Point, Renderer, Size, Subscription, Task, Theme, +}; +use tetris_logic::{BlockGrid, MoveDirection, Tetromino}; + +pub fn tetris_main() -> iced::Result { + iced::application(Tetris::title, update, view) + .subscription(subscription) + .run_with(|| (Tetris::new(), Task::none())) +} + +#[derive(Debug, Clone)] +enum Message { + Up, + Down, + Left, + Right, + Pause, + Tick(chrono::DateTime), +} + +fn update(state: &mut Tetris, message: Message) { + match message { + Message::Pause => state.paused = !state.paused, + Message::Tick(_time) => { + if !state.paused { + state.ticks += 1; + } + } + Message::Up => { + state.blocks.rotate_active_piece(); + } + Message::Down => { + state.blocks.move_active_piece(MoveDirection::HardDrop); + } + Message::Left => { + state.blocks.move_active_piece(MoveDirection::Left); + } + Message::Right => { + state.blocks.move_active_piece(MoveDirection::Right); + } + }; + if state.blocks.piece_currently_active() { + if state.ticks % 10 == 0 && !state.paused { + state.blocks.move_active_piece(MoveDirection::SoftDrop); + } + } else { + let piece: Tetromino = rand::random(); + state.blocks.drop_piece(piece); + } + + let lines_removed = state.blocks.clear_pieces(); + state.lines_removed += lines_removed; +} + +fn subscription(_state: &Tetris) -> Subscription { + let keyboard_subscription = iced::keyboard::on_key_press(|key, _modifiers| { + use iced::keyboard::key::{Key, Named}; + match key { + Key::Named(Named::ArrowUp) => Some(Message::Up), + Key::Named(Named::ArrowDown) => Some(Message::Down), + Key::Named(Named::ArrowLeft) => Some(Message::Left), + Key::Named(Named::ArrowRight) => Some(Message::Right), + Key::Named(Named::Space) => Some(Message::Pause), + _ => None, + } + }); + + let time_subscription = iced::time::every(std::time::Duration::from_millis(50)) + .map(|_| Message::Tick(chrono::Local::now())); + Subscription::batch([time_subscription, keyboard_subscription]) +} + +fn view(state: &Tetris) -> Element { + iced::widget::canvas(state) + .width(Length::Fill) + .height(Length::Fill) + .into() +} + +struct Tetris { + background_cache: canvas::Cache, + blocks: BlockGrid, + ticks: usize, + paused: bool, + lines_removed: u32, +} + +impl Tetris { + fn title(&self) -> String { + String::from("Tetris - Iced") + } + + fn new() -> Tetris { + Tetris { + background_cache: canvas::Cache::default(), + blocks: BlockGrid::new(), + ticks: 0, + paused: false, + lines_removed: 0, + } + } +} + +impl canvas::Program for Tetris { + type State = (); + + fn draw( + &self, + _state: &Self::State, + renderer: &Renderer, + _theme: &Theme, + bounds: iced::Rectangle, + _cursor: iced::mouse::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(renderer, 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(renderer, 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::default().with_width(3.0).with_color(stroke_color); + frame.stroke(&block, stroke); + } + + let text_color = Color::from_rgb8(255, 30, 30); + let text_size = Pixels(32.0); + let score = Text { + content: format!("Lines removed: {}", self.lines_removed), + position: Point::new(10.0, 30.0), + size: text_size, + color: text_color, + ..Default::default() + }; + + frame.fill_text(score); + + if self.paused { + let paused = Text { + content: "PAUSED".to_string(), + position: Point::new(10.0, 60.0), + color: text_color, + size: text_size, + ..Default::default() + }; + frame.fill_text(paused); + } + + vec![background, frame.into_geometry()] + } +} diff --git a/iced-tetris/src/main.rs b/iced-tetris/src/main.rs index 33203da..20f35ae 100644 --- a/iced-tetris/src/main.rs +++ b/iced-tetris/src/main.rs @@ -1,187 +1,3 @@ -/* -use iced::widget::canvas::{self, Path, Stroke, Text}; -use iced::{ - executor, keyboard, time, Application, Color, Command, Element, Length, Point, Rectangle, - Settings, Size, Subscription, -}; -use tetris_logic::{BlockGrid, MoveDirection, Tetromino}; -*/ - -use iced::{ - widget::canvas::{self, Path, Stroke, Text}, - Color, Element, Length, Pixels, Point, Renderer, Size, Subscription, Task, Theme, -}; -use tetris_logic::{BlockGrid, MoveDirection, Tetromino}; - fn main() -> iced::Result { - //Tetris::run(Settings::default()) - iced::application(Tetris::title, update, view) - .subscription(subscription) - .run_with(|| (Tetris::new(), Task::none())) -} - -#[derive(Debug, Clone)] -enum Message { - Up, - Down, - Left, - Right, - Pause, - Tick(chrono::DateTime), -} - -fn update(state: &mut Tetris, message: Message) { - match message { - Message::Pause => state.paused = !state.paused, - Message::Tick(_time) => { - if !state.paused { - state.ticks += 1; - } - } - Message::Up => { - state.blocks.rotate_active_piece(); - } - Message::Down => { - state.blocks.move_active_piece(MoveDirection::HardDrop); - } - Message::Left => { - state.blocks.move_active_piece(MoveDirection::Left); - } - Message::Right => { - state.blocks.move_active_piece(MoveDirection::Right); - } - }; - if state.blocks.piece_currently_active() { - if state.ticks % 10 == 0 && !state.paused { - state.blocks.move_active_piece(MoveDirection::SoftDrop); - } - } else { - let piece: Tetromino = rand::random(); - state.blocks.drop_piece(piece); - } - - let lines_removed = state.blocks.clear_pieces(); - state.lines_removed += lines_removed; -} - -fn subscription(_state: &Tetris) -> Subscription { - let keyboard_subscription = iced::keyboard::on_key_press(|key, _modifiers| { - use iced::keyboard::key::{Key, Named}; - match key { - Key::Named(Named::ArrowUp) => Some(Message::Up), - Key::Named(Named::ArrowDown) => Some(Message::Down), - Key::Named(Named::ArrowLeft) => Some(Message::Left), - Key::Named(Named::ArrowRight) => Some(Message::Right), - Key::Named(Named::Space) => Some(Message::Pause), - _ => None, - } - }); - - let time_subscription = iced::time::every(std::time::Duration::from_millis(50)) - .map(|_| Message::Tick(chrono::Local::now())); - Subscription::batch([time_subscription, keyboard_subscription]) -} - -fn view(state: &Tetris) -> Element { - iced::widget::canvas(state) - .width(Length::Fill) - .height(Length::Fill) - .into() -} - -struct Tetris { - background_cache: canvas::Cache, - blocks: BlockGrid, - ticks: usize, - paused: bool, - lines_removed: u32, -} - -impl Tetris { - fn title(&self) -> String { - String::from("Tetris - Iced") - } - - fn new() -> Tetris { - Tetris { - background_cache: canvas::Cache::default(), - blocks: BlockGrid::new(), - ticks: 0, - paused: false, - lines_removed: 0, - } - } -} - -impl canvas::Program for Tetris { - type State = (); - - fn draw( - &self, - _state: &Self::State, - renderer: &Renderer, - _theme: &Theme, - bounds: iced::Rectangle, - _cursor: iced::mouse::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(renderer, 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(renderer, 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::default().with_width(3.0).with_color(stroke_color); - frame.stroke(&block, stroke); - } - - let text_color = Color::from_rgb8(255, 30, 30); - let text_size = Pixels(32.0); - let score = Text { - content: format!("Lines removed: {}", self.lines_removed), - position: Point::new(10.0, 30.0), - size: text_size, - color: text_color, - ..Default::default() - }; - - frame.fill_text(score); - - if self.paused { - let paused = Text { - content: "PAUSED".to_string(), - position: Point::new(10.0, 60.0), - color: text_color, - size: text_size, - ..Default::default() - }; - frame.fill_text(paused); - } - - vec![background, frame.into_geometry()] - } + iced_tetris::tetris_main() }