.
This commit is contained in:
FantasyPvP
2023-10-11 23:22:46 +01:00
parent 550b61e07e
commit 3d889c4730
6 changed files with 620 additions and 98 deletions
+123
View File
@@ -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<String>) -> 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::<Vec<(i32, i32)>>();
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::<Vec<ScreenChar>>()).collect::<Vec<Vec<ScreenChar>>>();
let mut elem = ColouredElement::generate(cloned_frame, (80, 25));
elem.render((0,0));
Ok(())
}
}
+2
View File
@@ -9,3 +9,5 @@ mod gigachad_detector;
//mod shellrewrite;
mod snake;
mod grapher;
mod gameoflife;
mod tetris;
+31 -21
View File
@@ -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::<u64>() {
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
))
}
};
+125 -63
View File
@@ -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<Snake>,
pois: Vec<Position>,
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<String>) -> Result<(), Error> {
//Screen::application_mode();
async fn run(&mut self, args: Vec<String>) -> 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<Position>;
@@ -84,11 +125,14 @@ impl Game {
for i in 0..length {
let points: Vec<Position> = 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<Position>,
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<Position>, tails: &Vec<Position>) -> Status { // returns (lose_condition, scored)
fn next(&mut self, tails: &Vec<Position>, points_of_interest: &Vec<Position>) -> 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<Position>, pois: &Vec<Position>) -> 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 {
+119
View File
@@ -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<String>) -> 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<Position> {
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::<Vec<Position>>()
}
_ => unimplemented!("E"),
}
}
}
+220 -14
View File
@@ -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>) -> 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>) -> 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,
}
}
}