diff --git a/Cargo.toml b/Cargo.toml index ed17c1d..f18eaf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,8 @@ members = [ "crossterm_terminal", "crossterm_input", "crossterm_screen", + "examples/program_examples/snake", + "examples/program_examples/first_depth_search" ] [dependencies] @@ -49,4 +51,8 @@ path = "examples/program_examples/logging.rs" [[example]] name = "command_bar" -path = "examples/program_examples/command_bar.rs" \ No newline at end of file +path = "examples/program_examples/command_bar.rs" + +[[example]] +name = "snake" +path = "examples/program_examples/snake" \ No newline at end of file diff --git a/examples/program_examples/command_bar.rs b/examples/program_examples/command_bar.rs index c448fb5..7119046 100644 --- a/examples/program_examples/command_bar.rs +++ b/examples/program_examples/command_bar.rs @@ -1,6 +1,6 @@ extern crate crossterm; -use crossterm::{cursor, input, ClearType, Crossterm, Screen, Terminal, TerminalCursor}; +use crossterm::{input, ClearType, Crossterm, Screen, Terminal, TerminalCursor, InputEvent, KeyEvent}; use std::io::Read; use std::sync::{Arc, Mutex}; @@ -22,22 +22,20 @@ fn main() { thread::spawn(move || { let input = input(); - let mut stdin = input.read_async().bytes(); + let mut stdin = input.read_async(); loop { - let a = stdin.next(); - - match a { - Some(Ok(13)) => { + match stdin.next() { + Some(InputEvent::Keyboard(KeyEvent::Char('\n'))) => { input_buf.lock().unwrap().clear(); } - Some(Ok(val)) => { - input_buf.lock().unwrap().push(val as char); + Some(InputEvent::Keyboard(KeyEvent::Char(character))) => { + input_buf.lock().unwrap().push(character as char); } _ => {} } - thread::sleep(time::Duration::from_millis(100)); + thread::sleep(time::Duration::from_millis(10)); count += 1; } }) @@ -73,7 +71,7 @@ fn log(input_buf: Arc>, screen: &Screen) -> Vec { @@ -28,7 +28,7 @@ impl<'screen> FirstDepthSearch<'screen> { ) -> FirstDepthSearch<'screen> { FirstDepthSearch { direction: Direction::Up, - map: map, + map, stack: Vec::new(), root_pos: start_pos, is_terminated: false, diff --git a/examples/program_examples/first_depth_search/src/main.rs b/examples/program_examples/first_depth_search/src/main.rs index f8e3e50..7ada2d9 100644 --- a/examples/program_examples/first_depth_search/src/main.rs +++ b/examples/program_examples/first_depth_search/src/main.rs @@ -6,7 +6,7 @@ mod map; mod messages; mod variables; -use self::crossterm::{terminal, ClearType, Color, Crossterm, Screen}; +use self::crossterm::{Color, Crossterm, Screen, ClearType, InputEvent, KeyEvent}; use self::messages::WELCOME_MESSAGE; use self::variables::{Position, Size}; @@ -62,7 +62,11 @@ fn print_welcome_screen() { // clear the screen and print the welcome message. terminal.clear(ClearType::All); cursor.goto(0, 0); - terminal.write(WELCOME_MESSAGE.join("\r\n")); + + crossterm + .style(format!("{}", messages::WELCOME_MESSAGE.join("\n\r"))) + .with(Color::Cyan) + .paint(&screen.stdout); cursor.hide(); cursor.goto(0, 10); @@ -71,13 +75,11 @@ fn print_welcome_screen() { cursor.goto(0, 11); terminal.write("Press `q` to abort the program"); - let mut stdin = input.read_async().bytes(); + let mut stdin = input.read_async(); // print some progress example. for i in (1..5).rev() { - let a = stdin.next(); - - if let Some(Ok(b'q')) = a { + if let Some(InputEvent::Keyboard(KeyEvent::Char('q'))) = stdin.next() { drop(screen); terminal.exit(); break; @@ -95,3 +97,4 @@ fn print_welcome_screen() { thread::sleep(time::Duration::from_secs(1)); } } + diff --git a/examples/program_examples/first_depth_search/src/map.rs b/examples/program_examples/first_depth_search/src/map.rs index 57e8524..37a02c4 100644 --- a/examples/program_examples/first_depth_search/src/map.rs +++ b/examples/program_examples/first_depth_search/src/map.rs @@ -1,7 +1,5 @@ use super::variables::{Cell, Position, Size}; -use crossterm::{cursor, Color, Crossterm, ObjectStyle, Screen, StyledObject}; - -use std::fmt::Display; +use crossterm::{cursor, Color, Crossterm, Screen}; pub struct Map { pub map: Vec>, @@ -38,7 +36,7 @@ impl Map { } Map { - map: map, + map, size: Size::new(map_size.width, map_size.height), } } diff --git a/examples/program_examples/first_depth_search/src/variables.rs b/examples/program_examples/first_depth_search/src/variables.rs index 2e745e4..a4a979d 100644 --- a/examples/program_examples/first_depth_search/src/variables.rs +++ b/examples/program_examples/first_depth_search/src/variables.rs @@ -1,11 +1,7 @@ extern crate crossterm; -use self::crossterm::{terminal, ClearType}; use self::crossterm::{Color, ObjectStyle, StyledObject}; -use std::fmt; -use std::fmt::Debug; - #[derive(Copy, Clone, Debug)] pub enum Direction { Up = 0, diff --git a/examples/program_examples/logging.rs b/examples/program_examples/logging.rs index f41310e..3a337da 100644 --- a/examples/program_examples/logging.rs +++ b/examples/program_examples/logging.rs @@ -2,10 +2,7 @@ extern crate crossterm; use crossterm::Screen; use std::collections::VecDeque; -use std::io::Write; -use std::sync::mpsc::{self, Receiver, Sender}; -use std::sync::Arc; -use std::sync::Mutex; +use std::sync::{Arc, Mutex, mpsc::{self, Receiver, Sender}}; use std::thread::{self, JoinHandle}; /// This is an que that could be shared between threads safely. @@ -98,11 +95,6 @@ fn main() { // a thread that will log all logs in the queue. handle_incoming_logs(more_jobs_rx.clone(), queue.clone()); - - // for handle in thread_handles - // { - // handle.join(); - // } } fn handle_incoming_logs(more_jobs_rx: SyncFlagRx, queue: WorkQueue) { diff --git a/examples/program_examples/snake/src/main.rs b/examples/program_examples/snake/src/main.rs index b66913a..52e8730 100644 --- a/examples/program_examples/snake/src/main.rs +++ b/examples/program_examples/snake/src/main.rs @@ -6,122 +6,133 @@ mod messages; mod snake; mod variables; -use self::crossterm::{input, terminal, ClearType, Color, Crossterm, Screen}; +use self::crossterm::{ + AsyncReader, ClearType, Color, Colorize, Crossterm, InputEvent, KeyEvent, Screen, +}; use map::Map; use snake::Snake; use variables::{Direction, Position, Size}; use std::collections::HashMap; -use std::io::Read; use std::io::Write; use std::iter::Iterator; use std::{thread, time}; fn main() { - let map_size = title_screen(); + let map_size = ask_size(); - { - let mut screen = Screen::new(true); - let crossterm = Crossterm::from_screen(&screen); - let cursor = crossterm.cursor(); - let mut input = crossterm.input(); + // screen has to be in raw mode in order for the key presses not to be printed to the screen. + let screen = Screen::new(true); + let crossterm = Crossterm::from_screen(&screen); - cursor.hide(); + crossterm.cursor().hide(); - let mut stdin = input.read_async().bytes(); + // initialize free positions for the game map. + let mut free_positions: HashMap = + HashMap::with_capacity((map_size.width * map_size.height) as usize); - let mut free_positions: HashMap = - HashMap::with_capacity((map_size.width * map_size.height) as usize); + // render the map + let mut map = Map::new(map_size); + map.render_map(&screen, &mut free_positions); - let mut map = Map::new(map_size.clone()); - map.render_map(&screen, &mut free_positions); + let mut snake = Snake::new(); - let mut direction = Direction::Right; - - let mut snake = Snake::new(map_size.clone()); - - for part in snake.get_parts().iter() { - free_positions - .remove_entry(format!("{},{}", part.position.x, part.position.y).as_str()); - } - - map.spawn_food(&free_positions, &screen); - - loop { - thread::sleep(time::Duration::from_millis(200)); - let pressed_key = stdin.next(); - - if let Some(Ok(key)) = pressed_key { - match key as char { - 'w' => direction = Direction::Up, - 'a' => direction = Direction::Left, - 's' => direction = Direction::Down, - 'd' => direction = Direction::Right, - _ => {} - } - } - - snake.move_snake(&direction, &screen, &mut free_positions); - - if map.is_out_of_bounds(snake.snake_parts[0].position) { - break; - } - - snake.draw_snake(&screen); - - if snake.has_eaten_food(map.foot_pos) { - map.spawn_food(&free_positions, &screen); - } - } - drop(screen); + // remove snake coords from free positions. + for part in snake.get_parts().iter() { + free_positions.remove_entry(format!("{},{}", part.position.x, part.position.y).as_str()); } + map.spawn_food(&free_positions, &screen); + + let mut stdin = crossterm.input().read_async(); + let mut snake_direction = Direction::Right; + + // start the game loop; draw, move snake and spawn food. + loop { + if let Some(new_direction) = update_direction(&mut stdin) { + snake_direction = new_direction; + } + + snake.move_snake(&snake_direction, &screen, &mut free_positions); + + if map.is_out_of_bounds(snake.snake_parts[0].position) { + break; + } + + snake.draw_snake(&screen); + + if snake.has_eaten_food(map.foot_pos) { + map.spawn_food(&free_positions, &screen); + } + + thread::sleep(time::Duration::from_millis(400)); + } game_over_screen(); } -fn title_screen() -> Size { +fn update_direction(reader: &mut AsyncReader) -> Option { + let pressed_key = reader.next(); + + if let Some(InputEvent::Keyboard(KeyEvent::Char(character))) = pressed_key { + return Some(match character { + 'w' => Direction::Up, + 'a' => Direction::Left, + 's' => Direction::Down, + 'd' => Direction::Right, + _ => return None, + }); + } else if let Some(InputEvent::Keyboard(key)) = pressed_key { + return Some(match key { + KeyEvent::Up => Direction::Up, + KeyEvent::Left => Direction::Left, + KeyEvent::Down => Direction::Down, + KeyEvent::Right => Direction::Right, + _ => return None, + }); + } + + None +} + +fn ask_size() -> Size { let crossterm = Crossterm::new(); + crossterm.terminal().clear(ClearType::All); let cursor = crossterm.cursor(); - let terminal = crossterm.terminal().clear(ClearType::All); - println!("{}", messages::SNAKERS.join("\n\r")); + println!( + "{}", + crossterm + .style(format!("{}", messages::END_MESSAGE.join("\n\r"))) + .with(Color::Cyan) + ); + + // read width cursor.goto(0, 15); - println!("Enter map width:"); + println!("{}", "Enter map width:".green().on_yellow()); cursor.goto(17, 15); + + // read height let width = crossterm.input().read_line().unwrap(); - println!("\r\nEnter map height:"); + println!("{}", "\r\nEnter map height:".green().on_yellow()); cursor.goto(17, 17); + let height = crossterm.input().read_line().unwrap(); + // parse input let parsed_width = width.parse::().unwrap(); let parsed_height = height.parse::().unwrap(); - let terminal = crossterm.terminal().clear(ClearType::All); + crossterm.terminal().clear(ClearType::All); + return Size::new(parsed_width, parsed_height); } -fn print_game_stats(map_size: Size, snake_lenght: usize, food_aten: usize, screen: &mut Screen) { - let crossterm = Crossterm::new(); - - let cursor = crossterm.cursor(); - let terminal = crossterm.terminal().clear(ClearType::All); - - screen.write(format!("Snake Lenght: {}\n\r", snake_lenght).as_ref()); - screen.write(format!("Food aten: {}\n\r", snake_lenght).as_ref()); - - cursor.goto(0, map_size.height as u16); - cursor.goto(0, map_size.height as u16); -} - fn game_over_screen() { let crossterm = Crossterm::new(); - let cursor = crossterm.cursor(); - let terminal = crossterm.terminal(); - - terminal.clear(ClearType::All); + crossterm.terminal().clear(ClearType::All); println!( "{}", @@ -129,6 +140,6 @@ fn game_over_screen() { .style(format!("{}", messages::END_MESSAGE.join("\n\r"))) .with(Color::Red) ); - // cursor.goto() - cursor.show(); + + crossterm.cursor().show(); } diff --git a/examples/program_examples/snake/src/map.rs b/examples/program_examples/snake/src/map.rs index a1261bf..67ad7a4 100644 --- a/examples/program_examples/snake/src/map.rs +++ b/examples/program_examples/snake/src/map.rs @@ -1,16 +1,10 @@ -use super::snake::Snake; -use super::variables::{Direction, Position, Size}; +use super::variables::{Position, Size}; -use crossterm::{ - style, Color, ColorType, Crossterm, ObjectStyle, Screen, StyledObject, TerminalCursor, -}; +use crossterm::{style, Color, Colorize, Crossterm, Screen, TerminalCursor}; use rand::distributions::{IndependentSample, Range}; use std::collections::HashMap; -use std::fmt::Display; -use std::hash::Hash; -use std::ops::Index; use rand; @@ -30,14 +24,14 @@ impl Map { // render the map on the screen. pub fn render_map(&mut self, screen: &Screen, free_positions: &mut HashMap) { let crossterm = Crossterm::from_screen(screen); - let mut cursor = crossterm.cursor(); - let mut terminal = crossterm.terminal(); + let cursor = crossterm.cursor(); + let terminal = crossterm.terminal(); for y in 0..self.size.height { for x in 0..self.size.height { if (y == 0 || y == self.size.height - 1) || (x == 0 || x == self.size.width - 1) { cursor.goto(x as u16, y as u16); - terminal.write("█"); + "█".magenta().paint(&screen.stdout); } else { free_positions.insert(format!("{},{}", x, y), Position::new(x, y)); } @@ -68,7 +62,6 @@ impl Map { fn draw_food(&self, screen: &Screen) { let cursor = TerminalCursor::from_output(&screen.stdout); cursor.goto(self.foot_pos.x as u16, self.foot_pos.y as u16); - style("$").with(Color::Green).paint(&screen.stdout); - screen.stdout.flush(); + "$".green().paint(&screen.stdout); } } diff --git a/examples/program_examples/snake/src/snake.rs b/examples/program_examples/snake/src/snake.rs index 54e70a3..3854c7f 100644 --- a/examples/program_examples/snake/src/snake.rs +++ b/examples/program_examples/snake/src/snake.rs @@ -1,5 +1,5 @@ use super::variables::{Direction, Position, Size}; -use crossterm::{Color, Crossterm, Screen}; +use crossterm::{Crossterm, Screen}; use std::collections::HashMap; @@ -18,16 +18,14 @@ impl Part { pub struct Snake { pub snake_parts: Vec, pub parent_pos: Position, - map_size: Size, } impl Snake { - pub fn new(map_size: Size) -> Snake { - return Snake { - map_size, + pub fn new() -> Snake { + Snake { snake_parts: vec![Part::new(9, 10), Part::new(8, 10)], parent_pos: Position::new(0, 0), - }; + } } pub fn move_snake( @@ -41,7 +39,6 @@ impl Snake { let terminal = crossterm.terminal(); let count = self.snake_parts.len(); - let is_food_eaten = false; for (index, ref mut snake_part) in self.snake_parts.iter_mut().enumerate() { if index == count - 1 { @@ -74,7 +71,7 @@ impl Snake { } pub fn draw_snake(&mut self, screen: &Screen) { - for (index, ref mut snake_part) in self.snake_parts.iter_mut().enumerate() { + for snake_part in self.snake_parts.iter_mut() { snake_part.position.draw("■", screen); } } @@ -94,9 +91,3 @@ impl Snake { return &self.snake_parts; } } - -pub enum SnakeState { - MovedOutMap, - Moved, - AteFood, -} diff --git a/examples/program_examples/snake/src/variables.rs b/examples/program_examples/snake/src/variables.rs index ae5e97e..c219136 100644 --- a/examples/program_examples/snake/src/variables.rs +++ b/examples/program_examples/snake/src/variables.rs @@ -1,11 +1,6 @@ extern crate crossterm; -use self::crossterm::{ - cursor, style, ClearType, Color, Crossterm, ObjectStyle, Screen, StyledObject, TerminalCursor, -}; - -use std::fmt; -use std::fmt::Debug; +use self::crossterm::{style, Color, Crossterm, Screen, TerminalCursor}; #[derive(Copy, Clone, Debug)] pub enum Direction { diff --git a/src/lib.rs b/src/lib.rs index f632697..591b61c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,9 @@ mod crossterm; #[cfg(feature = "cursor")] pub use self::crossterm_cursor::{cursor, TerminalCursor}; #[cfg(feature = "input")] -pub use self::crossterm_input::{input, AsyncReader, KeyEvent, TerminalInput}; +pub use self::crossterm_input::{ + input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput, +}; #[cfg(feature = "screen")] pub use self::crossterm_screen::{AlternateScreen, Screen}; #[cfg(feature = "style")]