diff --git a/examples/brute_force.rs b/examples/brute_force.rs new file mode 100644 index 0000000..6ff9db5 --- /dev/null +++ b/examples/brute_force.rs @@ -0,0 +1,39 @@ +use factorio_blueprint::{ + belt_finding::{ + brute_force::{Bruteforce, PathField}, + Direction, Position, + }, + misc::Map, +}; + +fn main() { + let mut b = Bruteforce { + map: Map::new(5, 10), + path: vec![], + end_pos: Position::new(4, 9), + end_dir: Direction::Up, + count: 0, + solution_count: 0, + }; + + b.apply_path_field(PathField::Belt { + pos: Position::new(1, 0), + dir: Direction::Down, + }); + + b.map.get_mut(0, 2).blocked = true; + b.map.get_mut(1, 2).blocked = true; + b.map.get_mut(2, 2).blocked = true; + b.map.get_mut(3, 2).blocked = true; + b.map.get_mut(4, 2).blocked = true; + + b.map.get_mut(0, 4).blocked = true; + b.map.get_mut(1, 4).blocked = true; + b.map.get_mut(2, 4).blocked = true; + b.map.get_mut(3, 4).blocked = true; + b.map.get_mut(4, 4).blocked = true; + + while b.next_finish_state() { + println!("{}\n{}\n{}", b.count, b.solution_count, b); + } +} diff --git a/src/belt_finding/brute_force.rs b/src/belt_finding/brute_force.rs new file mode 100644 index 0000000..70b8a7a --- /dev/null +++ b/src/belt_finding/brute_force.rs @@ -0,0 +1,326 @@ +use std::fmt::Display; + +use colored::Colorize; + +use crate::misc::Map; + +use super::{Direction, Position, COLORS}; + +#[derive(Default)] +pub struct BruteforceField { + pub blocked: bool, + underground_vertical: bool, + underground_horizontal: bool, +} + +#[derive(Clone)] +pub enum PathField { + Belt { + pos: Position, + dir: Direction, + }, + Underground { + pos: Position, + dir: Direction, + len: u8, + }, +} + +impl PathField { + fn pos(&self) -> &Position { + match self { + PathField::Belt { pos, dir: _ } => pos, + PathField::Underground { + pos, + dir: _, + len: _, + } => pos, + } + } + + fn dir(&self) -> &Direction { + match self { + PathField::Belt { pos: _, dir } => dir, + PathField::Underground { + pos: _, + dir, + len: _, + } => dir, + } + } +} + +static MAX_UNDERGROUND_LENGTH: u8 = 5; + +pub struct Bruteforce { + pub map: Map, + pub path: Vec, + pub end_pos: Position, + pub end_dir: Direction, + pub solution_count: u128, + pub count: u128, +} + +impl Bruteforce { + pub fn apply_path_field(&mut self, path_field: PathField) { + match &path_field { + PathField::Belt { pos, dir: _ } => self.map.get_mut(pos.x, pos.y).blocked = true, + PathField::Underground { pos, dir, len } => { + self.map.get_mut(pos.x, pos.y).blocked = true; + + for i in 1..*len { + let mid_pos = self.pos_in_direction(pos, dir, i.into()).unwrap(); + if dir.vertical() { + self.map.get_mut(mid_pos.x, mid_pos.y).underground_vertical = true; + } else { + self.map + .get_mut(mid_pos.x, mid_pos.y) + .underground_horizontal = true; + } + } + + let end_pos = self.pos_in_direction(pos, dir, *len as usize).unwrap(); + self.map.get_mut(end_pos.x, end_pos.y).blocked = true; + } + } + self.path.push(path_field); + } + + fn apply_path_field_remove(&mut self) { + match self.path.pop().unwrap() { + PathField::Belt { pos, dir: _ } => self.map.get_mut(pos.x, pos.y).blocked = false, + PathField::Underground { pos, dir, len } => { + self.map.get_mut(pos.x, pos.y).blocked = false; + + for i in 1..len { + let mid_pos = self.pos_in_direction(&pos, &dir, i.into()).unwrap(); + if dir.vertical() { + self.map.get_mut(mid_pos.x, mid_pos.y).underground_vertical = false; + } else { + self.map + .get_mut(mid_pos.x, mid_pos.y) + .underground_horizontal = false; + } + } + + let end_pos = self.pos_in_direction(&pos, &dir, len.into()).unwrap(); + self.map.get_mut(end_pos.x, end_pos.y).blocked = false; + } + } + } + + fn check_finish(&self) -> bool { + match self.path.last().unwrap() { + PathField::Belt { pos, dir } => self + .pos_in_direction(pos, dir, 1) + .filter(|p| p == &self.end_pos && dir != &self.end_dir.reverse()) + .is_some(), + PathField::Underground { pos, dir, len } => self + .pos_in_direction(pos, dir, *len as usize + 1) + .filter(|p| p == &self.end_pos && dir != &self.end_dir.reverse()) + .is_some(), + } + } + + fn next_state_internal(&mut self, pos: Position, dir: Direction) -> bool { + if let Some(next_pos) = self + .pos_in_direction(&pos, &dir, 1) + .filter(|p| !self.map.get(p.x, p.y).blocked) + { + self.apply_path_field(PathField::Belt { pos: next_pos, dir }); + true + } else { + false + } + } + + fn path_field_end(&self, path_field: &PathField) -> (Position, Direction) { + match path_field { + PathField::Belt { pos, dir } => (*pos, *dir), + PathField::Underground { pos, dir, len } => ( + self.pos_in_direction(pos, dir, *len as usize).unwrap(), + *dir, + ), + } + } + + fn modify_underground(&mut self, pos: &Position, dir: &Direction, len: u8) -> bool { + for l in len..MAX_UNDERGROUND_LENGTH { + if let Some(p) = self.pos_in_direction(pos, dir, l as usize) { + if !self.map.get(p.x, p.y).blocked { + self.apply_path_field_remove(); + self.apply_path_field(PathField::Underground { + pos: *pos, + dir: *dir, + len: l, + }); + return true; + } + } + } + + false + } + + fn modify_remove(&mut self) -> bool { + if let Some([second_last, last]) = self.path.last_chunk().cloned() { + match last { + PathField::Belt { pos, dir } => { + if second_last.dir() == &dir { + self.apply_path_field_remove(); + self.apply_path_field(PathField::Belt { + pos, + dir: second_last.dir().clockwise(), + }); + return true; + } + + if second_last.dir().clockwise() == dir { + self.apply_path_field_remove(); + self.apply_path_field(PathField::Belt { + pos, + dir: second_last.dir().counter_clockwise(), + }); + return true; + } + + if second_last.dir().counter_clockwise() == dir + && self.modify_underground(&pos, second_last.dir(), 1) + { + return true; + } + } + PathField::Underground { pos, dir, len } => { + if self.modify_underground(&pos, &dir, len + 1) { + return true; + } + } + } + + self.apply_path_field_remove(); + self.modify_remove() + } else { + false + } + } + + pub fn next_state(&mut self) -> bool { + self.count += 1; + let (pos, dir) = self.path_field_end(self.path.last().unwrap()); + + // try add + + if let Some(next_pos) = self + .pos_in_direction(&pos, &dir, 1) + .filter(|p| !self.map.get(p.x, p.y).blocked) + { + self.apply_path_field(PathField::Belt { pos: next_pos, dir }); + return true; + } + + // modify and remove + + self.modify_remove() + } + + pub fn next_finish_state(&mut self) -> bool { + self.solution_count += 1; + while self.next_state() { + if self.check_finish() { + return true; + } + } + + false + } + + fn pos_in_direction(&self, pos: &Position, dir: &Direction, len: usize) -> Option { + match dir { + Direction::Up => pos.y.checked_sub(len).map(|y| Position::new(pos.x, y)), + Direction::Right => Some(pos.x + len) + .filter(|&x| x < self.map.width) + .map(|x| Position::new(x, pos.y)), + Direction::Down => Some(pos.y + len) + .filter(|&y| y < self.map.height) + .map(|y| Position::new(pos.x, y)), + Direction::Left => pos.x.checked_sub(len).map(|x| Position::new(x, pos.y)), + } + } +} + +impl Display for Bruteforce { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let width_digits = self.map.width.ilog10() + 1; + let height_digits = self.map.height.ilog10() + 1; + + // print header + for i in 0..width_digits { + let d = width_digits - i - 1; + // padding + for _ in 0..height_digits { + write!(f, " ")?; + } + + for x in 0..self.map.width { + let digits = x / (usize::pow(10, d)); + if digits == 0 && d > 0 { + write!(f, " ")?; + } else { + write!(f, "{}", char::from_u32((digits % 10) as u32 + 48).unwrap())?; + } + } + + writeln!(f)?; + } + + let mut m: Map> = Map::new(self.map.width, self.map.height); + + for p in &self.path { + match p { + PathField::Belt { pos, dir } => match dir { + Direction::Up => m.set(pos.x, pos.y, Some((0, "↑"))), + Direction::Right => m.set(pos.x, pos.y, Some((0, "→"))), + Direction::Down => m.set(pos.x, pos.y, Some((0, "↓"))), + Direction::Left => m.set(pos.x, pos.y, Some((0, "←"))), + }, + PathField::Underground { pos, dir, len } => { + match dir { + Direction::Up => m.set(pos.x, pos.y, Some((0, "↟"))), + Direction::Right => m.set(pos.x, pos.y, Some((0, "↠"))), + Direction::Down => m.set(pos.x, pos.y, Some((0, "↡"))), + Direction::Left => m.set(pos.x, pos.y, Some((0, "↞"))), + }; + let end_pos = self.pos_in_direction(pos, dir, *len as usize).unwrap(); + match dir { + Direction::Up => m.set(end_pos.x, end_pos.y, Some((0, "↥"))), + Direction::Right => m.set(end_pos.x, end_pos.y, Some((0, "↦"))), + Direction::Down => m.set(end_pos.x, end_pos.y, Some((0, "↧"))), + Direction::Left => m.set(end_pos.x, end_pos.y, Some((0, "↤"))), + }; + } + } + } + + // Print body + + for y in 0..self.map.height { + write!(f, "{:1$}", y, height_digits as usize)?; + + for x in 0..self.map.width { + if let Some((i, c)) = m.get(x, y) { + write!(f, "{}", c.color(COLORS[*i]))?; + } else if self.map.get(x, y).blocked { + write!(f, "#")?; + } else if x % 8 == 0 || y % 8 == 0 { + write!(f, "∙")?; + } else { + write!(f, " ")?; + } + } + + writeln!(f)?; + } + + Ok(()) + } +} diff --git a/src/belt_finding/mod.rs b/src/belt_finding/mod.rs index 7810e76..52c0b39 100644 --- a/src/belt_finding/mod.rs +++ b/src/belt_finding/mod.rs @@ -6,7 +6,9 @@ use colored::{Color, Colorize}; use std::fmt::{write, Display}; use std::io::Write; -#[derive(Clone, Copy)] +pub mod brute_force; + +#[derive(Clone, Copy, PartialEq, Eq)] pub enum Direction { Up, Right, @@ -14,6 +16,48 @@ pub enum Direction { Left, } +impl Direction { + fn vertical(&self) -> bool { + match self { + Direction::Up => true, + Direction::Right => false, + Direction::Down => true, + Direction::Left => false, + } + } + + fn horizontal(&self) -> bool { + !self.vertical() + } + + fn reverse(&self) -> Self { + match self { + Direction::Up => Direction::Down, + Direction::Right => Direction::Left, + Direction::Down => Direction::Up, + Direction::Left => Direction::Right, + } + } + + fn clockwise(&self) -> Self { + match self { + Direction::Up => Direction::Right, + Direction::Right => Direction::Down, + Direction::Down => Direction::Left, + Direction::Left => Direction::Up, + } + } + + fn counter_clockwise(&self) -> Self { + match self { + Direction::Up => Direction::Left, + Direction::Right => Direction::Up, + Direction::Down => Direction::Right, + Direction::Left => Direction::Down, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Position { pub x: usize, @@ -156,7 +200,7 @@ impl Display for Problem { write!(f, "{}", "↑".color(COLORS[i]))?; } } else if self.map.get(x, y).blocked { - write!(f, "{}", "#")?; + write!(f, "#")?; } else if x % 8 == 0 || y % 8 == 0 { write!(f, "∙")?; } else { diff --git a/src/lib.rs b/src/lib.rs index da96512..4f59bc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(slice_first_last_chunk)] + pub mod belt_finding; pub mod blueprint; pub mod graph;