diff --git a/examples/solve_belt.rs b/examples/solve_belt.rs index 7ab99cd..b7ab3f7 100644 --- a/examples/solve_belt.rs +++ b/examples/solve_belt.rs @@ -1,57 +1,55 @@ -use factorio_blueprint::belt_finding::{common::Position, Problem}; +use clap::{Parser, ValueEnum}; +use factorio_blueprint::belt_finding::{conflict_avoidance::ConflictAvoidance, problems, Problem}; + +#[derive(ValueEnum, Clone)] +enum Mode { + Solve, + ConflictAvoidance, +} + +#[derive(ValueEnum, Clone)] +enum ProblemCase { + Level1, + Level2, +} + +impl ProblemCase { + fn get_problem(&self) -> Problem { + match self { + ProblemCase::Level1 => problems::belt_madness_level_1(), + ProblemCase::Level2 => problems::belt_madness_level_2(), + } + } +} + +#[derive(Parser)] +struct Args { + #[arg(value_enum, default_value = "level1")] + problem: ProblemCase, + #[arg(value_enum, default_value = "solve")] + mode: Mode, +} fn main() { - let mut p = Problem::new(17, 13); + let args = Args::parse(); - p.set_blocked(0, 3, true); + let mut p = args.problem.get_problem(); - p.set_blocked(1, 4, true); - p.set_blocked(2, 4, true); - p.set_blocked(1, 5, true); - p.set_blocked(2, 5, true); - - p.set_blocked(1, 7, true); - p.set_blocked(2, 7, true); - p.set_blocked(1, 8, true); - p.set_blocked(2, 8, true); - - p.set_blocked(0, 9, true); - - p.set_blocked(16, 3, true); - - p.set_blocked(14, 4, true); - p.set_blocked(15, 4, true); - p.set_blocked(14, 5, true); - p.set_blocked(15, 5, true); - - p.set_blocked(14, 7, true); - p.set_blocked(15, 7, true); - p.set_blocked(14, 8, true); - p.set_blocked(15, 8, true); - - p.set_blocked(16, 9, true); - - p.add_connection(Position::new(3, 7), Position::new(13, 4)); - p.add_connection(Position::new(3, 8), Position::new(13, 5)); - - p.add_connection(Position::new(3, 4), Position::new(13, 8)); - p.add_connection(Position::new(3, 5), Position::new(13, 7)); - - // p.set_blocked(8, 12, true); - // p.set_blocked(8, 11, true); - // p.set_blocked(8, 10, true); - // p.set_blocked(8, 9, true); - // p.set_blocked(8, 8, true); - // p.set_blocked(8, 7, true); - // p.set_blocked(8, 6, true); - // p.set_blocked(8, 5, true); - // p.set_blocked(8, 4, true); - // p.set_blocked(8, 3, true); - // p.set_blocked(8, 2, true); - // p.set_blocked(8, 1, true); - // p.set_blocked(8, 0, true); - - println!("{}", p); - p.find_path(); - println!("{}", p); + match args.mode { + Mode::Solve => { + println!("{}", p); + p.find_path(); + println!("{}", p); + } + Mode::ConflictAvoidance => { + println!("{}", p); + p.find_path(); + println!("{}", p); + let mut c = ConflictAvoidance::new(p); + println!("{}", c); + while c.remove_conflict() { + println!("{}", c) + } + } + } } diff --git a/src/belt_finding/common.rs b/src/belt_finding/common.rs index e8b1bc9..782303d 100644 --- a/src/belt_finding/common.rs +++ b/src/belt_finding/common.rs @@ -7,7 +7,7 @@ pub enum Direction { } impl Direction { - pub fn from_neghbors(pos: &Position, neighbor: &Position) -> Self { + pub fn from_neighbors(pos: &Position, neighbor: &Position) -> Self { let x_diff = pos.x as i64 - neighbor.x as i64; let y_diff = pos.y as i64 - neighbor.y as i64; @@ -143,4 +143,24 @@ impl PathField { .map(|p| (p, *dir)), } } + + pub fn offset(&self, offset: (i32, i32)) -> Self { + match self { + PathField::Belt { pos, dir } => PathField::Belt { + pos: Position::new( + (pos.x as i32 + offset.0) as usize, + (pos.y as i32 + offset.1) as usize, + ), + dir: *dir, + }, + PathField::Underground { pos, dir, len } => PathField::Underground { + pos: Position::new( + (pos.x as i32 + offset.0) as usize, + (pos.y as i32 + offset.1) as usize, + ), + dir: *dir, + len: *len, + }, + } + } } diff --git a/src/belt_finding/conflict_avoidance.rs b/src/belt_finding/conflict_avoidance.rs new file mode 100644 index 0000000..f45564e --- /dev/null +++ b/src/belt_finding/conflict_avoidance.rs @@ -0,0 +1,335 @@ +use std::fmt::Display; + +use colored::Colorize; + +use crate::{belt_finding::brute_force::BruteforceBuilder, misc::Map}; + +use super::{ + common::{Dimension, Direction, PathField, Position}, + Problem, COLORS, +}; + +#[derive(Default)] +struct Field { + blocked: bool, +} + +pub struct ConflictAvoidance { + map: Map, + belts: Vec>, +} + +impl ConflictAvoidance { + pub fn new(problem: Problem) -> Self { + let mut map: Map = Map::new(problem.map.width, problem.map.height); + for x in 0..problem.map.width { + for y in 0..problem.map.height { + map.get_mut(x, y).blocked = problem.map.get(x, y).blocked; + } + } + let mut belts = Vec::new(); + for i in 0..problem.path.len() { + let mut p = Vec::new(); + + for j in 0..problem.path[i].len() - 1 { + p.push(PathField::Belt { + pos: problem.path[i][j], + dir: Direction::from_neighbors(&problem.path[i][j], &problem.path[i][j + 1]), + }); + } + + p.push(PathField::Belt { + pos: *problem.path[i].last().unwrap(), + dir: *p.last().unwrap().dir(), + }); + + belts.push(p); + } + + Self { map, belts } + } + + pub fn remove_conflict(&mut self) -> bool { + let mut conflicts: Map = Map::new(self.map.width, self.map.height); + + for x in 0..self.map.width { + for y in 0..self.map.height { + if self.map.get(x, y).blocked { + *conflicts.get_mut(x, y) += 1; + } + } + } + + for path in &self.belts { + for p in path { + match p { + PathField::Belt { pos, dir: _ } => *conflicts.get_mut(pos.x, pos.y) += 1, + PathField::Underground { pos, dir, len } => { + *conflicts.get_mut(pos.x, pos.y) += 1; + let end = pos + .in_direction( + dir, + *len as usize, + &Dimension { + width: self.map.width, + height: self.map.height, + }, + ) + .unwrap(); + *conflicts.get_mut(end.x, end.y) += 1; + } + } + } + } + + for y in 0..self.map.height { + for x in 0..self.map.width { + if *conflicts.get(x, y) > 1 { + print!("#"); + } else { + print!(" "); + } + } + println!(); + } + + let mut c = None; + for y in 0..self.map.height { + for x in 0..self.map.width { + if *conflicts.get(x, y) > 1 { + c = Some((x, y)); + break; + } + } + } + + let (cx, cy) = match c { + Some(c) => c, + None => { + return false; + } + }; + + let mut xoffset = 0; + let mut yoffset = 0; + + loop { + xoffset += 1; + yoffset += 1; + + let xrange = cx..=cx; + let yrange = cy..=cy; + + let xrange = xrange.start().saturating_sub(xoffset) + ..=usize::min(xrange.end() + xoffset, self.map.width - 1); + let yrange = yrange.start().saturating_sub(yoffset) + ..=usize::min(yrange.end() + yoffset, self.map.height - 1); + + println!("x: {:?}, y: {:?}", xrange, yrange); + + let xsize = xrange.end() - xrange.start() + 1; + let ysize = yrange.end() - yrange.start() + 1; + + // dbg!(xsize, ysize); + + let mut b = BruteforceBuilder::new(xsize + 2, ysize + 2); + b.set_blocked_range(0, 0, xsize + 1, 0, true); + b.set_blocked_range(0, ysize + 1, xsize + 1, ysize + 1, true); + b.set_blocked_range(0, 0, 0, ysize + 1, true); + b.set_blocked_range(xsize + 1, 0, xsize + 1, ysize + 1, true); + + for x in 0..xsize { + for y in 0..ysize { + b.set_blocked( + x + 1, + y + 1, + self.map.get(xrange.start() + x, yrange.start() + y).blocked, + ); + } + } + + let mut mapping = Vec::new(); + + for (i, path) in self.belts.iter().enumerate() { + let s = path + .iter() + .map(|p| p.pos()) + .position(|p| xrange.contains(&p.x) && yrange.contains(&p.y)) + .map(|i| i.saturating_sub(1)); + let e = path + .iter() + .map(|p| p.pos()) + .rev() + .position(|p| xrange.contains(&p.x) && yrange.contains(&p.y)) + .map(|i| usize::min(path.len() - 1, path.len() - i)); + + if let Some((start, end)) = s.zip(e) { + // dbg!(start, end); + mapping.push((i, start, end)); + + // dbg!(path[start], path[end]); + + let (start_pos, start_dir) = path[start] + .end_pos(&Dimension { + width: self.map.width, + height: self.map.height, + }) + .unwrap(); + let end_pos = path[end].pos(); + let end_dir = path[end].dir(); + + // dbg!(start_pos, end_pos); + + b.add_path( + ( + Position::new( + start_pos.x - (xrange.start() - 1), + start_pos.y - (yrange.start() - 1), + ), + start_dir, + ), + ( + Position::new( + end_pos.x - (xrange.start() - 1), + end_pos.y - (yrange.start() - 1), + ), + *end_dir, + ), + ); + } + } + + println!("{:?}", mapping); + + let mut b = b.build(); + + println!("{}", b); + + let mut min_cost = f64::INFINITY; + let mut solutions = Vec::new(); + + while b.next_finish_state() { + // println!("{}", b); + let c = b.cost(); + if c < min_cost { + min_cost = c; + solutions = b.get_paths(); + } + } + + println!("{}", b.solution_count()); + + if b.solution_count() > 0 { + for (p, (index, start, end)) in solutions.iter().zip(mapping) { + let mut t = Vec::new(); + + t.extend_from_slice(&self.belts[index][0..=start]); + t.extend(p[1..].iter().map(|p| { + p.offset(((*xrange.start() as i32) - 1, (*yrange.start() as i32) - 1)) + })); + t.extend_from_slice(&self.belts[index][end..]); + // println!("{:?}", &t); + // println!("{:?}", &self.belts[index]); + + self.belts[index] = t; + } + + return true; + } + } + } + + pub fn remove_all_conflicts(&mut self) { + while self.remove_conflict() {} + } +} + +impl Display for ConflictAvoidance { + 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 (i, problem) in self.belts.iter().enumerate() { + for p in problem { + match p { + PathField::Belt { pos, dir } => match dir { + Direction::Up => m.set(pos.x, pos.y, Some((i, "↑"))), + Direction::Right => m.set(pos.x, pos.y, Some((i, "→"))), + Direction::Down => m.set(pos.x, pos.y, Some((i, "↓"))), + Direction::Left => m.set(pos.x, pos.y, Some((i, "←"))), + }, + PathField::Underground { pos, dir, len } => { + match dir { + Direction::Up => m.set(pos.x, pos.y, Some((i, "↟"))), + Direction::Right => m.set(pos.x, pos.y, Some((i, "↠"))), + Direction::Down => m.set(pos.x, pos.y, Some((i, "↡"))), + Direction::Left => m.set(pos.x, pos.y, Some((i, "↞"))), + }; + let end_pos = pos + .in_direction( + dir, + *len as usize, + &Dimension { + width: self.map.width, + height: self.map.height, + }, + ) + .unwrap(); + match dir { + Direction::Up => m.set(end_pos.x, end_pos.y, Some((i, "↥"))), + Direction::Right => m.set(end_pos.x, end_pos.y, Some((i, "↦"))), + Direction::Down => m.set(end_pos.x, end_pos.y, Some((i, "↧"))), + Direction::Left => m.set(end_pos.x, end_pos.y, Some((i, "↤"))), + }; + } + } + } + let last_pos = problem.last().unwrap().pos(); + m.set(last_pos.x, last_pos.y, Some((i, "T"))); + } + + // 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 9cc61d9..d634e60 100644 --- a/src/belt_finding/mod.rs +++ b/src/belt_finding/mod.rs @@ -10,6 +10,7 @@ use self::common::Position; pub mod brute_force; pub mod common; +pub mod conflict_avoidance; #[derive(Default, Clone, Copy)] pub struct Field { @@ -216,137 +217,78 @@ impl Problem { self.path[i] = p; } } - - let mut conflicts: Map = Map::new(self.map.width, self.map.height); - - for x in 0..self.map.width { - for y in 0..self.map.height { - if self.map.get(x, y).blocked { - *conflicts.get_mut(x, y) += 1; - } - } - } - - for path in &self.path { - for pos in path { - *conflicts.get_mut(pos.x, pos.y) += 1; - } - } - - for y in 0..self.map.height { - for x in 0..self.map.width { - if *conflicts.get(x, y) > 1 { - print!("#"); - } else { - print!(" "); - } - } - println!(); - } - - println!("{self}"); - - loop { - let mut c = None; - for y in 0..self.map.height { - for x in 0..self.map.width { - if *conflicts.get(x, y) > 1 { - c = Some((x, y)); - break; - } - } - } - - dbg!(c); - - let xoffset = 2; - let yoffset = 2; - - if let Some((cx, cy)) = c { - *conflicts.get_mut(cx, cy) = 1; - - let xrange = cx..=cx; - let yrange = cy..=cy; - - let xrange = xrange.start().saturating_sub(xoffset) - ..=usize::min(xrange.end() + xoffset, self.map.width - 1); - let yrange = yrange.start().saturating_sub(yoffset) - ..=usize::min(yrange.end() + yoffset, self.map.height - 1); - // dbg!(&xrange, &yrange); - - let xsize = xrange.end() - xrange.start() + 1; - let ysize = yrange.end() - yrange.start() + 1; - - // dbg!(xsize, ysize); - - let mut b = BruteforceBuilder::new(xsize + 2, ysize + 2); - b.set_blocked_range(0, 0, xsize + 1, 0, true); - b.set_blocked_range(0, ysize + 1, xsize + 1, ysize + 1, true); - b.set_blocked_range(0, 0, 0, ysize + 1, true); - b.set_blocked_range(xsize + 1, 0, xsize + 1, ysize + 1, true); - - for x in 0..xsize { - for y in 0..ysize { - b.set_blocked( - x + 1, - y + 1, - self.map.get(xrange.start() + x, yrange.start() + y).blocked, - ); - } - } - - for path in &self.path { - let s = path - .iter() - .position(|p| xrange.contains(&p.x) && yrange.contains(&p.y)) - .map(|i| i.saturating_sub(1)); - let e = path - .iter() - .rev() - .position(|p| xrange.contains(&p.x) && yrange.contains(&p.y)) - .map(|i| usize::min(path.len() - 1, path.len() - i)); - - if let Some((start, end)) = s.zip(e) { - // dbg!(start, end); - - let start_pos = path[start]; - let start_dir = Direction::from_neghbors(&path[start], &path[start + 1]); - let end_pos = path[end]; - let end_dir = Direction::from_neghbors(&path[end - 1], &path[end]); - - // dbg!(start_pos, end_pos); - - b.add_path( - ( - Position::new( - start_pos.x - (xrange.start() - 1), - start_pos.y - (yrange.start() - 1), - ), - start_dir, - ), - ( - Position::new( - end_pos.x - (xrange.start() - 1), - end_pos.y - (yrange.start() - 1), - ), - end_dir, - ), - ); - } - } - - let mut b = b.build(); - - println!("{}", b); - - while b.next_finish_state() { - println!("{}", b); - } - - println!("{}", b.solution_count()); - } else { - break; - } - } + } +} + +pub mod problems { + use super::{common::Position, Problem}; + + pub fn belt_madness_level_1() -> Problem { + let mut p = Problem::new(17, 13); + + p.set_blocked(0, 3, true); + + p.set_blocked(1, 4, true); + p.set_blocked(2, 4, true); + p.set_blocked(1, 5, true); + p.set_blocked(2, 5, true); + + p.set_blocked(1, 7, true); + p.set_blocked(2, 7, true); + p.set_blocked(1, 8, true); + p.set_blocked(2, 8, true); + + p.set_blocked(0, 9, true); + + p.set_blocked(16, 3, true); + + p.set_blocked(14, 4, true); + p.set_blocked(15, 4, true); + p.set_blocked(14, 5, true); + p.set_blocked(15, 5, true); + + p.set_blocked(14, 7, true); + p.set_blocked(15, 7, true); + p.set_blocked(14, 8, true); + p.set_blocked(15, 8, true); + + p.set_blocked(16, 9, true); + + p.add_connection(Position::new(3, 7), Position::new(13, 4)); + p.add_connection(Position::new(3, 8), Position::new(13, 5)); + + p.add_connection(Position::new(3, 4), Position::new(13, 8)); + p.add_connection(Position::new(3, 5), Position::new(13, 7)); + + p + } + + pub fn belt_madness_level_2() -> Problem { + let mut p = Problem::new(33, 13); + + p.set_blocked_range(1, 3, 2, 5, true); + p.set_blocked_range(1, 7, 2, 9, true); + + p.set_blocked(0, 3, true); + p.set_blocked(0, 8, true); + + p.set_blocked_range(10, 0, 21, 2, true); + p.set_blocked_range(10, 5, 21, 7, true); + p.set_blocked_range(10, 10, 21, 12, true); + + p.set_blocked_range(30, 3, 31, 5, true); + p.set_blocked_range(30, 7, 31, 9, true); + p.set_blocked(32, 3, true); + p.set_blocked(32, 9, true); + + p.add_connection(Position::new(3, 3), Position::new(29, 7)); + p.add_connection(Position::new(3, 4), Position::new(29, 9)); + p.add_connection(Position::new(3, 5), Position::new(29, 8)); + + p.add_connection(Position::new(3, 7), Position::new(29, 3)); + p.add_connection(Position::new(3, 8), Position::new(29, 5)); + p.add_connection(Position::new(3, 9), Position::new(29, 4)); + + p } }