diff --git a/Cargo.toml b/Cargo.toml index 6a26459..d267b3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "st test-success-exit-code = 33 test-timeout = 30 -run-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio"] +run-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", "-accel", "kvm"] [dependencies] bootloader= { version = "0.9.23", features = ["map_physical_memory"] } diff --git a/src/user/bin/snake.rs b/src/user/bin/snake.rs index 6bc5451..135ad47 100644 --- a/src/user/bin/snake.rs +++ b/src/user/bin/snake.rs @@ -1,5 +1,6 @@ use alloc::string::String; use alloc::{format, vec, vec::Vec, boxed::Box}; +use core::arch::x86_64::_mm_test_all_ones; use async_trait::async_trait; use crate::std::io::{Color, Screen, Stdin}; use crate::std::time; @@ -16,113 +17,132 @@ struct Point { y: i8, } -pub struct Game { - snake: SegQueue, - head: Point, - poi: Point, - mv: char, - score: u8 +#[derive(Clone, Debug, PartialEq)] +struct Position { + x: i8, + y: i8, + dir: Direction, } +#[derive(Clone, Debug, PartialEq)] +enum Direction { + Up, + Down, + Left, + Right, +} + +#[derive(Clone, Debug, PartialEq)] +enum Status { + Scored, + Lost, + Exited, + None, +} + +pub struct Game { + snakes: Vec, + pois: Vec, + score: u8, + hardmode: bool, +} + + #[async_trait] impl Application for Game { fn new() -> Self { Self { - snake: SegQueue::new(), - head: Point { x: 5, y: 5 }, - poi: Point { x: 0, y: 0 }, - mv: ' ', - score: 0 + snakes: Vec::new(), + pois: Vec::new(), + score: 0, + hardmode: false, } } async fn run(&mut self, _: Vec) -> Result<(), Error> { Screen::application_mode(); - let clone = self.clone_snake(); - self.render(clone).map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; + // render the initial state of the screen. + self.render().map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; - (5..=7).for_each(|x| { - self.snake.push(Point { x, y: 5 }); - }); - self.head = Point { x: 7, y: 5 }; - self.new_poi(); + // make the first poi + for i in 0..5 { + self.new_poi(); + } - 'gameloop: loop { - - time::wait(0.2); - - if let Some(c) = Stdin::try_keystroke() { - self.mv = c; - } - - //self.mv = Stdin::keystroke().await; - - match self.mv { - 'w' => self.head.y -= 1, - 'a' => self.head.x -= 1, - 's' => self.head.y += 1, - 'd' => self.head.x += 1, - 'x' => break, - _ => continue, - } - - self.snake.push(Point { x: self.head.x, y: self.head.y }); // new head added - - if self.head == self.poi { - self.new_poi(); - self.score += 1 - } else { - self.snake.pop().unwrap(); // tail removed if score does not increase - } - - if self.lose_condition() { - self.render_end_screen().map_err(|_| Error::ApplicationError(String::from("failed to render game over screen")))?; - while let chr = KEYBOARD.lock().get_keystroke().await { - match chr { - 'x' => break 'gameloop, - _ => continue, - } - } - } - - let clone = self.clone_snake(); - self.render(clone).map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; - }; + // run the game + self.gameloop().await?; + // return to the terminal Screen::terminal_mode(); Ok(()) } } impl Game { + + async fn gameloop(&mut self) -> Result<(), Error> { // main gameloop + let mut all_points: Vec; + + 'gameloop: loop { + + time::wait(0.1); + + let res = self.snakes.iter_mut().map(|s| s.next(&self.pois, &self.snakes)).collect::>(); + if res.contains(&Status::Lost) { + self.render_end_screen().map_err(|_| Error::ApplicationError(String::from("failed to render end screen")))?; + + loop { + match Stdin::keystroke().await { + 'x' => break 'gameloop, + _ => continue, + } + } + } else if res.contains(&Status::Exited) { + break 'gameloop; + } else if res.contains(&Status::Scored) { + self.score += 1; + } + + + + + + + + + + }; + Ok(()) + } + fn new_poi(&mut self) { - self.poi = Point { x: Random::int(3, 76) as i8, y: Random::int(3, 21) as i8 } + self.pois.push(Point { x: Random::int(3, 76) as i8, y: Random::int(3, 21) as i8 }); } - fn render(&mut self, snake: Vec) -> Result<(), ()> { - let mut frame = vec![vec![ScreenChar::null(); 80]; 25]; - snake.into_iter().for_each(|p| { - frame[p.y as usize][p.x as usize] = ScreenChar::new('@' as u8, ColorCode::new(Color::Cyan, Color::Black)); - }); - - frame[self.poi.y as usize][self.poi.x as usize] = ScreenChar::new('o' as u8, ColorCode::new(Color::Red, Color::Black)); - let literal = format!("snake go brr score: {}", self.score); - let msg = Game::centre_text(80, literal); - frame[1] = msg.chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::LightGreen, Color::Black))).collect(); - - let mut elem = ColouredElement::generate(frame, (80, 25)); - elem.render((0,0)) + fn replace_poi(&mut self, poi: &Point) { + self.pois.remove(self.pois.iter().position(|p| p == poi).unwrap()); + self.new_poi(); } - fn lose_condition(&mut self) -> bool { - let cloned = self.clone_snake(); - let snake_overlaps = (1..cloned.len()).any(|i| cloned[i..].contains(&cloned[i - 1])); // checks if any part of the snake overlaps itself - let out_of_bounds = cloned.iter().filter(|p| p.x < 0 || p.y < 0 || p.x > 79 || p.y > 24).count() > 0; // checks if the snake goes out of bounds + fn render(&mut self) -> Result<(), ()> { + // let mut frame = vec![vec![ScreenChar::null(); 80]; 25]; + // snake.into_iter().for_each(|p| { + // frame[p.y as usize][p.x as usize] = ScreenChar::new('@' as u8, ColorCode::new(Color::Cyan, Color::Black)); + // }); + // + // frame[self.poi.y as usize][self.poi.x as usize] = ScreenChar::new('o' as u8, ColorCode::new(Color::Red, Color::Black)); + // let literal = format!("snake go brr score: {}", self.score); + // let msg = Game::centre_text(80, literal); + // frame[1] = msg.chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::LightGreen, Color::Black))).collect(); + // + // let mut elem = ColouredElement::generate(frame, (80, 25)); + // elem.render((0,0)) - snake_overlaps || out_of_bounds + Ok(()) } + fn centre_text(dims: usize, text: String) -> String { // centres text in a string of whitespace of a given length let max_pad = dims / 2; let mut msg = String::new(); @@ -143,20 +163,106 @@ impl Game { let mut elem = ColouredElement::generate(frame, (80, 25)); elem.render((0,0)) } +} - fn clone_snake(&mut self) -> Vec { - let mut cloned= Vec::new(); - let mut snake = SegQueue::new(); - while !self.snake.is_empty() { - let item = self.snake.pop().unwrap(); - cloned.push(item.clone()); - snake.push(item); + + + + + + + + + + + + +struct Snake { + ai_controlled: bool, + head: Point, + tail: Vec, + dir: Direction, +} + +impl Snake { + fn ai(id: usize) -> Self { + Self { + ai_controlled: true, + head: Point { x: 1 + id as i8 * 2, y: 1 }, + tail: Vec::new(), + dir: Direction::Up } - self.snake = snake; - cloned + } + fn player(id: usize) -> Self { + Self { + ai_controlled: false, + head: Point { x: 1 + id as i8 * 2, y: 1 }, + tail: Vec::new(), + dir: Direction::Up, + } + } + + fn next(&mut self, points_of_interest: &Vec, tails: &Vec) -> Status { // returns (lose_condition, scored) + + // uses pathing algorithm if ai else keyboard input if human + if self.ai_controlled { + self.dir = self.decide_dir(); + } else { + if let Some(c) = Stdin::try_keystroke() { + self.dir = match c { + 'w' => Direction::Up, + 'a' => Direction::Left, + 's' => Direction::Down, + 'd' => Direction::Right, + 'x' => return Status::Exited, + _ => self.dir.clone(), + } + } + } + + self.tail.push(self.head.clone()); + + match self.dir { + Direction::Up => self.head.y -= 1, + Direction::Down => self.head.y += 1, + Direction::Left => self.head.x -= 1, + Direction::Right => self.head.x += 1, + } + + if self.lose_condition(tails) { + if !self.ai_controlled { + return Status::Lost; + } + } + + if points_of_interest.contains(&self.head) { + if !self.ai_controlled { + Status::Scored + } + } else { + self.tail.remove(0); + } + + Status::None + } + + fn decide_dir(&mut self) -> Direction { + unimplemented!() // implement a basic pathfinding or random movement algorithm + } + + fn lose_condition(&mut self, tails: &Vec) -> bool { // where tails includes the tail of every other snake + let p = self.head.clone(); + + let tails: Vec = tails.iter().map(|t| t.tail.flatten()).collect(); + + let snake_overlaps = tails.contains(&self.head); // checks if any part of the snake overlaps itself + let out_of_bounds = p.x < 0 || p.y < 0 || p.x > 79 || p.y > 24; // checks if the snake goes out of bounds + + snake_overlaps || out_of_bounds } } + fn round_up(n: f64) -> usize { (n + 0.99) as usize } diff --git a/src/user/bin/snake_old.rs b/src/user/bin/snake_old.rs new file mode 100644 index 0000000..037481e --- /dev/null +++ b/src/user/bin/snake_old.rs @@ -0,0 +1,290 @@ +use alloc::string::String; +use alloc::{format, vec, vec::Vec, boxed::Box}; +use async_trait::async_trait; +use crate::std::io::{Color, Screen, Stdin}; +use crate::std::time; +use crate::kernel::tasks::keyboard::KEYBOARD; +use crossbeam_queue::SegQueue; +use crate::kernel::render::{ColorCode, ScreenChar}; +use crate::std::application::{Application, Error}; +use crate::std::random::Random; +use crate::system::std::frame::ColouredElement; + +#[derive(Clone, Debug, PartialEq)] +struct Point { + x: i8, + y: i8, +} + +#[derive(Clone, Debug, PartialEq)] +struct Position { + x: i8, + y: i8, + dir: Direction, +} + +#[derive(Clone, Debug, PartialEq)] +enum Direction { + Up, + Down, + Left, + Right, +} + +enum Status { + Scored, + Lost, + Exited, + None, +} + +pub struct Game { + snake: SegQueue, + head: Point, + poi: Point, + mv: char, + score: u8, + hardmode: bool, +} + + +snake.rs +#[async_trait] +impl Application for Game { + fn new() -> Self { + Self { + snake: SegQueue::new(), + head: Point { x: 5, y: 5 }, + poi: Point { x: 0, y: 0 }, + mv: ' ', + score: 0, + hardmode: false, + } + } + + async fn run(&mut self, _: Vec) -> Result<(), Error> { + Screen::application_mode(); + let clone = self.clone_snake(); + + // render the initial state of the screen. + self.render(clone).map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; + + + (5..=7).for_each(|x| { + self.snake.push(Point { x, y: 5 }); + }); + self.head = Point { x: 7, y: 5 }; + self.new_poi(); + + self.gameloop().await?; + + Screen::terminal_mode(); + Ok(()) + } +} + +impl Game { + + async fn gameloop(&mut self) -> Result<(), Error> { // main gameloop + 'gameloop: loop { + + time::wait(0.1); + + if let Some(c) = Stdin::try_keystroke() { + self.mv = c; + } + + //self.mv = Stdin::keystroke().await; + + match self.mv { + 'w' => self.head.y -= 1, + 'a' => self.head.x -= 1, + 's' => self.head.y += 1, + 'd' => self.head.x += 1, + 'x' => break, + _ => continue, + } + + self.snake.push(Point { x: self.head.x, y: self.head.y }); // new head added + + if self.head == self.poi { + self.new_poi(); + self.score += 1 + } else { + self.snake.pop().unwrap(); // tail removed if score does not increase + } + + if self.lose_condition() { + self.render_end_screen().map_err(|_| Error::ApplicationError(String::from("failed to render game over screen")))?; + while let chr = KEYBOARD.lock().get_keystroke().await { + match chr { + 'x' => break 'gameloop, + _ => continue, + } + } + } + + let clone = self.clone_snake(); + self.render(clone).map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; + }; + Ok(()) + } + + fn new_poi(&mut self) { + self.poi = Point { x: Random::int(3, 76) as i8, y: Random::int(3, 21) as i8 } + } + + fn render(&mut self, snake: Vec) -> Result<(), ()> { + let mut frame = vec![vec![ScreenChar::null(); 80]; 25]; + snake.into_iter().for_each(|p| { + frame[p.y as usize][p.x as usize] = ScreenChar::new('@' as u8, ColorCode::new(Color::Cyan, Color::Black)); + }); + + frame[self.poi.y as usize][self.poi.x as usize] = ScreenChar::new('o' as u8, ColorCode::new(Color::Red, Color::Black)); + let literal = format!("snake go brr score: {}", self.score); + let msg = Game::centre_text(80, literal); + frame[1] = msg.chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::LightGreen, Color::Black))).collect(); + + let mut elem = ColouredElement::generate(frame, (80, 25)); + elem.render((0,0)) + } + + fn lose_condition(&mut self) -> bool { + let cloned = self.clone_snake(); + let snake_overlaps = (1..cloned.len()).any(|i| cloned[i..].contains(&cloned[i - 1])); // checks if any part of the snake overlaps itself + let out_of_bounds = cloned.iter().filter(|p| p.x < 0 || p.y < 0 || p.x > 79 || p.y > 24).count() > 0; // checks if the snake goes out of bounds + + snake_overlaps || out_of_bounds + } + + fn centre_text(dims: usize, text: String) -> String { // centres text in a string of whitespace of a given length + let max_pad = dims / 2; + let mut msg = String::new(); + msg.push_str(" ".repeat(max_pad - round_up(text.len() as f64 / 2.0)).as_str()); + msg.push_str(text.as_str()); + msg.push_str(" ".repeat(max_pad - round_down(text.len() as f64 / 2.0 + 0.51)).as_str()); + msg + } + + fn render_end_screen(&mut self) -> Result<(), ()> { + let mut frame = vec![vec![ScreenChar::null(); 80]; 25]; + + frame[10] = Game::centre_text(80, String::from("u lost")).chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::Red, Color::Black))).collect(); + frame[12] = Game::centre_text(80, String::from(format!("ur score was {}", self.score))).chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::LightGreen, Color::Black))).collect(); + frame[14] = Game::centre_text(80, String::from("L bozo")).chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::Red, Color::Black))).collect(); + + + let mut elem = ColouredElement::generate(frame, (80, 25)); + elem.render((0,0)) + } + + fn clone_snake(&mut self) -> Vec { + let mut cloned= Vec::new(); + let mut snake = SegQueue::new(); + while !self.snake.is_empty() { + let item = self.snake.pop().unwrap(); + cloned.push(item.clone()); + snake.push(item); + } + self.snake = snake; + cloned + } +} + + + + + + + + + + + + + +struct Snake { + ai_controlled: bool, + head: Point, + tail: Vec, + dir: Direction, +} + +impl Snake { + fn ai(id: usize) -> Self { + Self { + ai_controlled: true, + head: Point { x: 1 + id as i8 * 2, y: 1 }, + tail: Vec::new(), + dir: Direction::Up + } + } + fn player(id: usize) -> Self { + Self { + ai_controlled: false, + head: Point { x: 1 + id as i8 * 2, y: 1 }, + tail: Vec::new(), + dir: Direction::Up, + } + } + + fn next(&mut self, points_of_interest: &Vec, tails: &Vec) -> Status { // returns (lose_condition, scored) + + // uses pathing algorithm if ai else keyboard input if human + if self.ai_controlled { + self.dir = self.decide_dir(); + } else { + if let Some(c) = Stdin::try_keystroke() { + self.dir = match c { + 'w' => Direction::Up, + 'a' => Direction::Left, + 's' => Direction::Down, + 'd' => Direction::Right, + 'x' => return Status::Exited, + _ => self.dir.clone(), + } + } + } + + self.tail.push(self.head.clone()); + + match self.dir { + Direction::Up => self.head.y -= 1, + Direction::Down => self.head.y += 1, + Direction::Left => self.head.x -= 1, + Direction::Right => self.head.x += 1, + } + + if self.lose_condition(tails) { + return Status::Lost; + } + + if points_of_interest.contains(&self.head) { + Status::Scored + } else { + self.tail.remove(0); + Status::None + } + } + + fn decide_dir(&mut self) -> Direction { + unimplemented!() // implement a basic pathfinding or random movement algorithm + } + + fn lose_condition(&mut self, tails: &Vec) -> bool { // where tails includes the tail of every other snake + let p = self.head.clone(); + + let snake_overlaps = tails.contains(&self.head); // checks if any part of the snake overlaps itself + let out_of_bounds = p.x < 0 || p.y < 0 || p.x > 79 || p.y > 24; // checks if the snake goes out of bounds + + snake_overlaps || out_of_bounds + } +} + + +fn round_up(n: f64) -> usize { + (n + 0.99) as usize +} +fn round_down(n: f64) -> usize { + n as usize +}