initial commit
This commit is contained in:
commit
08592261b3
12 changed files with 822 additions and 0 deletions
170
src/algorithm.rs
Normal file
170
src/algorithm.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
//! ## Algorithm
|
||||
//!
|
||||
//! This module contains the definition of the [`Algorithm`] trait, implemented by [`Maze`] resolution strategies.
|
||||
//! Already existing implementations of that trait can be found in the [`crate::implementations`] module.
|
||||
//!
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{Maze, Pos};
|
||||
|
||||
/// A guess to pass to the current [`Executor`] at the end of every `progress` call.
|
||||
pub struct Guess(Vec<Pos>);
|
||||
|
||||
/// An insight given to the [`Algorithm`] on every `progress` call.
|
||||
/// On the first time about the starting point and every consecutive call about the tail of the previous guess.
|
||||
pub struct Insight<'p> {
|
||||
position: Pos,
|
||||
paths: &'p [Pos],
|
||||
}
|
||||
|
||||
impl<'p> Insight<'p> {
|
||||
fn new(position: Pos, paths: &'p [Pos]) -> Self {
|
||||
Self { paths, position }
|
||||
}
|
||||
|
||||
fn from_position(position: Pos, maze: &'p Maze) -> Self {
|
||||
let paths = maze.paths_from(position);
|
||||
Self::new(position, paths)
|
||||
}
|
||||
|
||||
/// The position of the insight.
|
||||
pub fn position(&self) -> Pos {
|
||||
self.position
|
||||
}
|
||||
|
||||
/// the paths from that position.
|
||||
pub fn paths(&self) -> &[Pos] {
|
||||
self.paths
|
||||
}
|
||||
}
|
||||
|
||||
/// A context given to the [`Algorithm`] on every `progress` call, provide informations about the maze and method to create a [`Guess`].
|
||||
pub struct Context<'m> {
|
||||
maze: &'m Maze,
|
||||
}
|
||||
|
||||
impl<'m> Context<'m> {
|
||||
fn new(maze: &'m Maze) -> Self {
|
||||
Self { maze }
|
||||
}
|
||||
|
||||
/// Constructor for [`Guess`].
|
||||
/// Takes a path, that is a vector of positions from the starting point to the position to discover on the next call to `progress`.
|
||||
pub fn guess(&self, pos: Vec<Pos>) -> Guess {
|
||||
Guess(pos)
|
||||
}
|
||||
|
||||
/// Returns the position of the `start` of the [`Maze`].
|
||||
pub fn start(&self) -> Pos {
|
||||
self.maze.start()
|
||||
}
|
||||
|
||||
/// Returns the position of the `end` of the [`Maze`].
|
||||
pub fn end(&self) -> Pos {
|
||||
self.maze.end()
|
||||
}
|
||||
|
||||
/// Returns the `width` of the [`Maze`].
|
||||
pub fn width(&self) -> isize {
|
||||
self.maze.width()
|
||||
}
|
||||
|
||||
/// Returns the `height` of the [`Maze`].
|
||||
pub fn height(&self) -> isize {
|
||||
self.maze.width()
|
||||
}
|
||||
|
||||
/// Returns a tuple containing both the `width` and `height` of the [`Maze`].
|
||||
pub fn size(&self) -> (isize, isize) {
|
||||
self.maze.size()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait encapsulating the behavior of an algorithm solving mazes.
|
||||
/// Implementing this trait is done by providing a `progress` method which gets called iteratively on each steps of a [`Maze`] resolution.
|
||||
pub trait Algorithm {
|
||||
/// will be called on each step of the traversal of the [`Maze`].
|
||||
/// `insight` is a view on the position discovered on the previous movement.
|
||||
/// `ctx` is a view on the [`Maze`], useful for accessing properties of the maze.
|
||||
fn progress(&mut self, insight: &Insight, ctx: &mut Context) -> Guess;
|
||||
}
|
||||
|
||||
/// A structure holding a [`Maze`] and iteratively solving it with a provided [`Algorithm`].
|
||||
pub struct Executor<Algo>
|
||||
where
|
||||
Algo: Algorithm,
|
||||
{
|
||||
maze: Maze,
|
||||
algorithm: Algo,
|
||||
}
|
||||
|
||||
impl<A> Executor<A>
|
||||
where
|
||||
A: Algorithm,
|
||||
{
|
||||
/// Constructor.
|
||||
pub fn new(maze: Maze, algorithm: A) -> Self {
|
||||
Self { maze, algorithm }
|
||||
}
|
||||
|
||||
/// Submit the maze to the [`Algorithm`] and iteratively progress through the maze driven by said algorithm.
|
||||
pub fn run(&mut self) {
|
||||
let Self { maze, algorithm } = self;
|
||||
let mut insight = Insight::from_position(maze.start(), &maze);
|
||||
let mut tick = 0;
|
||||
let mut tried = HashSet::new();
|
||||
loop {
|
||||
let mut context = Context::new(maze);
|
||||
let Guess(guess) = algorithm.progress(&insight, &mut context);
|
||||
// TODO:
|
||||
// - extract metrics from the context
|
||||
// - check if path is actually a path
|
||||
guess.iter().for_each(|&p| {
|
||||
tried.insert(p);
|
||||
});
|
||||
let tail = *guess.last().expect("returned an empty path");
|
||||
|
||||
// draw
|
||||
Self::draw(maze, &tried, tick, &guess);
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
tick += 1;
|
||||
|
||||
// check for next iteration
|
||||
if maze.is_end(tail) {
|
||||
break;
|
||||
} else {
|
||||
insight = Insight::from_position(tail, maze)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(maze: &Maze, tried: &HashSet<Pos>, tick: usize, path: &Vec<Pos>) {
|
||||
let mut overlay = HashMap::new();
|
||||
for position in tried {
|
||||
overlay.insert(*position, 'T');
|
||||
}
|
||||
|
||||
for position in path {
|
||||
overlay.insert(*position, '#');
|
||||
}
|
||||
|
||||
overlay.insert(*path.last().unwrap(), 'G');
|
||||
|
||||
let text = maze.display(Some(overlay));
|
||||
|
||||
// DIRTY!
|
||||
// print the frame on top of the previous one
|
||||
if tick > 0 {
|
||||
let count = text.lines().count() + 1;
|
||||
let up = termion::cursor::Up(count as u16);
|
||||
print!("{up}")
|
||||
}
|
||||
|
||||
print!("tick {tick}:\n{text}\n");
|
||||
}
|
||||
}
|
29
src/implementations.rs
Normal file
29
src/implementations.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
//! ## Implementations
|
||||
//!
|
||||
//! This module contains concrete types implementing the [`crate::Algorithm`] trait.
|
||||
//! They drive the resolution of a [`crate::Maze`] through different means.
|
||||
//!
|
||||
|
||||
mod breath_first;
|
||||
mod depth_first;
|
||||
|
||||
pub use breath_first::BreathFirst;
|
||||
pub use depth_first::DepthFirst;
|
||||
|
||||
#[test]
|
||||
fn depth_first() {
|
||||
use crate::{algorithm::Executor, generate};
|
||||
let algorithm = DepthFirst::new();
|
||||
let maze = generate(20, 20);
|
||||
let mut executor = Executor::new(maze, algorithm);
|
||||
executor.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn breath_first() {
|
||||
use crate::{algorithm::Executor, generate};
|
||||
let algorithm = BreathFirst::new();
|
||||
let maze = generate(20, 20);
|
||||
let mut executor = Executor::new(maze, algorithm);
|
||||
executor.run();
|
||||
}
|
45
src/implementations/breath_first.rs
Normal file
45
src/implementations/breath_first.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use std::collections::{HashSet, VecDeque};
|
||||
|
||||
use crate::{
|
||||
algorithm::{Context, Guess, Insight},
|
||||
Algorithm, Pos,
|
||||
};
|
||||
|
||||
/// [`Algorithm`] traversing the [`crate::Maze`] as a common graph.
|
||||
/// Storing each possible paths form shortest to longest and extending the shortest ones first.
|
||||
/// Most effective when the resolution is among the shortest possible paths.
|
||||
pub struct BreathFirst {
|
||||
paths: VecDeque<Vec<Pos>>,
|
||||
visited: HashSet<Pos>,
|
||||
last_path: Vec<Pos>,
|
||||
}
|
||||
|
||||
impl BreathFirst {
|
||||
/// Constructor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
paths: VecDeque::new(),
|
||||
visited: HashSet::new(),
|
||||
last_path: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Algorithm for BreathFirst {
|
||||
fn progress(&mut self, insight: &Insight, ctx: &mut Context) -> Guess {
|
||||
self.visited.insert(insight.position());
|
||||
let path = self.last_path.clone();
|
||||
for &branch in insight.paths() {
|
||||
if self.visited.contains(&branch) {
|
||||
continue;
|
||||
}
|
||||
let mut new_path = path.clone();
|
||||
new_path.push(branch);
|
||||
self.paths.push_back(new_path);
|
||||
}
|
||||
|
||||
let path = self.paths.pop_front().expect("no more options");
|
||||
self.last_path = path.clone();
|
||||
ctx.guess(path)
|
||||
}
|
||||
}
|
55
src/implementations/depth_first.rs
Normal file
55
src/implementations/depth_first.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
algorithm::{Context, Guess, Insight},
|
||||
Algorithm, Pos,
|
||||
};
|
||||
|
||||
/// Frame of the stack used by a [`DepthFirst`] to retain its path and possible branches.
|
||||
pub struct Frame {
|
||||
position: Pos,
|
||||
remaining_branches: Vec<Pos>,
|
||||
}
|
||||
|
||||
/// [`Algorithm`] driving the resolution of a [`crate::Maze`] traversing it as a common graph in a depth-first fashion.
|
||||
/// Stores the current path and possible branches in a stack.
|
||||
pub struct DepthFirst {
|
||||
visited: HashSet<Pos>,
|
||||
stack: Vec<Frame>,
|
||||
}
|
||||
|
||||
impl DepthFirst {
|
||||
/// constructor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
visited: HashSet::new(),
|
||||
stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Algorithm for DepthFirst {
|
||||
fn progress(&mut self, insight: &Insight, ctx: &mut Context) -> Guess {
|
||||
let position = insight.position();
|
||||
let branches = insight.paths().iter().cloned().collect();
|
||||
|
||||
self.visited.insert(position);
|
||||
self.stack.push(Frame {
|
||||
position,
|
||||
remaining_branches: branches,
|
||||
});
|
||||
|
||||
loop {
|
||||
let last = self.stack.last_mut().expect("no more options");
|
||||
if let Some(branch) = last.remaining_branches.pop() {
|
||||
if !self.visited.contains(&branch) {
|
||||
let mut path: Vec<_> = self.stack.iter().map(|f| f.position).collect();
|
||||
path.push(branch);
|
||||
return ctx.guess(path);
|
||||
}
|
||||
} else {
|
||||
self.stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
218
src/labyrinth.rs
Normal file
218
src/labyrinth.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
//! ## Labyrinth
|
||||
//!
|
||||
//! This module contains the data structure representing a maze for the rest of the library.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::Pos;
|
||||
|
||||
/// Data structure representing a maze on a grid.
|
||||
/// stores each possible paths as a [`HashMap`] mapping each positions to the accessible adjascent ones.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Maze {
|
||||
width: isize,
|
||||
height: isize,
|
||||
start: Pos,
|
||||
end: Pos,
|
||||
paths: HashMap<Pos, Vec<Pos>>,
|
||||
}
|
||||
|
||||
impl Maze {
|
||||
/// Constructor.
|
||||
pub fn new(
|
||||
width: isize,
|
||||
height: isize,
|
||||
start: Pos,
|
||||
end: Pos,
|
||||
paths_: Vec<(Pos, Vec<Pos>)>,
|
||||
) -> Self {
|
||||
let mut paths = HashMap::new();
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
paths.insert((x, y).into(), Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = Self {
|
||||
width,
|
||||
height,
|
||||
start,
|
||||
end,
|
||||
paths,
|
||||
};
|
||||
|
||||
for (position, accessibles) in paths_ {
|
||||
for accessible in accessibles {
|
||||
result.create_path(position, accessible);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn create_path(&mut self, position_a: Pos, position_b: Pos) {
|
||||
self.paths
|
||||
.get_mut(&position_a)
|
||||
.expect("position out of bounds")
|
||||
.push(position_b); // warning: mutation before all preconditions are checked
|
||||
self.paths
|
||||
.get_mut(&position_b)
|
||||
.expect("position out of bounds")
|
||||
.push(position_a);
|
||||
}
|
||||
|
||||
/// Width of the [`Maze`].
|
||||
pub fn width(&self) -> isize {
|
||||
self.width
|
||||
}
|
||||
|
||||
/// Height of the [`Maze`].
|
||||
pub fn height(&self) -> isize {
|
||||
self.height
|
||||
}
|
||||
|
||||
/// Tuple containing both the width and height of the [`Maze`].
|
||||
pub fn size(&self) -> (isize, isize) {
|
||||
(self.width(), self.height())
|
||||
}
|
||||
|
||||
/// The starting position of the [`Maze`].
|
||||
pub fn start(&self) -> Pos {
|
||||
self.start
|
||||
}
|
||||
|
||||
/// The ending position of the [`Maze`].
|
||||
pub fn end(&self) -> Pos {
|
||||
self.end
|
||||
}
|
||||
|
||||
/// Check if the provided position is the start of the [`Maze`].
|
||||
pub fn is_start(&self, position: Pos) -> bool {
|
||||
self.start() == position
|
||||
}
|
||||
|
||||
/// Check if the provided position is the end of the [`Maze`].
|
||||
pub fn is_end(&self, position: Pos) -> bool {
|
||||
self.end() == position
|
||||
}
|
||||
|
||||
/// Returns an array of all positions directly accessible from a position in the [`Maze`].
|
||||
pub fn paths_from(&self, position: Pos) -> &[Pos] {
|
||||
let accessibles = self.paths.get(&position).expect("position out of bounds");
|
||||
accessibles
|
||||
}
|
||||
|
||||
/// Check if a position is included within the [`Maze`].
|
||||
pub fn is_inside(&self, position: Pos) -> bool {
|
||||
let (x, y) = position.decompose();
|
||||
x >= 0 && x < self.width() && y >= 0 && y < self.height()
|
||||
}
|
||||
|
||||
/// Returns adjascent positions of `position` that are included in the [`Maze`].
|
||||
pub fn adjascent(&self, position: Pos) -> Vec<Pos> {
|
||||
let (x, y) = position.decompose();
|
||||
[(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)]
|
||||
.into_iter()
|
||||
.map(|t| t.into())
|
||||
.filter(|&p| self.is_inside(p))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Check if there is a wall between two adjascent positions in the [`Maze`].
|
||||
pub fn is_walled(&self, position_a: Pos, position_b: Pos) -> bool {
|
||||
self.paths_from(position_a)
|
||||
.iter()
|
||||
.find(|&&p| p == position_b)
|
||||
.is_none()
|
||||
}
|
||||
|
||||
/// return a string representing the [`Maze`].
|
||||
pub fn display(&self, overlay: Option<HashMap<Pos, char>>) -> String {
|
||||
// output
|
||||
let mut out: Vec<Vec<_>> = (0..(self.height() * 2 + 1))
|
||||
.map(|_| (0..(self.width() * 2 + 1)).map(|_| ' ').collect())
|
||||
.collect();
|
||||
|
||||
// outer walls
|
||||
for x in 0..self.width() {
|
||||
let mapped_x = (x * 2 + 1) as usize;
|
||||
out.first_mut().unwrap()[mapped_x] = '─';
|
||||
out.last_mut().unwrap()[mapped_x] = '─';
|
||||
}
|
||||
for y in 0..self.height() {
|
||||
let mapped_y = (y * 2 + 1) as usize;
|
||||
*out[mapped_y].first_mut().unwrap() = '│';
|
||||
*out[mapped_y].last_mut().unwrap() = '│';
|
||||
}
|
||||
|
||||
// vertical walls
|
||||
for y in 0..self.height() {
|
||||
for x in 1..self.width() {
|
||||
let current_cell = (x, y).into();
|
||||
let left_cell = current_cell - (1, 0).into();
|
||||
if self.is_walled(left_cell, current_cell) {
|
||||
let mapped_y = (2 * y + 1) as usize;
|
||||
let mapped_x = (2 * x) as usize;
|
||||
out[mapped_y][mapped_x] = '│'
|
||||
}
|
||||
}
|
||||
}
|
||||
// horizontal walls
|
||||
for y in 1..self.height() {
|
||||
for x in 0..self.width() {
|
||||
let current_cell = (x, y).into();
|
||||
let upper_cell = current_cell - (0, 1).into();
|
||||
if self.is_walled(upper_cell, current_cell) {
|
||||
let mapped_x = (2 * x + 1) as usize;
|
||||
let mapped_y = (2 * y) as usize;
|
||||
out[mapped_y][mapped_x] = '─';
|
||||
}
|
||||
}
|
||||
}
|
||||
// corners
|
||||
for y in 0..(self.height() + 1) {
|
||||
for x in 0..(self.width() + 1) {
|
||||
let mapped_x = (2 * x) as usize;
|
||||
let mapped_y = (2 * y) as usize;
|
||||
out[mapped_y][mapped_x] = '•';
|
||||
}
|
||||
}
|
||||
|
||||
// overlay
|
||||
if let Some(overlay) = overlay {
|
||||
for (position, character) in overlay {
|
||||
let (x, y) = position.decompose();
|
||||
let mapped_x = (x * 2 + 1) as usize;
|
||||
let mapped_y = (y * 2 + 1) as usize;
|
||||
out[mapped_y][mapped_x] = character;
|
||||
}
|
||||
}
|
||||
|
||||
out.into_iter()
|
||||
.map(|line| line.into_iter().collect::<String>())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
let maze = Maze::new(
|
||||
3,
|
||||
3,
|
||||
(0, 0).into(),
|
||||
(2, 2).into(),
|
||||
vec![
|
||||
((0, 0).into(), vec![(1, 0).into(), (0, 1).into()]),
|
||||
((1, 0).into(), vec![(1, 1).into()]),
|
||||
((1, 1).into(), vec![(1, 2).into()]),
|
||||
((1, 2).into(), vec![(2, 2).into()]),
|
||||
],
|
||||
);
|
||||
|
||||
let text = maze.display(None);
|
||||
println!("{text}");
|
||||
}
|
||||
|
||||
pub mod generator;
|
46
src/labyrinth/generator.rs
Normal file
46
src/labyrinth/generator.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
//! ## Generator
|
||||
//!
|
||||
//! This module contains raw functions generating mazes.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rand::{prelude::SliceRandom, thread_rng};
|
||||
|
||||
use crate::{Maze, Pos};
|
||||
|
||||
/// Most common maze generation technique, recursively creating paths to unvisited cells, each time choosing next direction at random.
|
||||
pub fn generate(width: isize, height: isize) -> Maze {
|
||||
let mut result = Maze::new(
|
||||
width,
|
||||
height,
|
||||
Pos::zero(),
|
||||
(width - 1, height - 1).into(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
fn recursive(current: Pos, result: &mut Maze, visited: &mut HashSet<Pos>) {
|
||||
visited.insert(current);
|
||||
let mut adjascent_positions = result.adjascent(current);
|
||||
adjascent_positions.shuffle(&mut thread_rng());
|
||||
for neighbor in adjascent_positions {
|
||||
if visited.contains(&neighbor) {
|
||||
continue;
|
||||
}
|
||||
result.create_path(current, neighbor);
|
||||
recursive(neighbor, result, visited);
|
||||
}
|
||||
}
|
||||
|
||||
let mut visited = HashSet::new();
|
||||
let current = Pos::zero();
|
||||
recursive(current, &mut result, &mut visited);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generation() {
|
||||
let maze = generate(20, 20);
|
||||
let text = maze.display(None);
|
||||
println!("{text}");
|
||||
}
|
16
src/lib.rs
Normal file
16
src/lib.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
//! # Labirust
|
||||
//!
|
||||
//! This crate is a small and naive implementation of several [`Algorithm`]s resolving [`Maze`]s.
|
||||
//!
|
||||
//! * It exposes the [`Algorithm`] trait encapsulating the behavior of such an algorithm.
|
||||
//! * It also provides structures to generate [`Maze`] ([`generate`]) and execute said algorithms on them ([`Executor`]).
|
||||
//!
|
||||
|
||||
mod algorithm;
|
||||
pub mod implementations;
|
||||
mod labyrinth;
|
||||
mod position;
|
||||
|
||||
pub use algorithm::{Algorithm, Executor};
|
||||
pub use labyrinth::{generator::generate, Maze};
|
||||
pub use position::Pos;
|
86
src/position.rs
Normal file
86
src/position.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
//! ## Position
|
||||
//!
|
||||
//! This module contains the definition of the [`Pos`] type, used to represent positions in the rest of the library;
|
||||
|
||||
use std::ops::{Add, Mul, Sub};
|
||||
|
||||
/// A discrete position on a 2D-grid.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Pos(isize, isize);
|
||||
|
||||
impl Pos {
|
||||
/// Constructor.
|
||||
pub fn new(x: isize, y: isize) -> Self {
|
||||
Self(x, y)
|
||||
}
|
||||
|
||||
/// Origin of the 2D-grid.
|
||||
pub fn zero() -> Self {
|
||||
Self::new(0, 0)
|
||||
}
|
||||
|
||||
/// Unit length on both axis of the 2D-grid.
|
||||
pub fn one() -> Self {
|
||||
Self::new(1, 1)
|
||||
}
|
||||
|
||||
/// Constructor for positions of diagonal vectors.
|
||||
pub fn sized(scale: isize) -> Self {
|
||||
Self::one().scale(scale)
|
||||
}
|
||||
|
||||
/// Scale the vector by an integer value.
|
||||
pub fn scale(self, factor: isize) -> Self {
|
||||
self * Self::sized(factor)
|
||||
}
|
||||
|
||||
/// Accessor of the x-coordinate.
|
||||
pub fn x(self) -> isize {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Accessor of the y-coordinate.
|
||||
pub fn y(self) -> isize {
|
||||
self.1
|
||||
}
|
||||
|
||||
/// Decompose the position in both its x-part and y-part as a tuple.
|
||||
pub fn decompose(self) -> (isize, isize) {
|
||||
(self.x(), self.y())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(isize, isize)> for Pos {
|
||||
fn from((x, y): (isize, isize)) -> Self {
|
||||
Self::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let x = self.x() + rhs.x();
|
||||
let y = self.y() + rhs.y();
|
||||
Self::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
let x = self.x() - rhs.x();
|
||||
let y = self.y() - rhs.y();
|
||||
Self::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Pos {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
let x = self.x() * rhs.x();
|
||||
let y = self.y() * rhs.y();
|
||||
Self::new(x, y)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue