diff --git a/Cargo.toml b/Cargo.toml --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,4 @@ macros = { path = "macros" } [[bin]] name = "advent" -path = "src/main.rs" \ No newline at end of file +path = "src/main.rs" diff --git a/core/src/bootstrap.rs b/core/src/bootstrap.rs --- a/core/src/bootstrap.rs +++ b/core/src/bootstrap.rs @@ -4,7 +4,6 @@ use regex::Regex; use crate::MAX_DAY; - const DAY_TEMPLATE: &str = " use core::{Day, day_stuff, ex_for_day}; @@ -119,7 +118,7 @@ std::fs::write(cargo_path, contents).unwrap(); } -fn replace_year_list(new_year: &str ) { +fn replace_year_list(new_year: &str) { let main = include_str!("../../src/main.rs"); let global_runner_pattern = Regex::new(r"global_runner!\(([\d,]+)\)").unwrap(); @@ -128,13 +127,23 @@ let matches = global_runner_pattern.captures(main).unwrap(); let full = matches.get(0).unwrap().as_str(); - let mut years = matches.get(1).unwrap().as_str().split(",").map(|s| s.parse::().unwrap()).collect::>(); + let mut years = matches + .get(1) + .unwrap() + .as_str() + .split(",") + .map(|s| s.parse::().unwrap()) + .collect::>(); years.push(new_year.parse::().unwrap()); years.sort(); - let new_years = years.iter().map(|y| y.to_string()).collect::>().join(","); + let new_years = years + .iter() + .map(|y| y.to_string()) + .collect::>() + .join(","); let new_main = main.replace(full, &format!("global_runner!({})", new_years)); @@ -163,4 +172,4 @@ make_cargo(&year_path, year); replace_cargo_dependencies(year); replace_year_list(year); -} \ No newline at end of file +} diff --git a/core/src/day.rs b/core/src/day.rs --- a/core/src/day.rs +++ b/core/src/day.rs @@ -3,7 +3,13 @@ #[macro_export] macro_rules! ex_for_day { ($day:literal, $part:literal) => { - include_str!(concat!("examples/day_", stringify!($day), "/", stringify!($part), ".txt")) + include_str!(concat!( + "examples/day_", + stringify!($day), + "/", + stringify!($part), + ".txt" + )) }; } @@ -11,7 +17,7 @@ #[macro_export] macro_rules! day_stuff { ($day:literal, $e_1:literal, $e_2:literal) => { day_stuff!($day, $e_1, $e_2, String); - + fn parse_input(input: &str) -> Self::Input { input.to_string() } @@ -19,12 +25,12 @@ }; ($day:literal, $e_1:literal, $e_2:literal, $i: ty) => { type Input = $i; - + const DAY: usize = $day; const EXAMPLE_INPUT_1: &'static str = ex_for_day!($day, 1); const EXAMPLE_INPUT_2: &'static str = ex_for_day!($day, 2); const EXPECTED_1: &'static str = $e_1; - const EXPECTED_2: &'static str = $e_2; + const EXPECTED_2: &'static str = $e_2; } } @@ -40,7 +46,6 @@ /// /// Then, any runner can use `run_part` to run a part of the day with a given input or the example input. /// pub trait Day { - type Input; const DAY: usize = 0; @@ -68,12 +73,22 @@ 1 => Self::part_1(input), 2 => Self::part_2(input), _ => panic!("Invalid part number"), }; - println!("Day {} Part {}: {} ({}ms)", Self::DAY, part, solution.as_ref().unwrap_or(&"Not implemented".to_string()), instant.elapsed().as_millis()); + println!( + "Day {} Part {}: {} ({}ms)", + Self::DAY, + part, + solution.as_ref().unwrap_or(&"Not implemented".to_string()), + instant.elapsed().as_millis() + ); solution } fn run_all_parts(extra_indent: &str) { - println!("{extra_indent}Day {day}:", extra_indent = extra_indent, day = Self::DAY); + println!( + "{extra_indent}Day {day}:", + extra_indent = extra_indent, + day = Self::DAY + ); for part in 1..=2 { let part_time = Instant::now(); let solution = match part { @@ -81,7 +96,12 @@ 1 => Self::part_1(Self::parse_input(Self::EXAMPLE_INPUT_1)), 2 => Self::part_2(Self::parse_input(Self::EXAMPLE_INPUT_2)), _ => panic!("Invalid part number"), }; - println!("{extra_indent} Part {}: {} ({}ms)", part, solution.as_ref().unwrap_or(&"Not implemented".to_string()), part_time.elapsed().as_millis()); + println!( + "{extra_indent} Part {}: {} ({}ms)", + part, + solution.as_ref().unwrap_or(&"Not implemented".to_string()), + part_time.elapsed().as_millis() + ); } } @@ -119,7 +139,6 @@ struct TestDay; impl Day for TestDay { - type Input = String; const EXAMPLE_INPUT_1: &'static str = "Hello, world!"; @@ -144,7 +163,6 @@ struct TestDay2; impl Day for TestDay2 { - type Input = Vec; const EXAMPLE_INPUT_1: &'static str = "A\nB\nC"; diff --git a/core/src/lib.rs b/core/src/lib.rs --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,11 +1,11 @@ +mod bootstrap; mod day; -mod year; mod parser; -mod bootstrap; +mod year; pub const MAX_DAY: usize = 25; +pub use bootstrap::make_year; pub use day::Day; +pub use parser::{get_dp_and_input, get_ydp_and_input, Selection, DP, YDP}; pub use year::Year; -pub use parser::{Selection, YDP, DP, get_dp_and_input, get_ydp_and_input}; -pub use bootstrap::make_year; diff --git a/core/src/parser.rs b/core/src/parser.rs --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -1,5 +1,5 @@ -use std::io::{stdin, Read}; use std::env::args; +use std::io::{stdin, Read}; #[derive(Clone, Debug)] pub enum Selection { All, @@ -7,7 +7,6 @@ Single(usize), // TODO: Add range maybe? } impl Selection { - fn parse(input: &str) -> Self { if input == "*" { Self::All @@ -16,7 +15,6 @@ let input = input.parse::().unwrap(); Self::Single(input) } } - } #[derive(Clone, Debug)] @@ -31,19 +29,14 @@ part: Selection::All, }; impl DP { - fn parse(input: &str) -> Self { let mut split = input.split(':'); let day = split.next().map(Selection::parse).unwrap_or(Selection::All); let part = split.next().map(Selection::parse).unwrap_or(Selection::All); - Self { - day, - part, - } + Self { day, part } } - } #[derive(Clone, Debug)] @@ -54,7 +47,6 @@ pub part: Selection, } impl YDP { - fn parse(input: &str) -> Self { let mut split = input.split(':'); @@ -62,11 +54,7 @@ let year = split.next().map(Selection::parse).unwrap_or(Selection::All); let day = split.next().map(Selection::parse).unwrap_or(Selection::All); let part = split.next().map(Selection::parse).unwrap_or(Selection::All); - Self { - year, - day, - part, - } + Self { year, day, part } } pub fn to_dp(&self) -> DP { @@ -75,7 +63,6 @@ day: self.day.clone(), part: self.part.clone(), } } - } pub fn get_dp_and_input() -> (DP, Option) { @@ -86,7 +73,9 @@ let input = args.next().map(|s| s.trim().to_string()).map(|i| { if i == "-" { let mut input = String::new(); - stdin().read_to_string(&mut input).expect("Failed to read input"); + stdin() + .read_to_string(&mut input) + .expect("Failed to read input"); input.trim().to_string() } else { i @@ -97,7 +86,6 @@ (dp, input) } pub fn get_ydp_and_input(args: Vec) -> (YDP, Option) { - let mut args = args.into_iter(); let ydp = args.next().map(|s| YDP::parse(&s.trim())).unwrap_or(YDP { @@ -109,7 +97,9 @@ let input = args.next().map(|s| s.trim().to_string()).map(|i| { if i == "-" { let mut input = String::new(); - stdin().read_to_string(&mut input).expect("Failed to read input"); + stdin() + .read_to_string(&mut input) + .expect("Failed to read input"); input.trim().to_string() } else { i @@ -117,4 +107,4 @@ } }); (ydp, input) -} \ No newline at end of file +} diff --git a/core/src/year.rs b/core/src/year.rs --- a/core/src/year.rs +++ b/core/src/year.rs @@ -1,4 +1,4 @@ -use crate::parser::{DP, Selection}; +use crate::parser::{Selection, DP}; use super::MAX_DAY; @@ -20,15 +20,13 @@ fn run_dp(input: Option<&str>, dp: DP) { match dp.day { Selection::All => { Self::solve_all_days(); - }, - Selection::Single(day) => { - match dp.part { - Selection::All => { - Self::solve_day_both_parts(day, ""); - }, - Selection::Single(part) => { - Self::solve_day(day, part, input); - }, + } + Selection::Single(day) => match dp.part { + Selection::All => { + Self::solve_day_both_parts(day, ""); + } + Selection::Single(part) => { + Self::solve_day(day, part, input); } }, } diff --git a/macros/src/lib.rs b/macros/src/lib.rs --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -5,19 +5,31 @@ use proc_macro::TokenStream; fn make_day_mods() -> String { - (1..=MAX_DAY).map(|day| format!("mod day_{day};", day = day)).collect::>().join("\n") + (1..=MAX_DAY) + .map(|day| format!("mod day_{day};", day = day)) + .collect::>() + .join("\n") } fn make_use_days() -> String { - (1..=MAX_DAY).map(|day| format!("use day_{day}::Day{day};", day = day)).collect::>().join("\n") + (1..=MAX_DAY) + .map(|day| format!("use day_{day}::Day{day};", day = day)) + .collect::>() + .join("\n") } fn make_day_match(inner: &str) -> String { - (1..=MAX_DAY).map(|day| format!("{day} => {},", inner.replace("{day}", &day.to_string()))).collect::>().join("\n") + (1..=MAX_DAY) + .map(|day| format!("{day} => {},", inner.replace("{day}", &day.to_string()))) + .collect::>() + .join("\n") } fn make_day_tests() -> String { - (1..=MAX_DAY).map(|day| format!(" + (1..=MAX_DAY) + .map(|day| { + format!( + " #[test] fn test_day_{day}_part_1() {{ Day{day}::assert_part_1(); @@ -26,33 +38,44 @@ #[test] fn test_day_{day}_part_2() {{ Day{day}::assert_part_2(); - }}")).collect::>().join("\n") + }}" + ) + }) + .collect::>() + .join("\n") } fn get_solve_day() -> String { let inner = make_day_match("Day{day}::run_part(part, input)"); - format!(" + format!( + " fn solve_day(day: usize, part: usize, input: Option<&str>) -> Option {{ match day {{ {inner} _ => None, }} - }}", inner = inner) + }}", + inner = inner + ) } fn get_solve_day_both_parts() -> String { let inner = make_day_match("Day{day}::run_all_parts(extra_indent)"); - format!(" + format!( + " fn solve_day_both_parts(day: usize, extra_indent: &str) {{ match day {{ {inner} _ => (), }} - }}", inner = inner) + }}", + inner = inner + ) } fn make_year_struct(year: &str) -> String { - format!(" + format!( + " pub struct Year{year}; impl Year for Year{year} {{ @@ -61,18 +84,24 @@ {solve_day} {solve_day_both_parts} - }}", solve_day = get_solve_day(), solve_day_both_parts = get_solve_day_both_parts()) + }}", + solve_day = get_solve_day(), + solve_day_both_parts = get_solve_day_both_parts() + ) } fn make_tests() -> String { - format!(" + format!( + " #[cfg(test)] mod tests {{ use super::*; use core::{{Day, Year}}; {day_tests} - }}", day_tests = make_day_tests()) + }}", + day_tests = make_day_tests() + ) } #[proc_macro] @@ -86,7 +115,8 @@ let year_struct = make_year_struct(&year); let tests = make_tests(); - format!(" + format!( + " {mods} use core::{{Year, Day}}; @@ -95,14 +125,18 @@ {year_struct} {tests} - ").parse::().unwrap() + " + ) + .parse::() + .unwrap() } #[proc_macro] pub fn year_runner(item: TokenStream) -> TokenStream { let year = item.to_string(); - format!(" + format!( + " use core::{{Year, get_dp_and_input}}; use y_{year}::Year{year}; @@ -110,24 +144,45 @@ fn main() {{ let (dp, input) = get_dp_and_input(); Year{year}::run_dp(input.as_deref(), dp); - }}").parse::().unwrap() + }}" + ) + .parse::() + .unwrap() } fn make_year_match(years: &Vec<&str>, inner: &str) -> String { - years.iter().map(|year| format!("{year} => {},", inner.replace("{year}", &year.to_string()))).collect::>().join("\n") + years + .iter() + .map(|year| format!("{year} => {},", inner.replace("{year}", &year.to_string()))) + .collect::>() + .join("\n") } fn make_year_uses(years: &Vec<&str>) -> String { - years.iter().map(|year| format!("use y_{year}::Year{year};", year = year)).collect::>().join("\n") + years + .iter() + .map(|year| format!("use y_{year}::Year{year};", year = year)) + .collect::>() + .join("\n") } fn make_run_all_years(years: &Vec<&str>) -> String { - years.iter().map(|year| format!("Year{year}::run_dp(input.as_deref(), dp.clone());", year = year)).collect::>().join("\n") + years + .iter() + .map(|year| { + format!( + "Year{year}::run_dp(input.as_deref(), dp.clone());", + year = year + ) + }) + .collect::>() + .join("\n") } fn make_run_year(years: &Vec<&str>) -> String { let inner = make_year_match(years, "Year{year}::run_dp(input.as_deref(), dp)"); - format!(" + format!( + " fn run_year(year: usize, dp: DP, input: Option<&str>) {{ match year {{ {inner} @@ -135,7 +190,9 @@ _ => {{ println!(\"Unknown year: {{year}}\"); }} }} - }}", inner = inner) + }}", + inner = inner + ) } #[proc_macro] @@ -147,12 +204,16 @@ let year_uses = make_year_uses(&years); let run_all_years = make_run_all_years(&years); let run_year = make_run_year(&years); - format!(" + format!( + " {year_uses} {run_year} fn run_all_years(dp: &DP, input: Option) {{ {run_all_years} - }}").parse::().unwrap() -} \ No newline at end of file + }}" + ) + .parse::() + .unwrap() +} diff --git a/src/main.rs b/src/main.rs --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use core::{get_ydp_and_input, make_year, Year, Selection, YDP, DP}; +use core::{get_ydp_and_input, make_year, Selection, Year, DP, YDP}; use macros::global_runner; global_runner!(2023); @@ -9,10 +9,10 @@ match ydp.year { Selection::All => { run_all_years(&dp, input); - }, + } Selection::Single(year) => { run_year(year, dp, input.as_deref()); - }, + } } } @@ -22,20 +22,18 @@ let command = args.get(0); match command { - Some(command) => { - match command.as_str() { - "new" => { - let year = args.get(1).expect("No year provided"); - make_year(year); - }, - "solve" | "run" => { - let (ydp, input) = get_ydp_and_input(args[1..].to_vec()); - run_ydp(ydp, input); - } - _ => { - println!("Unknown command: {}", command); - println!("Available commands: new, solve"); - } + Some(command) => match command.as_str() { + "new" => { + let year = args.get(1).expect("No year provided"); + make_year(year); + } + "solve" | "run" => { + let (ydp, input) = get_ydp_and_input(args[1..].to_vec()); + run_ydp(ydp, input); + } + _ => { + println!("Unknown command: {}", command); + println!("Available commands: new, solve"); } }, None => { diff --git a/utils/Cargo.toml b/utils/Cargo.toml --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -2,7 +2,3 @@ [package] name = "utils" version = "0.1.0" edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/utils/src/day_utils.rs b/utils/src/day_utils.rs new file mode 100644 --- /dev/null +++ b/utils/src/day_utils.rs @@ -0,0 +1,30 @@ +#[macro_export] +macro_rules! yippee { + () => { + fn part_2(_: Self::Input) -> Option { + Some("🥳".to_string()) + } + }; +} + +#[macro_export] +macro_rules! grid_day { + ($day:literal, $e_1:literal, $e_2:literal, $t:ty) => { + day_stuff!($day, $e_1, $e_2, utils::grid::Grid<$t>); + + fn parse_input(input: &str) -> Self::Input { + Self::Input::parse(input) + } + }; +} + +#[macro_export] +macro_rules! lines_day { + ($day:literal, $e_1:literal, $e_2:literal, $t:ty) => { + day_stuff!($day, $e_1, $e_2, Vec<$t>); + + fn parse_input(input: &str) -> Self::Input { + input.lines().map(|l| l.parse().unwrap()).collect() + } + }; +} diff --git a/utils/src/dir.rs b/utils/src/dir.rs new file mode 100644 --- /dev/null +++ b/utils/src/dir.rs @@ -0,0 +1,146 @@ +/// Module containing utilities related to direction and movement. +use crate::pos::Position; + +/// Trait used to define an object that can be used to move around a grid. +/// +/// This is meant for complex scenarios where you want to move around a grid in a non-standard way. +/// By implementing this trait you can use various methods from the [Position] struct to move around. +/// +/// # Implementing +/// +/// Implementing this trait requires you to define a `get_kernel` method that returns a `Position`. +/// This position is used to move around the grid by applying it to the current position. +/// +/// # Examples +/// +/// ``` +/// use utils::prelude::*; +/// +/// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +/// struct RightBy(usize); +/// +/// impl Movement for RightBy { +/// fn get_kernel(&self) -> Position { +/// Position::new(self.0 as isize, 0) +/// } +/// } +/// +/// let pos = Position::new(0, 0); +/// assert_eq!(pos.move_dir(RightBy(1)), Position::new(1, 0)); +/// ``` +/// +/// # See also +/// +/// - [Direction] is a simple implementation of this trait. +/// - [Position] is the main user of this trait. +/// +pub trait Movement: std::fmt::Debug + Copy + Clone + PartialEq + std::hash::Hash { + fn get_kernel(&self) -> Position; +} + +/// The four cardinal directions. +/// Useful for iterating over all four directions. +pub const CARDINALS: [Direction; 4] = [ + Direction::North, + Direction::South, + Direction::East, + Direction::West, +]; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +/// The four cardinal directions. +/// This is a simple implementation of the [Movement] trait. +/// +/// # Examples +/// +/// ``` +/// use utils::prelude::*; +/// +/// let pos = Position::new(0, 0); +/// assert_eq!(pos.move_dir(Direction::North), Position::new(0, -1)); +/// assert_eq!(pos.move_dir(Direction::South), Position::new(0, 1)); +/// assert_eq!(pos.move_dir(Direction::East), Position::new(1, 0)); +/// assert_eq!(pos.move_dir(Direction::West), Position::new(-1, 0)); +/// ``` +/// +pub enum Direction { + North, + South, + East, + West, +} + +impl Direction { + /// Returns the direction that is opposite to the current one. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// assert_eq!(Direction::North.opposite(), Direction::South); + /// assert_eq!(Direction::South.opposite(), Direction::North); + /// assert_eq!(Direction::East.opposite(), Direction::West); + /// assert_eq!(Direction::West.opposite(), Direction::East); + /// ``` + /// + pub fn opposite(&self) -> Self { + match self { + Self::North => Self::South, + Self::South => Self::North, + Self::East => Self::West, + Self::West => Self::East, + } + } + + /// Returns the direction that is 90 degrees to the current one. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// assert_eq!(Direction::North.ninety_deg(true), Direction::East); + /// assert_eq!(Direction::North.ninety_deg(false), Direction::West); + /// + /// assert_eq!(Direction::South.ninety_deg(true), Direction::West); + /// assert_eq!(Direction::South.ninety_deg(false), Direction::East); + /// ``` + /// + pub fn ninety_deg(&self, clockwise: bool) -> Self { + match (self, clockwise) { + (Self::North, true) => Self::East, + (Self::North, false) => Self::West, + (Self::South, true) => Self::West, + (Self::South, false) => Self::East, + (Self::East, true) => Self::South, + (Self::East, false) => Self::North, + (Self::West, true) => Self::North, + (Self::West, false) => Self::South, + } + } +} + +impl From for Direction { + fn from(pos: Position) -> Self { + let pos = pos.normalize(); + match (pos.x, pos.y) { + (0, -1) => Self::North, + (0, 1) => Self::South, + (1, 0) => Self::East, + (-1, 0) => Self::West, + _ => panic!("Invalid position"), + } + } +} + +impl Movement for Direction { + fn get_kernel(&self) -> Position { + match self { + Direction::North => Position::new(0, -1), + Direction::South => Position::new(0, 1), + Direction::East => Position::new(1, 0), + Direction::West => Position::new(-1, 0), + } + } +} diff --git a/utils/src/geom.rs b/utils/src/geom.rs new file mode 100644 --- /dev/null +++ b/utils/src/geom.rs @@ -0,0 +1,24 @@ +use crate::pos::Position; + +/// Get the area of a polygon given its vertices. +/// +/// This is the shoelace formula. +/// +pub fn area(verts: &[Position]) -> isize { + verts + .windows(2) + .map(|w| ((w[0].x) * (w[1].y)) - ((w[0].y) * (w[1].x))) + .sum::() + / 2 +} + +/// Get the perimeter of a polygon given its vertices. +/// +/// This is the sum of the distances between each vertex. +/// +pub fn perimeter(verts: &[Position]) -> isize { + verts + .windows(2) + .map(|w| (w[0].x - w[1].x).abs() + (w[0].y - w[1].y).abs()) + .sum::() +} diff --git a/utils/src/grid.rs b/utils/src/grid.rs new file mode 100644 --- /dev/null +++ b/utils/src/grid.rs @@ -0,0 +1,938 @@ +use crate::{ + dir::{Direction, Movement, CARDINALS}, + pos::Position, +}; + +/// A 2D integer grid of values. +/// +/// This grid is represented by a vector of vectors. +/// +/// # Examples +/// +/// ``` +/// use utils::prelude::*; +/// +/// let data = vec![ +/// vec![1, 2, 3], +/// vec![4, 5, 6], +/// vec![7, 8, 9], +/// ]; +/// +/// let grid = Grid::new(data); +/// +/// assert_eq!(grid.get(Position::new(0, 0)), Some(&1)); +/// assert_eq!(grid.get(Position::new(1, 1)), Some(&5)); +/// assert_eq!(grid.get(Position::new(2, 2)), Some(&9)); +/// ``` +/// +pub struct Grid { + data: Vec>, +} + +impl Grid { + /// Create a new grid from a vector of vectors. + pub fn new(data: Vec>) -> Self { + Self { data } + } + + /// Parse a grid from a string, this will convert each character into `T` via `From`. + /// + /// Use the `tiles!` macro to easily create an enum that implements `From`. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + /// enum Tile { + /// Floor, + /// Wall, + /// } + /// + /// impl From for Tile { + /// fn from(c: char) -> Self { + /// match c { + /// '.' => Self::Floor, + /// '#' => Self::Wall, + /// _ => panic!("Invalid tile {c}"), + /// } + /// } + /// } + /// + /// let input = ".#.\n#.#\n.#."; + /// let grid = Grid::::parse(input); + /// + /// assert_eq!(grid.get(Position::new(0, 0)), Some(&Tile::Floor)); + /// assert_eq!(grid.get(Position::new(1, 0)), Some(&Tile::Wall)); + /// assert_eq!(grid.get(Position::new(2, 0)), Some(&Tile::Floor)); + /// assert_eq!(grid.get(Position::new(1, 1)), Some(&Tile::Floor)); + /// ``` + /// + /// Using `tiles!`... + /// + /// ``` + /// use utils::prelude::*; + /// use utils::tiles; + /// + /// tiles!(Tile, [ + /// '.' => Floor, + /// '#' => Wall, + /// ]); + /// + /// let input = ".#.\n#.#\n.#."; + /// let grid = Grid::::parse(input); + /// + /// assert_eq!(grid.get(Position::new(0, 0)), Some(&Tile::Floor)); + /// assert_eq!(grid.get(Position::new(1, 0)), Some(&Tile::Wall)); + /// assert_eq!(grid.get(Position::new(2, 0)), Some(&Tile::Floor)); + /// assert_eq!(grid.get(Position::new(1, 1)), Some(&Tile::Floor)); + /// ``` + /// + pub fn parse(input: &str) -> Self + where + T: From, + { + let data = input + .lines() + .map(|line| line.chars().map(|c| c.into()).collect()) + .collect(); + Self::new(data) + } + + /// Return the width of the grid. + pub fn width(&self) -> usize { + self.data[0].len() + } + + /// Return the height of the grid. + pub fn height(&self) -> usize { + self.data.len() + } + + /// Get the size of the grid. + pub fn size(&self) -> (usize, usize) { + (self.width(), self.height()) + } + + /// Get the bounds of the grid. + /// + /// (This is the same as `self.size()` with -1 added to each component) + pub fn bounds(&self) -> (usize, usize) { + (self.width() - 1, self.height() - 1) + } + + /// Get a value from the grid at the given position. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// assert_eq!(grid.get(Position::new(0, 0)), Some(&1)); + /// assert_eq!(grid.get(Position::new(1, 1)), Some(&5)); + /// assert_eq!(grid.get(Position::new(2, 2)), Some(&9)); + /// assert_eq!(grid.get(Position::new(3, 3)), None); + /// ``` + /// + pub fn get(&self, pos: Position) -> Option<&T> { + self.data + .get(pos.y as usize) + .and_then(|row| row.get(pos.x as usize)) + } + + /// Get a value from the grid at the given position, + /// panicking if the position is out of bounds. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// assert_eq!(grid.unsafe_get(Position::new(0, 0)), &1); + /// assert_eq!(grid.unsafe_get(Position::new(1, 1)), &5); + /// ``` + /// + pub fn unsafe_get(&self, pos: Position) -> &T { + &self.data[pos.y as usize][pos.x as usize] + } + + /// Get the value at the given position, wrapping around the grid if necessary. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// assert_eq!(grid.get_wrapped(Position::new(0, 0)), &1); + /// assert_eq!(grid.get_wrapped(Position::new(1, 1)), &5); + /// assert_eq!(grid.get_wrapped(Position::new(2, 2)), &9); + /// assert_eq!(grid.get_wrapped(Position::new(3, 3)), &1); + /// assert_eq!(grid.get_wrapped(Position::new(-1, -1)), &9); + /// ``` + /// + pub fn get_wrapped(&self, pos: Position) -> &T { + let wrapped_pos = pos.bind(self.size()); + &self.data[wrapped_pos.1][wrapped_pos.0] + } + + /// Iterate over a row of the grid. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// assert_eq!(grid.iter_row(0).unwrap().collect::>(), vec![&1, &2, &3]); + /// assert_eq!(grid.iter_row(1).unwrap().sum::(), 4+5+6); + /// assert!(grid.iter_row(8).is_none()); + /// ``` + /// + pub fn iter_row(&self, row: usize) -> Option> { + self.data.get(row).map(|row| row.iter()) + } + + /// Iterate over a column of the grid. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// assert_eq!(grid.iter_col(0).unwrap().collect::>(), vec![&1, &4, &7]); + /// assert_eq!(grid.iter_col(1).unwrap().sum::(), 2+5+8); + /// assert!(grid.iter_col(8).is_none()); + /// ``` + /// + pub fn iter_col(&self, col: usize) -> Option> { + if col > self.width() { + return None; + } + Some(self.data.iter().filter_map(move |row| row.get(col))) + } + + /// Get a row of the grid. + /// + /// This is the same as `self.iter_row(row).map(|iter| iter.collect())`. + pub fn get_row(&self, y: usize) -> Option> { + self.iter_row(y).map(|iter| iter.collect()) + } + + /// Get a column of the grid. + /// + /// This is the same as `self.iter_col(col).map(|iter| iter.collect())`. + pub fn get_col(&self, x: usize) -> Option> { + self.iter_col(x).map(|iter| iter.collect()) + } + + /// Iterate over all rows of the grid. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// assert_eq!(grid.iter_rows().enumerate().filter_map(|(y, row)| row.collect::>().get(y).copied()).sum::(), 1+5+9); + /// ``` + /// + pub fn iter_rows(&self) -> impl Iterator> { + self.data.iter().map(|row| row.iter()) + } + + /// Iterate over all columns of the grid. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// assert_eq!(grid.iter_cols().enumerate().filter_map(|(x, col)| col.collect::>().get(x).copied()).sum::(), 1+5+9); + /// ``` + /// + pub fn iter_cols(&self) -> impl Iterator> { + (0..self.width()).map(move |col| self.iter_col(col).unwrap()) + } + + /// Iterate over all elements of the grid. + /// + /// This also yields the position of each element for easy access. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// assert_eq!(grid.iter().map(|(_, v)| v).sum::(), 1+2+3+4+5+6+7+8+9); + /// ``` + /// + pub fn iter(&self) -> impl Iterator { + self.data.iter().enumerate().flat_map(|(y, row)| { + row.iter() + .enumerate() + .map(move |(x, col)| (Position::new(x as isize, y as isize), col)) + }) + } + + /// Get all positions relative to the given position in the grid based off the given kernels. + /// + /// This will automatically filter out any positions that are out of bounds. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// let pos = Position::new(1, 1); + /// let kernels = &[ + /// Direction::North, + /// Direction::East, + /// ]; + /// + /// let mut relatives = grid.relatives(pos, kernels); + /// + /// assert_eq!(relatives.next(), Some((Direction::North, Position::new(1, 0), &2))); + /// assert_eq!(relatives.next(), Some((Direction::East, Position::new(2, 1), &6))); + /// ``` + /// + /// ``` + /// use utils::prelude::*; + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// let pos = Position::new(1, 0); + /// let kernels = &[ + /// Direction::North, // This will be filtered out, as (1, -1) is out of bounds + /// Direction::East, + /// ]; + /// + /// let mut relatives = grid.relatives(pos, kernels); + /// + /// assert_eq!(relatives.next(), Some((Direction::East, Position::new(2, 0), &3))); + /// ``` + /// + pub fn relatives<'a, M: Movement>( + &'a self, + pos: Position, + kernels: &'a [M], + ) -> impl Iterator + 'a { + pos.relatives(kernels) + .filter_map(move |(pos, dir)| self.get(pos).map(|v| (dir, pos, v))) + } + + /// Get all positions relative to the given position in the grid based off the given kernels. + /// + /// Wraps around the grid if necessary. + /// + pub fn relatives_wrapped<'a, M: Movement>( + &'a self, + pos: Position, + kernels: &'a [M], + ) -> impl Iterator + 'a { + pos.relatives(kernels) + .map(move |(pos, dir)| (dir, pos, self.get_wrapped(pos))) + } + + /// Get all positions relative to the given position in the grid based off the given kernels, + /// applying the kernel multiple times. + /// + /// This will automatically filter out any positions that are out of bounds. + /// + pub fn relatives_expand_by<'a, M: Movement>( + &'a self, + pos: Position, + kernels: &'a [M], + expand: usize, + ) -> impl Iterator + 'a { + pos.relatives_expand_by(kernels, expand) + .filter_map(move |(dir, pos)| self.get(pos).map(|v| (dir, pos, v))) + } + + /// Get all positions relative to the given position in the grid based off the given kernels, + /// applying the kernel multiple times. + /// + /// Wraps around the grid if necessary. + /// + pub fn relatives_expand_by_wrapped<'a, M: Movement>( + &'a self, + pos: Position, + kernels: &'a [M], + expand: usize, + ) -> impl Iterator + 'a { + pos.relatives_expand_by(kernels, expand) + .map(move |(dir, pos)| (dir, pos, self.get_wrapped(pos))) + } + + /// Like [Grid::relatives] but with `kernels` set to the four cardinal directions. + pub fn adjacent<'a>( + &'a self, + pos: Position, + ) -> impl Iterator + 'a { + self.relatives(pos, &CARDINALS) + } + + /// Like [Grid::relatives_wrapped] but with `kernels` set to the four cardinal directions. + pub fn adjacent_wrapped<'a>( + &'a self, + pos: Position, + ) -> impl Iterator + 'a { + self.relatives_wrapped(pos, &CARDINALS) + } + + /// Like [Grid::relatives_expand_by] but with `kernels` set to the four cardinal directions. + pub fn adjacent_expand_by<'a>( + &'a self, + pos: Position, + expand: usize, + ) -> impl Iterator + 'a { + self.relatives_expand_by(pos, &CARDINALS, expand) + } + + /// Like [Grid::relatives_expand_by_wrapped] but with `kernels` set to the four cardinal directions. + pub fn adjacent_expand_by_wrapped<'a>( + &'a self, + pos: Position, + expand: usize, + ) -> impl Iterator + 'a { + self.relatives_expand_by_wrapped(pos, &CARDINALS, expand) + } +} + +impl std::fmt::Debug for Grid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_struct("Grid"); + for (y, row) in self.data.iter().enumerate() { + debug.field(&format!("row_{}", y), row); + } + debug.finish() + } +} + +/// Utilities for making tiles of a grid. +pub mod tiles { + use crate::{dir::Movement, pos::Position}; + + use super::Grid; + + #[macro_export] + /// Create an enum that implements `From`. + /// + /// There are three versions of this macro: + /// + /// ## 1. Simple + /// + /// Create a simple enum that implements `From`, with the specific characters mapping to specific variants. + /// Also will make the implementation panic if an invalid character is given. + /// + /// ``` + /// use utils::prelude::*; + /// use utils::tiles; + /// + /// tiles!(Tile, [ + /// '.' => Floor, + /// '#' => Wall, + /// ]); + /// + /// assert_eq!(Tile::from('.'), Tile::Floor); + /// assert_eq!(Tile::from('#'), Tile::Wall); + /// ``` + /// + /// ## 2. With Extra Variants + /// + /// Create an enum that implements `From`, with the specific characters mapping to specific variants. + /// Also allows for extra variants to be added, which won't be mapped to any characters. + /// + /// ``` + /// use utils::prelude::*; + /// use utils::tiles; + /// + /// tiles!(Tile, [ + /// '.' => Floor, + /// '#' => Wall, + /// ], [ + /// Empty, + /// ]); + /// + /// assert_eq!(Tile::from('.'), Tile::Floor); + /// assert_eq!(Tile::from('#'), Tile::Wall); + /// let empty = Tile::Empty; + /// ``` + /// + /// The extra variants can also have fields. + /// + /// ``` + /// use utils::prelude::*; + /// use utils::tiles; + /// + /// tiles!(Tile, [ + /// '.' => Floor, + /// '#' => Wall, + /// ], [ + /// Door(bool), + /// ]); + /// + /// assert_eq!(Tile::from('.'), Tile::Floor); + /// assert_eq!(Tile::from('#'), Tile::Wall); + /// let door = Tile::Door(true); + /// ``` + /// + /// ## 3. With Extra Variants and Extra Logic for Invalid Characters + /// + /// Create an enum that implements `From`, with the specific characters mapping to specific variants. + /// Also allows for extra variants to be added, which won't be mapped to any characters. + /// Also allows for extra logic to be added for invalid characters. + /// + /// ``` + /// use utils::prelude::*; + /// use utils::tiles; + /// + /// tiles!(Tile, [ + /// '.' => Floor, + /// '#' => Wall, + /// ], [ + /// Slope(Direction) + /// ], |c| { + /// match c { + /// '>' => Tile::Slope(Direction::East), + /// '<' => Tile::Slope(Direction::West), + /// _ => panic!("Invalid tile {c}"), + /// } + /// }); + /// + /// assert_eq!(Tile::from('.'), Tile::Floor); + /// assert_eq!(Tile::from('#'), Tile::Wall); + /// assert_eq!(Tile::from('>'), Tile::Slope(Direction::East)); + /// ``` + /// + macro_rules! tiles { + ($name:ident, [$($char:pat => $v_name:ident$(,)?)*]) => { + tiles!($name, [$($char => $v_name,)*], [], |c| { panic!("Invalid tile {c}") }); + }; + + ($name:ident, [$($char:pat => $v_name:ident$(,)?)*], [$($e_name:ident$(($($i_name:ty$(,)?)*))?$(,)?)*]) => { + tiles!($name, [$($char => $v_name,)*], [$($e_name$(($($i_name,)*))?,)*], |c| { panic!("Invalid tile {c}") }); + }; + + ($name:ident, [$($char:pat => $v_name:ident$(,)?)*], [$($e_name:ident$(($($i_name:ty$(,)?)*))?$(,)?)*], $default:expr) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum $name { + $($v_name,)* + $($e_name$(($($i_name,)*))?,)* + } + + impl From for $name { + fn from(c: char) -> Self { + match c { + $($char => Self::$v_name,)* + _ => ($default)(c), + } + } + } + }; + } + + /// Simple tile that holds a number value. + pub struct NumberTile { + pub value: isize, + } + + impl From for NumberTile { + fn from(c: char) -> Self { + Self { + value: c.to_digit(10).unwrap() as isize, + } + } + } + + /// A tile that represents some kind of movement to another position in the grid. + pub trait DirectedTile: Copy + Clone { + /// Get the next direction from the previous direction and position. + fn next_dir(&self, previous_dir: T, pos: Position) -> Option; + + /// Get the next position and position from the previous direction and position. + fn next_pos(&self, previous_dir: T, pos: Position) -> Option<(T, Position)> { + self.next_dir(previous_dir, pos) + .map(|d| (d, pos.move_dir(d))) + } + } + + /// A tile that can be used in a flood fill. + pub trait FillableTile: Copy + Clone { + /// Check if the tile can be filled. + fn get_next_tiles(&self, pos: Position, grid: &Grid) -> Vec; + } +} + +/// Utilities for traversing a grid. +pub mod cursors { + + use std::{ + collections::{HashSet, VecDeque}, + hash::Hasher, + }; + + use super::{ + tiles::{DirectedTile, FillableTile}, + *, + }; + + #[derive(Clone, Copy)] + /// A cursor for traversing a grid. + /// + /// This cursor holds a position and a direction which represents the current position in the grid. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let data = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// let mut cursor = GridCursor::zero(&grid); + /// + /// assert_eq!(cursor.get(), Some(&1)); + /// cursor.move_forward(); + /// assert_eq!(cursor.get(), Some(&2)); + /// cursor.turn(true); + /// cursor.move_forward(); + /// assert_eq!(cursor.get(), Some(&5)); + /// ``` + /// + pub struct GridCursor<'a, T, D: Movement> { + grid: &'a Grid, + pos: Position, + dir: D, + } + + impl<'a, T> GridCursor<'a, T, Direction> { + /// Create a new cursor at position (0, 0) facing east. + pub fn zero(grid: &'a Grid) -> Self { + Self { + grid, + pos: Position::new(0, 0), + dir: Direction::East, + } + } + + /// Turn the cursor 90 degrees clockwise or counter-clockwise. + pub fn turn(&mut self, clockwise: bool) { + self.dir = self.dir.ninety_deg(clockwise); + } + + /// Turn the cursor 180 degrees. + pub fn turn_around(&mut self) { + self.dir = self.dir.opposite(); + } + } + + impl<'a, T, D: Movement> PartialEq for GridCursor<'a, T, D> { + fn eq(&self, other: &Self) -> bool { + self.pos == other.pos && self.dir == other.dir + } + } + + impl<'a, T, D: Movement> std::hash::Hash for GridCursor<'a, T, D> { + fn hash(&self, state: &mut H) { + self.pos.hash(state); + self.dir.hash(state); + } + } + + impl<'a, T, D: Movement> GridCursor<'a, T, D> { + /// Create a new cursor at the given position and direction. + pub fn new(grid: &'a Grid, pos: Position, dir: D) -> Self { + Self { grid, pos, dir } + } + + /// Move the cursor forward one step in the direction it is facing. + pub fn move_forward(&mut self) { + self.pos = self.pos.move_dir(self.dir); + } + + /// Get the value at the current position of the cursor. + pub fn get(&self) -> Option<&T> { + self.grid.get(self.pos) + } + + /// Move the cursor forward one step in the direction it is facing and get the value at the new position. + pub fn next(&mut self) -> Option<&T> { + self.move_forward(); + self.get() + } + } + + impl<'a, T: std::fmt::Debug, D: Movement> std::fmt::Debug for GridCursor<'a, T, D> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GridCursor") + .field("pos", &self.pos) + .field("dir", &self.dir) + .field("value", &self.get()) + .finish() + } + } + + /// A cursor for traversing a grid with a direction. + /// + /// This cursor will follow the direction of the tile it is currently on. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// use utils::tiles; + /// + /// tiles!(Tile, [ + /// '>' => Right, + /// '<' => Left, + /// '^' => Up, + /// 'v' => Down, + /// ]); + /// + /// impl DirectedTile for Tile { + /// fn next_dir(&self, previous_dir: Direction, pos: Position) -> Option { + /// match self { + /// Tile::Right => Some(Direction::East), + /// Tile::Left => Some(Direction::West), + /// Tile::Up => Some(Direction::North), + /// Tile::Down => Some(Direction::South), + /// _ => None, + /// } + /// } + /// } + /// + /// let data = vec![ + /// vec![Tile::Right, Tile::Right, Tile::Down], + /// vec![Tile::Up, Tile::Left, Tile::Down], + /// vec![Tile::Up, Tile::Left, Tile::Left], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// let mut cursor = DirectedCursor::new(&grid, Position::new(0, 0), Direction::East); + /// + /// let path = cursor.map(|(p, _, _)| p).take(8).collect::>(); + /// + /// assert_eq!(path, vec![ + /// Position::new(1, 0), + /// Position::new(2, 0), + /// Position::new(2, 1), + /// Position::new(2, 2), + /// Position::new(1, 2), + /// Position::new(0, 2), + /// Position::new(0, 1), + /// Position::new(0, 0), + /// ]); + /// ``` + /// + pub struct DirectedCursor<'a, T: DirectedTile, D: Movement>(GridCursor<'a, T, D>); + + impl<'a, T: DirectedTile, D: Movement> DirectedCursor<'a, T, D> { + /// Create a new cursor at the given position and direction. + /// Note this starting position will *not* be included in the iterator. + pub fn new(grid: &'a Grid, pos: Position, dir: D) -> Self { + let initial_cursor = GridCursor::new(grid, pos, dir); + Self(initial_cursor) + } + } + + impl<'a, T: DirectedTile, D: Movement> Iterator for DirectedCursor<'a, T, D> { + type Item = (Position, D, T); + + fn next(&mut self) -> Option { + let current_val = self.0.get().cloned(); + current_val.and_then(|tile| { + tile.next_pos(self.0.dir, self.0.pos).map(|(dir, pos)| { + self.0.dir = dir; + self.0.pos = pos; + (self.0.pos, self.0.dir, tile) + }) + }) + } + } + + /// A cursor that flood fills a grid. + /// + /// This cursor will flood fill the grid from the given position, + /// using [FillableTile::get_next_tiles] to determine which tiles to fill. + /// + /// Setting `wrapped` to true will make the cursor wrap around the grid if necessary. + /// Note this can lead to infinite loops if you don't have something to stop the iterator. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// use utils::tiles; + /// + /// tiles!(Tile, [ + /// '.' => Floor, + /// '#' => Wall, + /// ]); + /// + /// impl FillableTile for Tile { + /// fn get_next_tiles(&self, pos: Position, grid: &Grid) -> Vec { + /// match self { + /// Tile::Floor => grid.adjacent(pos).filter(|(_, _, t)| t == &&Tile::Floor).map(|(_, p, _)| p).collect(), + /// _ => vec![], + /// } + /// } + /// } + /// + /// let data = vec![ + /// vec![Tile::Floor, Tile::Floor, Tile::Floor], + /// vec![Tile::Floor, Tile::Wall, Tile::Floor], + /// vec![Tile::Floor, Tile::Floor, Tile::Wall], + /// ]; + /// + /// let grid = Grid::new(data); + /// + /// let mut cursor = FloodFillCursor::new(&grid, Position::new(0, 0), true); + /// + /// let path = cursor.collect::>(); + /// + /// assert_eq!(path, vec![ + /// Position::new(0, 0), + /// Position::new(0, 1), + /// Position::new(1, 0), + /// Position::new(0, 2), + /// Position::new(2, 0), + /// Position::new(1, 2), + /// Position::new(2, 1), + /// ]); + /// ``` + /// + pub struct FloodFillCursor<'a, T: FillableTile> { + grid: &'a Grid, + visited: HashSet, + queue: VecDeque, + wrapped: bool, + } + + impl<'a, T: FillableTile> FloodFillCursor<'a, T> { + /// Create a new cursor at the given position. + pub fn new(grid: &'a Grid, pos: Position, wrapped: bool) -> Self { + let mut visited = HashSet::new(); + visited.insert(pos); + let mut queue = VecDeque::new(); + queue.push_back(pos); + Self { + grid, + visited, + queue, + wrapped, + } + } + } + + impl<'a, T: FillableTile> std::fmt::Debug for FloodFillCursor<'a, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FloodFillCursor") + .field("visited", &self.visited) + .field("queue", &self.queue) + .finish() + } + } + + impl<'a, T: FillableTile> Iterator for FloodFillCursor<'a, T> { + type Item = Position; + + fn next(&mut self) -> Option { + let pos = self.queue.pop_front()?; + let tile = if self.wrapped { + self.grid.get_wrapped(pos) + } else { + self.grid.get(pos)? + }; + for next_pos in tile.get_next_tiles(pos, self.grid) { + if self.visited.insert(next_pos) { + self.queue.push_back(next_pos); + } + } + Some(pos) + } + } +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,14 +1,19 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} +pub mod day_utils; +pub mod dir; +pub mod geom; +pub mod grid; +pub mod line; +pub mod pos; +pub mod range; -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } +pub mod prelude { + pub use crate::day_utils::*; + pub use crate::dir::*; + pub use crate::geom; + pub use crate::grid::cursors::*; + pub use crate::grid::tiles::*; + pub use crate::grid::*; + pub use crate::line::*; + pub use crate::pos::*; + pub use crate::range::*; } diff --git a/utils/src/line.rs b/utils/src/line.rs new file mode 100644 --- /dev/null +++ b/utils/src/line.rs @@ -0,0 +1,171 @@ +use std::ops::Neg; + +use crate::{dir::Direction, pos::Position}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// A line between two points. +/// +/// This line is represented by two [Position]s. +/// +/// # Examples +/// +/// ``` +/// use utils::prelude::*; +/// +/// let line = Line::new(Position::new(0, 0), Position::new(1, 1)); +/// assert_eq!(line.get_slope(), 1.0); +/// assert_eq!(line.get_intercept(), 0.0); +/// ``` +/// +pub struct Line(Position, Position); + +impl Line { + /// Create a new line between two points. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let line = Line::new(Position::new(0, 0), Position::new(1, 1)); + /// assert_eq!(line.end().x, 1); + /// ``` + /// + pub fn new(start: Position, end: Position) -> Self { + Self(start, end) + } + + /// Create a new line from a starting point and a direction. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let line = Line::from_dir(Position::new(0, 0), Direction::East); + /// assert_eq!(line.end().x, 1); + /// ``` + /// + pub fn from_dir(start: Position, dir: Direction) -> Self { + Self(start, start.move_dir(dir)) + } + + /// Get the linear slope of the line. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let line = Line::new(Position::new(0, 0), Position::new(1, 1)); + /// assert_eq!(line.get_slope(), 1.0); + /// + /// let line = Line::new(Position::new(0, 0), Position::new(2, 1)); + /// assert_eq!(line.get_slope(), 0.5); + /// ``` + /// + pub fn get_slope(&self) -> f64 { + let dx = self.1.x - self.0.x; + let dy = self.1.y - self.0.y; + dy as f64 / dx as f64 + } + + /// Get the y-intercept of the line. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let line = Line::new(Position::new(0, 0), Position::new(1, 1)); + /// assert_eq!(line.get_intercept(), 0.0); + /// + /// let line = Line::new(Position::new(0, 5), Position::new(2, 1)); + /// assert_eq!(line.get_intercept(), 5.0); + /// ``` + /// + pub fn get_intercept(&self) -> f64 { + let slope = self.get_slope(); + self.0.y as f64 - slope * self.0.x as f64 + } + + /// Check that the given point is *after* the start position on the line. + /// + /// Note this doesn't check if the point is on the line, just that it is + /// on the same side of the line as the end point. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let line = Line::new(Position::new(0, 0), Position::new(2, 2)); + /// assert_eq!(line.check_after(&Position::new(1, 1)), true); + /// + /// let line = Line::new(Position::new(0, 0), Position::new(2, 2)); + /// assert_eq!(line.check_after(&Position::new(-1, -1)), false); + /// ``` + /// + pub fn check_after(&self, pos: &Position) -> bool { + let relative = pos.sub(&self.0); + let d = self.1.sub(&self.0); + relative.normalize() == d.normalize() + } + + /// Get the intersection point between this line and another. + /// + /// Pass `check_after` as `true` to ensure that the intersection point is + /// after the start of both lines. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let line1 = Line::new(Position::new(2, -2), Position::new(-2, 2)); + /// let line2 = Line::new(Position::new(2, 2), Position::new(-2, -2)); + /// assert_eq!(line1.get_intersection(&line2, true), Some(Position::new(0, 0))); + /// + /// let line1 = Line::new(Position::new(5, 0), Position::new(6, 8)); + /// let line2 = Line::new(Position::new(0, 1), Position::new(-4, -3)); + /// assert_eq!(line1.get_intersection(&line2, true), None); + /// ``` + /// + pub fn get_intersection(&self, other: &Self, check_after: bool) -> Option { + let slope = self.get_slope(); + let intercept = self.get_intercept(); + let other_slope = other.get_slope(); + let other_intercept = other.get_intercept(); + + if slope == other_slope { + return None; + } + + let x = (other_intercept - intercept) / (slope - other_slope); + let y = slope * x + intercept; + + let point = Position::new(x as isize, y as isize); + + if !check_after || self.check_after(&point) && other.check_after(&point) { + Some(point) + } else { + None + } + } + + pub fn start(&self) -> Position { + self.0 + } + + pub fn end(&self) -> Position { + self.1 + } +} + +impl Neg for Line { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(self.1, self.0) + } +} diff --git a/utils/src/pos.rs b/utils/src/pos.rs new file mode 100644 --- /dev/null +++ b/utils/src/pos.rs @@ -0,0 +1,775 @@ +/// Position utilities +use std::{ + fmt::{Debug, Display}, + ops::{Add, Mul, Neg, Sub}, + str::FromStr, +}; + +use crate::dir::{Direction, Movement, CARDINALS}; + +type CompType = isize; +type PositiveType = (usize, usize); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// A position in 2D space on an integer grid +/// This is meant to represent indices of a 2D array, so north is negative y +/// +/// This is also used to represent a vector in 2D space at times +pub struct Position { + pub x: CompType, + pub y: CompType, +} + +impl Position { + /// Create a new position + pub fn new(x: CompType, y: CompType) -> Self { + Self { x, y } + } + + /// Create a new position at 0, 0 + pub fn zero() -> Self { + Self { x: 0, y: 0 } + } + + /// Create the unit vector position + pub fn one() -> Self { + Self { x: 1, y: 1 } + } + + /// Get the position flipped over the line y = x + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let flipped = Position::new(1, 2).flip(); + /// assert_eq!(flipped, Position::new(2, 1)); + /// ``` + /// + pub fn flip(&self) -> Self { + Self { + x: self.y, + y: self.x, + } + } + + /// Normalize a position to a unit vector + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let normalized = Position::new(1, 1).normalize(); + /// assert_eq!(normalized, Position::new(1, 1)); + /// + /// let normalized = Position::new(50, -45).normalize(); + /// assert_eq!(normalized, Position::new(1, -1)); + /// + /// let normalized = Position::new(-30, 0).normalize(); + /// assert_eq!(normalized, Position::new(-1, 0)); + /// ``` + /// + pub fn normalize(&self) -> Self { + Self { + x: self.x.signum(), + y: self.y.signum(), + } + } + + /// Get the magnitude of a position + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let mag = Position::new(0, 1).magnitude(); + /// assert_eq!(mag, 1.0); + /// + /// let mag = Position::new(3, 4).magnitude(); + /// assert_eq!(mag, 5.0); + /// ``` + /// + pub fn magnitude(&self) -> f64 { + (((self.x * self.x) + (self.y * self.y)) as f64).sqrt() + } + + /// Get the absolute value of a position + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let abs = Position::new(-1, -1).abs(); + /// assert_eq!(abs, Position::new(1, 1)); + /// ``` + /// + pub fn abs(&self) -> Self { + Self { + x: self.x.abs(), + y: self.y.abs(), + } + } + + /// Sum the components of a position + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let sum = Position::new(1, 1).sum(); + /// assert_eq!(sum, 2); + /// ``` + /// + pub fn sum(&self) -> CompType { + self.x + self.y + } + + /// Get the difference between the components of a position + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let diff = Position::new(1, 1).diff(); + /// assert_eq!(diff, 0); + /// ``` + /// + pub fn diff(&self) -> CompType { + self.x - self.y + } + + /// Get the direction of one position relative to another + /// + /// This is the direction that the second position is from the first + /// + /// Meaning + /// + /// ```txt + /// ... + /// A.B + /// ... + /// + /// A.get_dir(B) == Direction::East + /// and + /// B.get_dir(A) == Direction::West + /// ``` + /// + /// # Panics + /// + /// If the positions are the same or diagonal from each other + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let dir = Position::new(1, 1).get_dir(&Position::new(5, 1)); + /// assert_eq!(dir, Direction::East); + /// + /// let dir = Position::new(5, 1).get_dir(&Position::new(1, 1)); + /// assert_eq!(dir, Direction::West); + /// + /// let dir = Position::new(1, 1).get_dir(&Position::new(1, 5)); + /// assert_eq!(dir, Direction::South); + /// ``` + /// + pub fn get_dir(&self, other: &Self) -> Direction { + other.sub(self).normalize().into() + } + + /// Add two positions together + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let sum = Position::new(1, 1).add(&Position::new(5, 4)); + /// assert_eq!(sum, Position::new(6, 5)); + /// ``` + /// + pub fn add(&self, other: &Self) -> Self { + Self { + x: self.x + other.x, + y: self.y + other.y, + } + } + + /// Get the difference between two positions + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let diff = Position::new(1, 1).sub(&Position::new(5, 5)); + /// assert_eq!(diff, Position::new(-4, -4)); + /// ``` + /// + pub fn sub(&self, other: &Self) -> Self { + Self { + x: self.x - other.x, + y: self.y - other.y, + } + } + + /// Multiply two positions together + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let multiplied = Position::new(1, 1).multiply(&Position::new(5, 4)); + /// assert_eq!(multiplied, Position::new(5, 4)); + /// ``` + /// + pub fn multiply(&self, other: &Self) -> Self { + Self { + x: self.x * other.x, + y: self.y * other.y, + } + } + + /// Get the dot product of two positions + /// + /// x1 * x2 + y1 * y2 + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let dot = Position::new(1, 1).dot(&Position::new(5, 4)); + /// assert_eq!(dot, 9); + /// ``` + /// + pub fn dot(&self, other: &Self) -> CompType { + self.multiply(other).sum() + } + + /// Get the angle between two positions + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let angle = Position::new(0, 1).angle(&Position::new(1, 0)); + /// + /// assert_eq!(angle, std::f64::consts::FRAC_PI_2); + /// ``` + /// + pub fn angle(&self, other: &Self) -> f64 { + let dot = self.dot(other) as f64; + let mag = self.magnitude() * other.magnitude(); + (dot / mag).acos() + } + + /// Get the cross product of two positions + /// + /// x1 * y2 - y1 * x2 + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let cross = Position::new(2, 3).cross(&Position::new(5, 4)); + /// assert_eq!(cross, -7); + /// ``` + /// + pub fn cross(&self, other: &Self) -> CompType { + self.multiply(&other.flip()).diff() + } + + /// Multiply a position by a scalar + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let multiplied = Position::new(1, 1).multiply_comp(5); + /// assert_eq!(multiplied, Position::new(5, 5)); + /// ``` + /// + pub fn multiply_comp(&self, other: CompType) -> Self { + Self { + x: self.x * other, + y: self.y * other, + } + } + + /// Get the manhattan distance between two positions + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let distance = Position::new(1, 1).manhattan(&Position::new(5, 5)); + /// assert_eq!(distance, 8); + /// ``` + /// + pub fn manhattan(&self, other: &Self) -> CompType { + self.sub(other).abs().sum() + } + + /// Get the chebyshev distance between two positions + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let distance = Position::new(1, 1).chebyshev(&Position::new(5, 5)); + /// assert_eq!(distance, 4); + /// ``` + /// + pub fn chebyshev(&self, other: &Self) -> CompType { + let diff = self.sub(other).abs(); + diff.x.max(diff.y) + } + + /// Check if a component is within a range of 0..bound + /// Note bound is exclusive + fn check_comp(comp: CompType, bound: usize) -> bool { + (0..(bound as isize)).contains(&comp) + } + + /// Check if a position is within a range of (0..bound.0, 0..bound.1) + /// Note bound is exclusive + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let checked = Position::new(0, 0).check((10, 10)); + /// assert!(checked); + /// + /// let checked = Position::new(50, 50).check((5, 5)); + /// assert_eq!(checked, false); + /// ``` + /// + pub fn check(&self, bounds: PositiveType) -> bool { + Self::check_comp(self.x, bounds.0) && Self::check_comp(self.y, bounds.1) + } + + /// Normalize a value to be within a range of 0..bound + /// Note bound is exclusive + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let normalized = Position::bind_comp(-1, 10); + /// assert_eq!(normalized, 9); + /// + /// let normalized = Position::bind_comp(-10, 5); + /// assert_eq!(normalized, 0); + /// + /// let normalized = Position::bind_comp(10, 5); + /// assert_eq!(normalized, 0); + /// + /// let normalized = Position::bind_comp(3, 6); + /// assert_eq!(normalized, 3); + /// ``` + /// + pub fn bind_comp(comp: CompType, bound: usize) -> usize { + let bound = bound as isize; + if comp >= bound || comp.is_negative() { + let ans = comp % bound; + if ans.is_negative() { + (ans + bound) as usize + } else { + ans as usize + } + } else { + comp as usize + } + } + + /// Bind a position to be within ranges of (0..bound.0, 0..bound.1) + /// Note bound is exclusive + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let bound = Position::new(-1, -1).bind((11, 11)); + /// assert_eq!(bound, (10, 10)); + /// + /// let bound = Position::new(-10, -10).bind((6, 6)); + /// assert_eq!(bound, (2, 2)); + /// + /// let bound = Position::new(10, 10).bind((6, 6)); + /// assert_eq!(bound, (4, 4)); + /// ``` + /// + pub fn bind(&self, bounds: PositiveType) -> PositiveType { + ( + Self::bind_comp(self.x, bounds.0), + Self::bind_comp(self.y, bounds.1), + ) + } + + /// Move a position by direction + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let moved = Position::new(0, 0).move_dir(Direction::North); + /// assert_eq!(moved, Position::new(0, -1)); + /// + /// let moved = Position::new(0, 0).move_dir(Direction::East); + /// assert_eq!(moved, Position::new(1, 0)); + /// ``` + /// + pub fn move_dir(&self, dir: impl Movement) -> Self { + self.add(&dir.get_kernel()) + } + + /// Move a position by direction a certain number of times + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let moved = Position::new(0, 0).move_times(Direction::North, 5); + /// assert_eq!(moved, Position::new(0, -5)); + /// ``` + /// + pub fn move_times(&self, dir: impl Movement, times: usize) -> Self { + self.add(&dir.get_kernel().multiply_comp(times as isize)) + } + + /// Move a position by direction, + /// checking if it is within a range of (0..bound.0, 0..bound.1) + /// + /// # Returns + /// + /// * `Some(Position)` if the new position is within the bounds + /// * `None` if the new position is outside the bounds + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let moved = Position::new(0, 0).move_dir_checked(Direction::East, (10, 10)); + /// assert_eq!(moved.unwrap(), Position::new(1, 0)); + /// + /// let moved = Position::new(40, 40).move_dir_checked(Direction::East, (40, 40)); + /// assert!(moved.is_none()); + /// ``` + /// + pub fn move_dir_checked(&self, dir: impl Movement, bounds: PositiveType) -> Option { + let new = self.move_dir(dir); + if new.check(bounds) { + Some(new) + } else { + None + } + } + + /// Move a position by direction a certain number of times, + /// checking if it is within a range of (0..bound.0, 0..bound.1) + /// + /// # Returns + /// + /// * `Some(Position)` if the new position is within the bounds + /// * `None` if the new position is outside the bounds + /// + pub fn move_times_checked( + &self, + dir: impl Movement, + times: usize, + bounds: PositiveType, + ) -> Option { + let new = self.move_times(dir, times); + if new.check(bounds) { + Some(new) + } else { + None + } + } + + /// Get all positions relative to this position by a list of directions + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let relatives = Position::new(0, 0).relatives(&[Direction::North, Direction::East]).collect::>(); + /// assert_eq!(relatives, vec![(Position::new(0, -1), Direction::North), (Position::new(1, 0), Direction::East)]); + /// ``` + /// + pub fn relatives<'a, T: Movement>( + self, + kernels: &'a [T], + ) -> impl Iterator + 'a { + kernels.into_iter().map(move |k| (self.move_dir(*k), *k)) + } + + /// Get all positions relative to this position by a list of directions, + /// checking if they are within a range of (0..bound.0, 0..bound.1) + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let relatives = Position::new(0, 0).relatives_checked(&[Direction::North, Direction::East], (10, 10)).collect::>(); + /// assert_eq!(relatives, vec![(Position::new(1, 0), Direction::East)]); + /// ``` + /// + pub fn relatives_checked<'a, T: Movement>( + self, + kernels: &'a [T], + bounds: PositiveType, + ) -> impl Iterator + 'a { + kernels + .iter() + .filter_map(move |k| self.move_dir_checked(*k, bounds).map(|p| (p, *k))) + } + + /// Get all positions relative to this position by a list of directions, + /// repeating each direction a certain number of times + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let relatives = Position::new(0, 0).relatives_expand_by(&[Direction::North, Direction::East], 2).collect::>(); + /// let expected = vec![ + /// ((Direction::North, 1), Position::new(0, -1)), + /// ((Direction::North, 2), Position::new(0, -2)), + /// ((Direction::East, 1), Position::new(1, 0)), + /// ((Direction::East, 2), Position::new(2, 0)), + /// ]; + /// + /// assert_eq!(relatives, expected); + /// ``` + /// + pub fn relatives_expand_by<'a, T: Movement>( + self, + kernels: &'a [T], + times: usize, + ) -> impl Iterator + 'a { + kernels + .into_iter() + .flat_map(move |k| (1..=times).map(move |t| ((*k, t), self.move_times(*k, t)))) + } + + /// Get all positions relative to this position by a list of directions, + /// repeating each direction a certain number of times, + /// checking if they are within a range of (0..bound.0, 0..bound.1) + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let relatives = Position::new(0, 0).relatives_expand_by_checked(&[Direction::North, Direction::East], 2, (10, 10)).collect::>(); + /// let expected = vec![ + /// ((Direction::East, 1), Position::new(1, 0)), + /// ((Direction::East, 2), Position::new(2, 0)), + /// ]; + /// + /// assert_eq!(relatives, expected); + /// ``` + /// + pub fn relatives_expand_by_checked<'a, T: Movement>( + self, + kernels: &'a [T], + times: usize, + bounds: PositiveType, + ) -> impl Iterator + 'a { + kernels.into_iter().flat_map(move |k| { + (1..=times) + .filter_map(move |t| self.move_times_checked(*k, t, bounds).map(|p| ((*k, t), p))) + }) + } + + /// Get all positions adjacent to this position + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let adjacents = Position::new(0, 0).adjacents().collect::>(); + /// let expected = vec![ + /// (Position::new(0, -1), Direction::North), + /// (Position::new(0, 1), Direction::South), + /// (Position::new(1, 0), Direction::East), + /// (Position::new(-1, 0), Direction::West), + /// ]; + /// + /// assert_eq!(adjacents, expected); + /// ``` + /// + pub fn adjacents(self) -> impl Iterator { + self.relatives(&CARDINALS) + } + + /// Get all positions adjacent to this position + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let adjacents = Position::new(0, 0).adjacents_checked((2, 2)).collect::>(); + /// let expected = vec![ + /// (Position::new(0, 1), Direction::South), + /// (Position::new(1, 0), Direction::East), + /// ]; + /// + /// assert_eq!(adjacents, expected); + /// ``` + /// + pub fn adjacents_checked( + self, + bounds: PositiveType, + ) -> impl Iterator { + self.relatives_checked(&CARDINALS, bounds) + } +} + +impl Add for Position { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self::add(&self, &other) + } +} + +impl Add<&Position> for Position { + type Output = Self; + + fn add(self, other: &Self) -> Self { + Self::add(&self, other) + } +} + +impl Sub for Position { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self::sub(&self, &other) + } +} + +impl Sub<&Position> for Position { + type Output = Self; + + fn sub(self, other: &Self) -> Self { + Self::sub(&self, other) + } +} + +impl Mul for Position { + type Output = Self; + + fn mul(self, other: Self) -> Self { + Self::multiply(&self, &other) + } +} + +impl Mul<&Position> for Position { + type Output = Self; + + fn mul(self, other: &Self) -> Self { + Self::multiply(&self, other) + } +} + +impl Mul for Position { + type Output = Self; + + fn mul(self, other: CompType) -> Self { + Self::multiply_comp(&self, other) + } +} + +impl Mul for Position { + type Output = Self; + + fn mul(self, other: usize) -> Self { + Self::multiply_comp(&self, other as isize) + } +} + +impl Neg for Position { + type Output = Self; + + fn neg(self) -> Self { + self.multiply_comp(-1) + } +} + +impl Display for Position { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({},{})", self.x, self.y) + } +} + +impl FromStr for Position { + type Err = String; + + fn from_str(s: &str) -> Result { + let mut split = s.split(','); + let x = split.next().ok_or("No x")?.parse().expect("No x"); + let y = split.next().ok_or("No y")?.parse().expect("No y"); + Ok(Self { x, y }) + } +} + +impl From<(CompType, CompType)> for Position { + fn from((x, y): (CompType, CompType)) -> Self { + Self { x, y } + } +} + +impl Into<(CompType, CompType)> for Position { + fn into(self) -> (CompType, CompType) { + (self.x, self.y) + } +} + +impl From<(usize, usize)> for Position { + fn from((x, y): (usize, usize)) -> Self { + Self { + x: x as isize, + y: y as isize, + } + } +} + +impl Into<(usize, usize)> for Position { + fn into(self) -> (usize, usize) { + (self.x as usize, self.y as usize) + } +} + +impl From for Position { + fn from(dir: Direction) -> Self { + dir.get_kernel() + } +} diff --git a/utils/src/range.rs b/utils/src/range.rs new file mode 100644 --- /dev/null +++ b/utils/src/range.rs @@ -0,0 +1,172 @@ +use std::{fmt::Debug, ops::Range}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// Represents a range of values. +/// +/// End is exclusive. +pub struct BetterRange { + pub start: T, + pub end: T, +} + +impl BetterRange { + pub fn new(start: T, end: T) -> Self { + Self { start, end } + } +} + +pub enum RangeSplitBehavior { + IncludeLower, + IncludeUpper, + Exclude, +} + +impl BetterRange { + /// Checks if the range contains the given value. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let range = BetterRange::new(0, 10); + /// assert!(range.contains(&5)); + /// assert!(!range.contains(&10)); + /// ``` + /// + pub fn contains(&self, value: &T) -> bool { + self.start <= *value && *value < self.end + } + + /// Checks if the range contains the given range. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let range = BetterRange::new(0, 10); + /// assert!(range.contains_range(&BetterRange::new(5, 7))); + /// assert!(!range.contains_range(&BetterRange::new(5, 15))); + /// ``` + /// + pub fn contains_range(&self, other: &Self) -> bool { + self.start <= other.start && other.end < self.end + } + + /// Split the range at the given value. + /// + /// The behaviour determines if the value is included in the lower range, upper range, or neither. + /// + /// # Examples + /// + /// ``` + /// use utils::prelude::*; + /// + /// let range = BetterRange::new(0, 10); + /// + /// let (lower, upper) = range.split(&5, RangeSplitBehavior::IncludeLower); + /// assert_eq!(lower, Some(BetterRange::new(0, 6))); + /// assert_eq!(upper, Some(BetterRange::new(6, 10))); + /// ``` + /// + /// ``` + /// use utils::prelude::*; + /// + /// let range = BetterRange::new(0, 10); + /// + /// let (lower, upper) = range.split(&5, RangeSplitBehavior::IncludeUpper); + /// assert_eq!(lower, Some(BetterRange::new(0, 5))); + /// assert_eq!(upper, Some(BetterRange::new(5, 10))); + /// ``` + /// + /// ``` + /// use utils::prelude::*; + /// + /// let range = BetterRange::new(0, 10); + /// + /// let (lower, upper) = range.split(&5, RangeSplitBehavior::Exclude); + /// assert_eq!(lower, Some(BetterRange::new(0, 5))); + /// assert_eq!(upper, Some(BetterRange::new(6, 10))); + /// ``` + /// + pub fn split(&self, value: &T, behaviour: RangeSplitBehavior) -> (Option, Option) + where + T: std::ops::Add + std::ops::Sub, + { + if self.contains(value) { + match behaviour { + RangeSplitBehavior::IncludeLower => ( + Some(Self::new(self.start, *value + 1)), + Some(Self::new(*value + 1, self.end)), + ), + RangeSplitBehavior::IncludeUpper => ( + Some(Self::new(self.start, *value)), + Some(Self::new(*value, self.end)), + ), + RangeSplitBehavior::Exclude => ( + Some(Self::new(self.start, *value)), + Some(Self::new(*value + 1, self.end)), + ), + } + } else { + (None, None) + } + } +} + +impl Into> for BetterRange { + fn into(self) -> Range { + self.start..self.end + } +} + +impl From> for BetterRange { + fn from(range: Range) -> Self { + Self::new(range.start, range.end) + } +} + +impl std::ops::Add for BetterRange +where + T: std::ops::Add, +{ + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + Self::new(self.start + rhs, self.end + rhs) + } +} + +impl std::ops::Sub for BetterRange +where + T: std::ops::Sub, +{ + type Output = Self; + + fn sub(self, rhs: usize) -> Self::Output { + Self::new(self.start - rhs, self.end - rhs) + } +} + +impl std::ops::BitAnd for BetterRange +where + T: std::ops::BitAnd, +{ + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + Self::new(self.start.max(rhs.start), self.end.min(rhs.end)) + } +} + +impl std::ops::BitOr for BetterRange +where + T: std::ops::BitOr, +{ + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self::new(self.start.min(rhs.start), self.end.max(rhs.end)) + } +} diff --git a/years/2023/src/day_25.rs b/years/2023/src/day_25.rs --- a/years/2023/src/day_25.rs +++ b/years/2023/src/day_25.rs @@ -1,17 +1,18 @@ use core::{Day, day_stuff, ex_for_day}; +use utils::yippee; + pub struct Day25; impl Day for Day25 { - day_stuff!(25, "", ""); + day_stuff!(25, "", "🥳"); fn part_1(_input: Self::Input) -> Option { None } - fn part_2(_input: Self::Input) -> Option { - None - } + yippee!(); + } diff --git a/years/2023/src/lib.rs b/years/2023/src/lib.rs --- a/years/2023/src/lib.rs +++ b/years/2023/src/lib.rs @@ -1,4 +1,3 @@ - use macros::year; year!(2023); diff --git a/years/2023/src/main.rs b/years/2023/src/main.rs --- a/years/2023/src/main.rs +++ b/years/2023/src/main.rs @@ -1,4 +1,3 @@ - use macros::year_runner; year_runner!(2023);