diff --git a/src/user/bin/gameoflife.rs b/src/user/bin/gameoflife.rs new file mode 100644 index 0000000..bc63611 --- /dev/null +++ b/src/user/bin/gameoflife.rs @@ -0,0 +1,123 @@ +use alloc::borrow::ToOwned; +use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; +use alloc::boxed::Box; +use crate::std::application::{Application, Error}; +use async_trait::async_trait; +use crate::kernel::render::{Color, ColorCode, ScreenChar}; +use crate::{println, serial_println}; +use crate::std::frame::ColouredElement; +use crate::std::io::{Screen, Stdin}; +use crate::std::time::wait; +use crate::user::bin::snake::Game; + +pub struct GameOfLife { + frame: [[ScreenChar; 80]; 25], +} + +const LOOP_SPEED: f64 = 0.1; + +#[async_trait] +impl Application for GameOfLife { + fn new() -> Self { + Self { + frame: [[ScreenChar::null(); 80]; 25], + } + } + async fn run(&mut self, args: Vec) -> Result<(), Error> { + // setup: + Screen::application_mode(); + + let xoffset = 38; + let yoffset = 5; + + // example pattern + self.activate(0 + xoffset, 1 + yoffset); + self.activate(1 + xoffset, 0 + yoffset); + self.activate(1 + xoffset, 1 + yoffset); + self.activate(2 + xoffset, 1 + yoffset); + + self.activate(0 + xoffset, 4 + yoffset); + self.activate(1 + xoffset, 4 + yoffset); + self.activate(2 + xoffset, 4 + yoffset); + + self.activate(0 + xoffset, 6 + yoffset); + self.activate(2 + xoffset, 6 + yoffset); + self.activate(0 + xoffset, 7 + yoffset); + self.activate(2 + xoffset, 7 + yoffset); + + self.activate(0 + xoffset, 9 + yoffset); + self.activate(1 + xoffset, 9 + yoffset); + self.activate(2 + xoffset, 9 + yoffset); + + self.activate(0 + xoffset, 12 + yoffset); + self.activate(1 + xoffset, 13 + yoffset); + self.activate(1 + xoffset, 12 + yoffset); + self.activate(2 + xoffset, 12 + yoffset); + + self.mainloop()?; + + Screen::terminal_mode(); + Ok(()) + } +} + +impl GameOfLife { + fn activate(&mut self, x: u8, y: u8) { + self.frame[24 - y as usize][x as usize] = ScreenChar::new('#' as u8, ColorCode::new(Color::Green, Color::Black)); + } + fn mainloop(&mut self) -> Result<(), Error> { + 'mainloop: loop { + // render element previous frame before resetting. + + wait(LOOP_SPEED); + + self.render().map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; + match Stdin::try_keystroke() { + Some('x') => break 'mainloop, + _ => {}, + } + + // TODO: Logic goes here + + let mut new_frame = [[ScreenChar::null(); 80]; 25]; + + self.frame.iter().enumerate().for_each(|(y, row)| row.iter().enumerate().for_each(|(x, chr)| { + new_frame[y][x] = self.get_new_value(x as u8, y as u8); + })); + + self.frame = new_frame; + } + Ok(()) + } + + fn get_new_value(&self, x: u8, y: u8) -> ScreenChar { + let adjacent = vec![(0i32, 1i32), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1)].into_iter().map(|(relx, rely)| { + (x as i32 + relx, y as i32 + rely) + }).filter(|(absx, absy)| { + 0 <= *absx && *absx < 80 && 0 <= *absy && *absy < 25 + }).collect::>(); + + let alive = adjacent.iter().filter(|(x, y)| self.frame[*y as usize][*x as usize] == ScreenChar::new('#' as u8, ColorCode::new(Color::Green, Color::Black))).count(); + + if alive == 2 { + if self.frame[y as usize][x as usize] == ScreenChar::new('#' as u8, ColorCode::new(Color::Green, Color::Black)) { + return ScreenChar::new('#' as u8, ColorCode::new(Color::Green, Color::Black)); + } else { + return ScreenChar::null(); + } + } else if alive == 3 { + ScreenChar::new('#' as u8, ColorCode::new(Color::Green, Color::Black)) + } else { + ScreenChar::null() + } + } + + fn render(&self) -> Result<(), ()> { + let cloned_frame = self.frame.iter().map(|r| r.iter().map(|c| c.clone()).collect::>()).collect::>>(); + let mut elem = ColouredElement::generate(cloned_frame, (80, 25)); + elem.render((0,0)); + Ok(()) + } +} \ No newline at end of file diff --git a/src/user/bin/mod.rs b/src/user/bin/mod.rs index 17ce021..c766da5 100644 --- a/src/user/bin/mod.rs +++ b/src/user/bin/mod.rs @@ -9,3 +9,5 @@ mod gigachad_detector; //mod shellrewrite; mod snake; mod grapher; +mod gameoflife; +mod tetris; diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index 21e2bfd..5a7593c 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -6,7 +6,7 @@ use x86_64::instructions::interrupts; use alloc::{boxed::Box, string::{String, ToString}, vec, vec::Vec}; use vga::writers::{GraphicsWriter, PrimitiveDrawing}; -use crate::{print, println, std, std::application::{Application, Error}, user::bin::*}; +use crate::{print, printerr, println, std, std::application::{Application, Error}, user::bin::*}; use crate::std::io::{Color, write, Screen, Stdin}; use crate::std::random::Random; use crate::user::bin::gigachad_detector::GigachadDetector; @@ -51,11 +51,19 @@ pub async fn eventloop() { } fn handle_error(e: Error) { - if let Error::ApplicationError(s) = e { - println!("there was an error! exiting program!: {}", s); - } else { - println!("there was an error! exiting program!"); - + match e { + Error::EmptyCommand => { + printerr!("empty command"); + }, + Error::UnknownCommand(cmd_str) => { + printerr!("unknown command: '{}'", cmd_str); + }, + Error::ApplicationError(e) => { + printerr!("application returned error:\n{}", e); + }, + Error::CommandFailed(e) => { + printerr!("command failed:\n{}", e); + }, } } @@ -112,8 +120,17 @@ async fn exec() -> Result<(), Error> { } "snake" => { let mut game = snake::Game::new(); - game.run(Vec::new()).await; + game.run(args).await?; } + "gameoflife" => { + let mut game = gameoflife::GameOfLife::new(); + game.run(Vec::new()).await?; + } + "tetris" => { + let mut game = tetris::TetrisEngine::new(); + game.run(Vec::new()).await?; + } + "gigachad?" => { let mut gigachad_detector = GigachadDetector::new(); gigachad_detector.run(args).await?; @@ -122,13 +139,12 @@ async fn exec() -> Result<(), Error> { "wait" => { use std::time::wait; - for _ in 0..20 { - wait(0.5); - let key = Stdin::try_keystroke(); - println!("waited {}", match key { - Some(c) => c, - None => '_', - }); + if args.len() != 1 { + return Err(Error::CommandFailed("exactly one argument must be provided".to_string())) + } + if let Ok(time) = args[0].parse::() { + wait(time as f64); + println!("waited for {}s", time); } } @@ -149,12 +165,6 @@ async fn exec() -> Result<(), Error> { // not sure why this code was here but leaving it in case weird bugs happen so i remember to add it back if so //interrupts::without_interrupts(|| {}); } - - "print" => { - use crate::std::os::OS; - let x: String = OS.lock().version.clone(); - println!("{}", x); - } "switch" => { Screen::switch(); } @@ -170,7 +180,7 @@ async fn exec() -> Result<(), Error> { } _ => { return Err(Error::UnknownCommand( - "command not yet implemented".to_string(), + cmd )) } }; diff --git a/src/user/bin/snake.rs b/src/user/bin/snake.rs index 98eeb27..7e2d9ad 100644 --- a/src/user/bin/snake.rs +++ b/src/user/bin/snake.rs @@ -1,4 +1,4 @@ -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::{format, vec, vec::Vec, boxed::Box}; use alloc::borrow::ToOwned; use core::arch::x86_64::_mm_test_all_ones; @@ -10,12 +10,19 @@ 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::{println, serial_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(PartialEq)] +enum Gamemode { + SinglePlayer, + WithAI(u8, u8, u8), // number of ai, ai length, number of poi's + Uninitialised, +} + #[derive(Clone, Debug, PartialEq)] enum Status { @@ -29,7 +36,7 @@ pub struct Game { snakes: Vec, pois: Vec, score: u8, - hardmode: bool, + gamemode: Gamemode, } @@ -41,36 +48,70 @@ impl Application for Game { snakes: Vec::new(), pois: Vec::new(), score: 0, - hardmode: false, + gamemode: Gamemode::Uninitialised, } } - async fn run(&mut self, _: Vec) -> Result<(), Error> { - //Screen::application_mode(); + async fn run(&mut self, args: Vec) -> Result<(), Error> { + let mut settings = [0, 0, 0]; // ai_count, snake_len, poi_count - // make the first poi - - self.snakes.push(Snake::player(0)); - - for i in 0..5 { - self.new_poi(); - self.snakes.push(Snake::ai(i + 1)); + if args.len() == 0 { + self.gamemode == Gamemode::SinglePlayer; + } else { + match args[0].as_str() { + "easy" => { + self.gamemode = Gamemode::SinglePlayer; + }, + "normal" => { + self.gamemode = Gamemode::WithAI(5, 5, 5); + }, + "impossible" => { + self.gamemode = Gamemode::WithAI(10, 15, 5); + } + "chaos" => { + self.gamemode = Gamemode::WithAI(20, 15, 20); + } + _ => { + self.gamemode = Gamemode::SinglePlayer; + }, + } } + // sets up game based on difficulty + self.prepare(); + + // switch OS to 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")))?; - // run the game self.gameloop().await?; - // return to the terminal - //Screen::terminal_mode(); + Screen::terminal_mode(); Ok(()) } } impl Game { + fn prepare(&mut self) { + self.snakes.push(Snake::player(0, 3)); + + if let Gamemode::WithAI(ai_count, snake_len, poi_count) = self.gamemode { + for i in 0..ai_count { + self.snakes.push(Snake::ai(i as usize + 1, snake_len)); + } + (0..poi_count).for_each(|_| self.new_poi()); + } else { + self.new_poi() + } + } + + fn respawn_snakes(&mut self) { + if let Gamemode::WithAI(ai_count, snake_len, _poi) = self.gamemode { + self.snakes.push(Snake::ai(self.snakes.len() + 1, snake_len)); + } + } async fn gameloop(&mut self) -> Result<(), Error> { // main gameloop let mut all_points: Vec; @@ -84,11 +125,14 @@ impl Game { for i in 0..length { let points: Vec = self.snakes.clone().into_iter().map(|s| s.tail).flatten().collect(); - let res = self.snakes[i].next(&self.pois, &points); + let res = self.snakes[i].next(&points, &self.pois); match res { Status::Lost => { - if !self.snakes[i].ai_controlled { + if self.snakes[i].ai_controlled { + self.snakes.remove(i); + self.respawn_snakes(); + } else { self.render_end_screen().map_err(|_| Error::ApplicationError(String::from("failed to render end screen")))?; // loop triggers when game is lost loop { @@ -111,6 +155,7 @@ impl Game { Status::None => {}, } } + self.snakes.retain(|s| s.alive); self.render().map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; }; @@ -128,9 +173,18 @@ impl Game { fn render(&mut self) -> Result<(), ()> { let mut frame = vec![vec![ScreenChar::null(); 80]; 25]; - 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)); - }); + let mut curr_colour = ColorCode::new(Color::LightBlue, Color::Black); + + for s in self.snakes.clone() { + curr_colour = if s.ai_controlled { + ColorCode::new(Color::Cyan, Color::Black) + } else { + ColorCode::new(Color::LightGreen, Color::Black) + }; + for point in s.tail.iter() { + frame[24 - point.y as usize][point.x as usize] = ScreenChar::new('@' as u8, curr_colour); + } + } self.pois.iter().for_each(|poi| { frame[24 - poi.y as usize][poi.x as usize] = ScreenChar::new('o' as u8, ColorCode::new(Color::Red, Color::Black)); @@ -138,7 +192,11 @@ impl Game { 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(); + msg.chars().enumerate().for_each(|(i, c)| { + if c != ' ' { + frame[1][i] = ScreenChar::new(c as u8, ColorCode::new(Color::LightGreen, Color::Black)) + } + }); let mut elem = ColouredElement::generate(frame, (80, 25)); elem.render((0,0)); @@ -185,43 +243,45 @@ struct Snake { head: Position, tail: Vec, dir: Direction, + alive: bool, } impl Snake { - fn ai(id: usize) -> Self { + fn ai(id: usize, len: u8) -> Self { Self { ai_controlled: true, - 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, + head: Position { x: 2 + 2*id as i64 * 2, y: 9 }, + tail: (1..=len as i64).map(|p| Position { x: 2 + 2*id as i64, y: 5 + p}).collect(), + dir: Direction::Degrees0, + alive: true, } } - fn player(id: usize) -> Self { + fn player(id: usize, len: u8) -> Self { Self { ai_controlled: false, - 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, + head: Position { x: 2 + 2*id as i64, y: 9 }, + tail: (1..=len as i64).map(|p| Position { x: 2 + 2*id as i64, y: 5 + p}).collect(), + dir: Direction::Degrees0, + alive: true, } } - fn next(&mut self, points_of_interest: &Vec, tails: &Vec) -> Status { // returns (lose_condition, scored) + fn next(&mut self, tails: &Vec, points_of_interest: &Vec) -> Status { // returns (lose_condition, scored) // uses pathing algorithm if ai else keyboard input if human if self.ai_controlled { - self.dir = PathFinder::decide(&self.head, points_of_interest, tails); + self.dir = PathFinder::decide(&self.head, tails, points_of_interest); } else { - // 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; + if let Some(c) = Stdin::try_keystroke() { + self.dir = match c { + 'w' => Direction::Degrees0, + 'a' => Direction::Degrees270, + 's' => Direction::Degrees180, + 'd' => Direction::Degrees90, + 'x' => return Status::Exited, + _ => self.dir.clone(), + }; + } } if self.dir != Direction::None { @@ -229,14 +289,15 @@ impl Snake { } match self.dir { - 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::Degrees0 => self.head.y += 1, + Direction::Degrees180 => self.head.y -= 1, + Direction::Degrees270 => self.head.x -= 1, + Direction::Degrees90 => self.head.x += 1, Direction::None => {}, } if self.lose_condition(tails) { + self.alive = false; self.tail.remove(0); return Status::Lost; } @@ -266,7 +327,7 @@ 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); + let rel_pos = head.get_offset(&nearest_poi); // check actions don't lose them the game let mut possible_moves = Vec::new(); @@ -274,27 +335,28 @@ impl PathFinder { h = Position { x: head.x + 1, y: head.y }; if !(PathFinder::check_bounds(&h) || PathFinder::check_collision(&h, &tails)) { - possible_moves.push(Direction::PosX); + possible_moves.push(Direction::Degrees90); } h = Position { x: head.x - 1, y: head.y }; if !(PathFinder::check_bounds(&h) || PathFinder::check_collision(&h, &tails)) { - possible_moves.push(Direction::NegX); + possible_moves.push(Direction::Degrees270); } h = Position { x: head.x, y: head.y + 1 }; if !(PathFinder::check_bounds(&h) || PathFinder::check_collision(&h, &tails)) { - possible_moves.push(Direction::PosY); + possible_moves.push(Direction::Degrees0); } h = Position { x: head.x, y: head.y - 1 }; if !(PathFinder::check_bounds(&h) || PathFinder::check_collision(&h, &tails)) { - possible_moves.push(Direction::NegY); + possible_moves.push(Direction::Degrees180); } if possible_moves.is_empty() { - panic!("no possible moves"); - return Direction::None; + // panic!("no possible moves"); // use for debugging if a snake cannot find a move for some reason + return Direction::Degrees90; } else { let optimal = PathFinder::optimal_move(head, &rel_pos, &possible_moves); - println!("{:?} {:?} {:?} {:?}", nearest_poi, rel_pos, head, optimal); + // serial_println!("{:?} {:?} {:?} {:?}", nearest_poi, rel_pos, head, optimal); + return optimal; } Direction::None @@ -315,19 +377,19 @@ impl PathFinder { } if rel_pos.x < 0 { - optimal_moves[x_offset] = Direction::NegX; - optimal_moves[x_offset + 2] = Direction::PosX; + optimal_moves[x_offset] = Direction::Degrees270; + optimal_moves[x_offset + 2] = Direction::Degrees90; } else { - optimal_moves[x_offset] = Direction::PosX; - optimal_moves[x_offset + 2] = Direction::NegX; + optimal_moves[x_offset] = Direction::Degrees90; + optimal_moves[x_offset + 2] = Direction::Degrees270; } if rel_pos.y < 0 { - optimal_moves[y_offset] = Direction::NegY; - optimal_moves[y_offset + 2] = Direction::PosY; + optimal_moves[y_offset] = Direction::Degrees180; + optimal_moves[y_offset + 2] = Direction::Degrees0; } else { - optimal_moves[y_offset] = Direction::PosY; - optimal_moves[y_offset + 2] = Direction::NegY; + optimal_moves[y_offset] = Direction::Degrees0; + optimal_moves[y_offset + 2] = Direction::Degrees180; } //println!("moves: {:?}, optimal_moves: {:?}, rel_pos: {:?}", moves, optimal_moves, rel_pos); for m in optimal_moves { diff --git a/src/user/bin/tetris.rs b/src/user/bin/tetris.rs new file mode 100644 index 0000000..6f62c3f --- /dev/null +++ b/src/user/bin/tetris.rs @@ -0,0 +1,119 @@ +use async_trait::async_trait; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; +use crate::kernel::render::ScreenChar; +use crate::{serial_print, serial_println}; +use crate::std::application::{Application, Error}; +use crate::std::io::Screen; +use crate::user::lib::coords::{Direction, Position, PositionReal}; +use crate::user::lib::libgui::libgui_core::Pos; + + +pub(crate) struct TetrisEngine { + score: u32, + next: TetrisPiece, + completed_frame: [[ScreenChar; 80]; 25], // this frame does not contain falling blocks, only static ones + +} + +#[async_trait] +impl Application for TetrisEngine { + fn new() -> Self { + Self { + score: 0, + next: TetrisPiece::new(PieceType::OPiece), + completed_frame: [[ScreenChar::null(); 80]; 25], + } + } + async fn run(&mut self, args: Vec) -> Result<(), Error> { + // setup: + Screen::application_mode(); + + let piece_type = PieceType::OPiece; + let mut piece = TetrisPiece::new(piece_type); + + serial_println!("{:?}", piece.get_positions()); + piece.rotate_right(); + serial_println!("{:?}", piece.get_positions()); + + + Screen::terminal_mode(); + Ok(()) + } +} + + + + + + + + + + + +enum PieceType { + OPiece, + IPiece, + JPiece, + LPiece, + SPiece, + ZPiece, +} + +struct TetrisPiece { + type_: PieceType, + pos: Position, + rotation: Direction, +} + +impl TetrisPiece { + fn new(type_: PieceType) -> Self { + Self { + type_, + pos: Position { x: 40, y: 30 }, + rotation: Direction::Degrees0, + } + } + fn rotate_right(&mut self) { + self.rotation = match self.rotation { + Direction::Degrees90 => Direction::Degrees180, + Direction::Degrees180 => Direction::Degrees270, + Direction::Degrees270 => Direction::Degrees0, + Direction::Degrees0 => Direction::Degrees90, + Direction::None => panic!("direction should never be none in this application"), + }; + } + + /// function that maps the coordinates of the object. + fn get_positions(&self) -> Vec { + match self.type_ { + PieceType::OPiece => { + let positions = vec![ + PositionReal { x: -0.5, y: -0.5 }, + PositionReal { x: 0.5, y: -0.5 }, + PositionReal { x: -0.5, y: 0.5 }, + PositionReal { x: 0.5, y: 0.5 }, + ]; + positions.into_iter().map(|p| + ( p.rotate(self.rotation.clone()) + self.pos.clone().real() + PositionReal { x: -0.5, y: 0.5 } ).integer() + ).collect::>() + } + _ => unimplemented!("E"), + } + } +} + + + + + + + + + + + + diff --git a/src/user/lib/coords.rs b/src/user/lib/coords.rs index ee6c0bb..678d83a 100644 --- a/src/user/lib/coords.rs +++ b/src/user/lib/coords.rs @@ -1,8 +1,8 @@ use alloc::borrow::ToOwned; use alloc::vec::Vec; +use libm::sqrt; use crate::println; - #[derive(Clone, Debug, PartialEq)] pub enum Line { Vertical(i64), @@ -15,61 +15,267 @@ pub struct Position { pub y: i64, } +/// a point represented with x and y coordinates. impl Position { + + /// checks if the point is on a given line pub fn touches_line(&self, line: &Line) -> bool { match line { - Line::Vertical(y) => self.y == *y, - Line::Horizontal(x) => self.x == *x, + Line::Vertical(y) => self.y as i64 == *y, + Line::Horizontal(x) => self.x as i64 == *x, } } + /// checks if two points are equal and returns the x and y equality result for each pub fn aligns(&self, other: &Position) -> (bool, bool) { (self.x == other.x, self.y == other.y) } - pub fn distance(&self, other: &Position) -> Position { + /// calculates x + y distance between two points + pub fn get_offset(&self, other: &Position) -> Position { Position { x: other.x - self.x, y: other.y - self.y } } + pub fn diagonal_distance(&self, other: &Position) -> i64 { + sqrt((self.x - other.x).pow(2) as f64 + (self.y - other.y).pow(2) as f64) as i64 + } + 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()) + 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); + let p = self.get_offset(p); p.x.abs() + p.y.abs() }); points.first().unwrap().to_owned() } + + + pub fn rotated_around(&self, angle: Direction, p: Position) -> Position { // rotates by an angle around a point + + // gets coords relative to point to rotate around + let mut p_offset = self.get_offset(&p); + + p_offset = match angle { // default angle is posy = 0 degrees and negy = 180 + Direction::Degrees0 => Position { + x: p_offset.x, + y: p_offset.y, + }, + Direction::Degrees90 => Position { + x: -p_offset.y, + y: p_offset.x, + }, + Direction::Degrees180 => Position { + x: -p_offset.x, + y: -p_offset.y, + }, + Direction::Degrees270 => Position { + x: p_offset.y, + y: -p_offset.x, + }, + Direction::None => panic!("direction should never be none in this application"), + }; + + return p_offset + p; + } + + pub fn rotate(&self, angle: Direction) -> Position { // rotates by an angle around origin + match angle { // default angle is posy = 0 degrees and negy = 180 + Direction::Degrees0 => Position { + x: self.x, + y: self.y, + }, + Direction::Degrees90 => Position { + x: -self.y, + y: self.x, + }, + Direction::Degrees180 => Position { + x: -self.x, + y: -self.y, + }, + Direction::Degrees270 => Position { + x: self.y, + y: -self.x, + }, + Direction::None => panic!("direction should never be none in this application"), + } + } + pub fn real(self) -> PositionReal { + PositionReal { + x: self.x as f64, + y: self.y as f64, + } + } + } +impl core::ops::Add for Position { + type Output = Position; + fn add(self, other: Position) -> Position { + Position { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +/// can be expressed as degrees relative to north (where north faces towards the top of the screen) +/// none variant used for when the value is missing / no value is decided. #[derive(Clone, Debug, PartialEq)] pub enum Direction { - PosY, - NegY, - PosX, - NegX, + Degrees0, + Degrees180, + Degrees90, + Degrees270, 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::Degrees0 => Direction::Degrees180, + Direction::Degrees180 => Direction::Degrees0, + Direction::Degrees90 => Direction::Degrees270, + Direction::Degrees270 => Direction::Degrees90, Direction::None => Direction::None, } } +} + + +#[derive(Clone, Debug, PartialEq)] +pub struct PositionReal { + pub x: f64, + pub y: f64, +} + +/// a point represented with x and y coordinates. +impl PositionReal { + + /// checks if the point is on a given line + pub fn touches_line(&self, line: &Line) -> bool { + match line { + Line::Vertical(y) => self.y as i64 == *y, + Line::Horizontal(x) => self.x as i64 == *x, + } + } + + /// checks if two points are equal and returns the x and y equality result for each + pub fn aligns(&self, other: &PositionReal) -> (bool, bool) { + (self.x == other.x, self.y == other.y) + } + + /// calculates x + y distance between two points + pub fn get_offset(&self, other: &PositionReal) -> PositionReal { + PositionReal { + x: other.x - self.x, + y: other.y - self.y + } + } + + pub fn diagonal_distance(&self, other: &PositionReal) -> f64 { + sqrt((self.x - other.x)*(self.x - other.x) + (self.y - other.y)* (self.y - other.y) as f64) + } + + pub fn as_usize(&self) -> (usize, usize) { + (self.x as usize, self.y as usize) + } + + pub fn magnitude(&self) -> i64 { + let absx = if self.x >= 0.0 { self.x } else { -self.x }; + let absy = if self.y >= 0.0 { self.y } else { -self.y }; + (absx + absy) as i64 + + } + + pub fn nearest(&self, points: &Vec) -> PositionReal { + + let mut points = points.clone(); + points.sort_by_key(|p| { + let p = self.get_offset(p); + let absx = if p.x >= 0.0 { p.x } else { -p.x }; + let absy = if p.y >= 0.0 { p.y } else { -p.y }; + (absx + absy) as i64 + }); + points.first().unwrap().to_owned() + } + + + pub fn rotated_around(&self, angle: Direction, p: PositionReal) -> PositionReal { // rotates by an angle around a point + + // gets coords relative to point to rotate around + let mut p_offset = self.get_offset(&p); + + p_offset = match angle { // default angle is posy = 0 degrees and negy = 180 + Direction::Degrees0 => PositionReal { + x: p_offset.x, + y: p_offset.y, + }, + Direction::Degrees90 => PositionReal { + x: -p_offset.y, + y: p_offset.x, + }, + Direction::Degrees180 => PositionReal { + x: -p_offset.x, + y: -p_offset.y, + }, + Direction::Degrees270 => PositionReal { + x: p_offset.y, + y: -p_offset.x, + }, + Direction::None => panic!("direction should never be none in this application"), + }; + + return p_offset + p; + } + + pub fn rotate(&self, angle: Direction) -> PositionReal { // rotates by an angle around origin + match angle { // default angle is posy = 0 degrees and negy = 180 + Direction::Degrees0 => PositionReal { + x: self.x, + y: self.y, + }, + Direction::Degrees90 => PositionReal { + x: -self.y, + y: self.x, + }, + Direction::Degrees180 => PositionReal { + x: -self.x, + y: -self.y, + }, + Direction::Degrees270 => PositionReal { + x: self.y, + y: -self.x, + }, + Direction::None => panic!("direction should never be none in this application"), + } + } + pub fn integer(self) -> Position { + Position { + x: self.x as i64, + y: self.y as i64, + } + } + +} + +impl core::ops::Add for PositionReal { + type Output = PositionReal; + fn add(self, other: PositionReal) -> PositionReal { + PositionReal { + x: self.x + other.x, + y: self.y + other.y, + } + } } \ No newline at end of file