diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d267b3f..ac1e823 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [package.metadata.bootimage] test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", "-display", "none"] test-success-exit-code = 33 diff --git a/src/user/bin/snake.rs b/src/user/bin/snake.rs index 3784e39..98eeb27 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 alloc::borrow::ToOwned; use core::arch::x86_64::_mm_test_all_ones; use core::cell::RefCell; use async_trait::async_trait; @@ -9,30 +10,12 @@ use crate::kernel::tasks::keyboard::KEYBOARD; use crossbeam_queue::SegQueue; use lazy_static::lazy_static; use crate::kernel::render::{ColorCode, ScreenChar}; +use crate::{println}; use crate::std::application::{Application, Error}; use crate::std::random::Random; use crate::system::std::frame::ColouredElement; +use super::super::lib::coords::{Line, Position, Direction}; -#[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, -} #[derive(Clone, Debug, PartialEq)] enum Status { @@ -44,7 +27,7 @@ enum Status { pub struct Game { snakes: Vec, - pois: Vec, + pois: Vec, score: u8, hardmode: bool, } @@ -63,25 +46,26 @@ impl Application for Game { } async fn run(&mut self, _: Vec) -> Result<(), Error> { - Screen::application_mode(); + //Screen::application_mode(); - // render the initial state of the screen. - self.render().map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; // make the first poi - self.snakes.borrow_mut().push(Snake::player(0)); + self.snakes.push(Snake::player(0)); for i in 0..5 { self.new_poi(); - self.snakes.borrow_mut().push(Snake::ai(i + 1)); + self.snakes.push(Snake::ai(i + 1)); } + // render the initial state of the screen. + self.render().map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; + // run the game self.gameloop().await?; // return to the terminal - Screen::terminal_mode(); + //Screen::terminal_mode(); Ok(()) } } @@ -89,47 +73,43 @@ impl Application for Game { impl Game { async fn gameloop(&mut self) -> Result<(), Error> { // main gameloop - let mut all_points: Vec; + let mut all_points: Vec; 'gameloop: loop { time::wait(0.1); - let mut points: Vec; + let mut points: Vec; let length = self.snakes.len(); - let mut status = Vec::new(); for i in 0..length { - let points: Vec = self.snakes.clone().into_iter().map(|s| s.tail).flatten().collect(); + let points: Vec = self.snakes.clone().into_iter().map(|s| s.tail).flatten().collect(); let res = self.snakes[i].next(&self.pois, &points); - status.push(res); - - - } - - - let res = self.snakes.iter_mut().map(|s| { - - let points: Vec = self.snakes.clone().into_iter().map(|s| s.tail).flatten().collect(); - s.next(&self.pois, &points) - }).collect::>(); - - - if res.contains(&Status::Lost) { - self.render_end_screen().map_err(|_| Error::ApplicationError(String::from("failed to render end screen")))?; - - // loop triggers when game is lost - loop { - match Stdin::keystroke().await { - 'x' => break 'gameloop, - _ => continue, - } + match res { + Status::Lost => { + if !self.snakes[i].ai_controlled { + self.render_end_screen().map_err(|_| Error::ApplicationError(String::from("failed to render end screen")))?; + // loop triggers when game is lost + loop { + match Stdin::keystroke().await { + 'x' => break 'gameloop, + _ => continue, + } + } + } + }, + Status::Exited => { + break 'gameloop + }, + Status::Scored => { + if !self.snakes[i].ai_controlled { + self.score += 1; + } + self.replace_poi(&self.snakes[i].clone().head); // passes a reference to the location of the current snake's head + }, + Status::None => {}, } - } else if res.contains(&Status::Exited) { - break 'gameloop; - } else if res.contains(&Status::Scored) { - self.score += 1; } self.render().map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; @@ -138,22 +118,22 @@ impl Game { } fn new_poi(&mut self) { - self.pois.push(Point { x: Random::int(3, 76) as i8, y: Random::int(3, 21) as i8 }); + self.pois.push(Position { x: Random::int(3, 76) as i64, y: Random::int(3, 21) as i64 }); } - fn replace_poi(&mut self, poi: &Point) { + fn replace_poi(&mut self, poi: &Position) { self.pois.remove(self.pois.iter().position(|p| p == poi).unwrap()); self.new_poi(); } fn render(&mut self) -> Result<(), ()> { let mut frame = vec![vec![ScreenChar::null(); 80]; 25]; - self.snakes.borrow().clone().into_iter().map(|s| s.tail).flatten().for_each(|p| { - frame[p.y as usize][p.x as usize] = ScreenChar::new('@' as u8, ColorCode::new(Color::Cyan, Color::Black)); + self.snakes.clone().into_iter().map(|s| s.tail).flatten().for_each(|p| { + frame[24 - p.y as usize][p.x as usize] = ScreenChar::new('@' as u8, ColorCode::new(Color::Cyan, Color::Black)); }); self.pois.iter().for_each(|poi| { - frame[poi.y as usize][poi.x as usize] = ScreenChar::new('o' as u8, ColorCode::new(Color::Red, Color::Black)); + frame[24 - poi.y as usize][poi.x as usize] = ScreenChar::new('o' as u8, ColorCode::new(Color::Red, Color::Black)); }); let literal = format!("snake go brr score: {}", self.score); @@ -183,7 +163,6 @@ impl Game { 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)) } @@ -203,8 +182,8 @@ impl Game { #[derive(Debug, Clone)] struct Snake { ai_controlled: bool, - head: Point, - tail: Vec, + head: Position, + tail: Vec, dir: Direction, } @@ -212,71 +191,69 @@ 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 + head: Position { x: 4 + 4*id as i64 * 2, y: 9 }, + tail: (1..4).map(|p| Position { x: 4 + 4*id as i64, y: 5 + p}).collect(), + dir: Direction::PosY, } } 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, + head: Position { x: 4 + 4*id as i64, y: 9 }, + tail: (1..4).map(|p| Position { x: 4 + 4*id as i64, y: 5 + p}).collect(), + dir: Direction::PosY, } } - fn next(&mut self, points_of_interest: &Vec, tails: &Vec) -> Status { // returns (lose_condition, scored) + 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(); + self.dir = PathFinder::decide(&self.head, points_of_interest, tails); } 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(), - } - } + // if let Some(c) = Stdin::try_keystroke() { + // self.dir = match c { + // 'w' => Direction::PosY, + // 'a' => Direction::NegX, + // 's' => Direction::NegY, + // 'd' => Direction::PosX, + // 'x' => return Status::Exited, + // _ => self.dir.clone(), + // } + // } + self.dir = Direction::None; } - self.tail.push(self.head.clone()); + if self.dir != Direction::None { + 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, + Direction::PosY => self.head.y += 1, + Direction::NegY => self.head.y -= 1, + Direction::NegX => self.head.x -= 1, + Direction::PosX => self.head.x += 1, + Direction::None => {}, } if self.lose_condition(tails) { - if !self.ai_controlled { - return Status::Lost; - } + self.tail.remove(0); + return Status::Lost; } if points_of_interest.contains(&self.head) { - if !self.ai_controlled { - return Status::Scored; - } + return Status::Scored; } else { - self.tail.remove(0); + if self.dir != Direction::None { + 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 + 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 @@ -284,6 +261,98 @@ impl Snake { } } +struct PathFinder {} + +impl PathFinder { + fn decide(head: &Position, tails: &Vec, pois: &Vec) -> Direction { + let nearest_poi = head.nearest(pois); + let rel_pos = head.distance(&nearest_poi); + + // check actions don't lose them the game + let mut possible_moves = Vec::new(); + let mut h: Position; + + h = Position { x: head.x + 1, y: head.y }; + if !(PathFinder::check_bounds(&h) || PathFinder::check_collision(&h, &tails)) { + possible_moves.push(Direction::PosX); + } + h = Position { x: head.x - 1, y: head.y }; + if !(PathFinder::check_bounds(&h) || PathFinder::check_collision(&h, &tails)) { + possible_moves.push(Direction::NegX); + } + h = Position { x: head.x, y: head.y + 1 }; + if !(PathFinder::check_bounds(&h) || PathFinder::check_collision(&h, &tails)) { + possible_moves.push(Direction::PosY); + } + h = Position { x: head.x, y: head.y - 1 }; + if !(PathFinder::check_bounds(&h) || PathFinder::check_collision(&h, &tails)) { + possible_moves.push(Direction::NegY); + } + + if possible_moves.is_empty() { + panic!("no possible moves"); + return Direction::None; + } else { + let optimal = PathFinder::optimal_move(head, &rel_pos, &possible_moves); + println!("{:?} {:?} {:?} {:?}", nearest_poi, rel_pos, head, optimal); + } + + Direction::None + } + + fn optimal_move(head: &Position, rel_pos: &Position, moves: &Vec) -> Direction { + let mut optimal_moves = vec![Direction::None; 4]; + + let x_offset: usize; + let y_offset: usize; + + if rel_pos.x.abs() > rel_pos.y.abs() { + y_offset = 1; + x_offset = 0; + } else { + x_offset = 1; + y_offset = 0; + } + + if rel_pos.x < 0 { + optimal_moves[x_offset] = Direction::NegX; + optimal_moves[x_offset + 2] = Direction::PosX; + } else { + optimal_moves[x_offset] = Direction::PosX; + optimal_moves[x_offset + 2] = Direction::NegX; + } + + if rel_pos.y < 0 { + optimal_moves[y_offset] = Direction::NegY; + optimal_moves[y_offset + 2] = Direction::PosY; + } else { + optimal_moves[y_offset] = Direction::PosY; + optimal_moves[y_offset + 2] = Direction::NegY; + } + //println!("moves: {:?}, optimal_moves: {:?}, rel_pos: {:?}", moves, optimal_moves, rel_pos); + for m in optimal_moves { + if moves.contains(&m) { + return m; + } + }; + + // this should never be used, the above statement should always return a value. + panic!("No optimal move found (this should not happen)"); + } + + + fn check_bounds(head: &Position) -> bool { + head.x < 0 || head.y < 0 || head.x > 79 || head.y > 24 + } + + fn check_collision(head: &Position, tails: &Vec) -> bool { + tails.contains(&head) + } +} + + + + fn round_up(n: f64) -> usize { (n + 0.99) as usize @@ -291,3 +360,4 @@ fn round_up(n: f64) -> usize { fn round_down(n: f64) -> usize { n as usize } + diff --git a/src/user/lib/coords.rs b/src/user/lib/coords.rs new file mode 100644 index 0000000..ee6c0bb --- /dev/null +++ b/src/user/lib/coords.rs @@ -0,0 +1,75 @@ +use alloc::borrow::ToOwned; +use alloc::vec::Vec; +use crate::println; + + +#[derive(Clone, Debug, PartialEq)] +pub enum Line { + Vertical(i64), + Horizontal(i64), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Position { + pub x: i64, + pub y: i64, +} + +impl Position { + pub fn touches_line(&self, line: &Line) -> bool { + match line { + Line::Vertical(y) => self.y == *y, + Line::Horizontal(x) => self.x == *x, + } + } + + pub fn aligns(&self, other: &Position) -> (bool, bool) { + (self.x == other.x, self.y == other.y) + } + + pub fn distance(&self, other: &Position) -> Position { + Position { + x: other.x - self.x, + y: other.y - self.y + } + } + + pub fn as_usize(&self) -> (usize, usize) { + (self.x as usize, self.y as usize) + } + + pub fn magnitude(&self) -> i64 { + (self.x.abs() + self.y.abs()) + } + + pub fn nearest(&self, points: &Vec) -> Position { + + let mut points = points.clone(); + points.sort_by_key(|p| { + let p = self.distance(p); + p.x.abs() + p.y.abs() + }); + points.first().unwrap().to_owned() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Direction { + PosY, + NegY, + PosX, + NegX, + None, +} + +impl Direction { + pub fn rev(&self) -> Direction { + match self { + Direction::PosY => Direction::NegY, + Direction::NegY => Direction::PosY, + Direction::PosX => Direction::NegX, + Direction::NegX => Direction::PosX, + Direction::None => Direction::None, + } + } +} \ No newline at end of file diff --git a/src/user/lib/mod.rs b/src/user/lib/mod.rs index da1a9da..001acf5 100644 --- a/src/user/lib/mod.rs +++ b/src/user/lib/mod.rs @@ -1 +1,2 @@ pub mod libgui; +pub mod coords;