Refactor into different crates

This commit is contained in:
hal8174 2025-01-18 17:30:55 +01:00
parent 94473c64e0
commit dfdeae5638
82 changed files with 624 additions and 647 deletions

View file

@ -0,0 +1,31 @@
[package]
name = "factorio-pathfinding"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
criterion = "0.3"
[[bench]]
name = "bruteforce"
harness = false
[dependencies]
factorio-core = { path = "../factorio-core" }
factorio-blueprint = { path = "../factorio-blueprint" }
base64 = "0.21.5"
bon = "3.0.2"
clap = { version = "4.4.8", features = ["derive"] }
flate2 = "1.0.28"
image = "0.25.2"
miette = { version = "7.2.0", features = ["fancy"] }
proptest = "1.5.0"
proptest-derive = "0.5.0"
rand = { version = "0.8.5", features = ["small_rng"] }
serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108"
serde_yaml = "0.9.34"
termcolor = "1.4.1"

View file

@ -0,0 +1,33 @@
use criterion::{criterion_group, criterion_main, Criterion};
use factorio_pathfinding::belt_finding::brute_force::problems::{mid, simple, snake};
macro_rules! bench_bruteforce {
($b:ident; $i:ident) => {
$b.bench_function(stringify!($i), |b| {
let p = $i();
b.iter(|| {
let mut b = p.clone();
while b.next_finish_state(None) {}
b.solution_count()
});
})
};
($b:ident; $i:ident $($is:ident )+) => {
bench_bruteforce!($b; $i);
bench_bruteforce!($b; $($is)+);
};
}
fn brute_force(c: &mut Criterion) {
bench_bruteforce!(c; simple mid snake);
}
criterion_group!(benches, brute_force);
criterion_main!(benches);

View file

@ -0,0 +1,91 @@
use clap::{Parser, ValueEnum};
use factorio_core::visualize::Visualize;
use factorio_pathfinding::belt_finding::brute_force::{problems, Bruteforce};
use std::io;
#[derive(ValueEnum, Clone)]
enum Mode {
Solutions,
Step,
Statistics,
}
#[derive(ValueEnum, Clone)]
enum Problem {
Simple,
Mid,
Snake,
Weaving,
Debug,
}
impl Problem {
fn get_problem(&self) -> Bruteforce {
match self {
Problem::Simple => problems::simple(),
Problem::Mid => problems::mid(),
Problem::Snake => problems::snake(),
Problem::Weaving => problems::weaving(),
Problem::Debug => problems::debug(),
}
}
}
#[derive(Parser)]
struct Args {
#[arg(value_enum, default_value = "simple")]
problem: Problem,
#[arg(value_enum, default_value = "solutions")]
mode: Mode,
}
fn main() {
let args = Args::parse();
let mut b = args.problem.get_problem();
b.print_visualization();
match args.mode {
Mode::Solutions => {
while b.next_finish_state(None) {
println!("{}\n{}\n{}", b.count(), b.solution_count(), b.cost());
b.print_visualization();
}
println!("Solutions: {}\nStates: {}", b.solution_count(), b.count());
}
Mode::Step => {
while b.next_state() {
b.print_visualization();
let mut s = String::new();
let _ = io::stdin().read_line(&mut s);
}
println!("Solutions: {}\nStates: {}", b.solution_count(), b.count());
}
Mode::Statistics => {
while b.next_finish_state(None) {}
println!("Solutions: {}\nStates: {}", b.solution_count(), b.count());
}
}
// println!(
// "{}\n{}\n{}\n{}",
// b.count(),
// b.solution_count(),
// b.modify_pointer(),
// b
// );
// for i in 0..20 {
// b.next_state();
// println!(
// "{}\n{}\n{}\n{}",
// b.count(),
// b.solution_count(),
// b.modify_pointer(),
// b
// );
// }
}

View file

@ -0,0 +1,24 @@
use clap::Parser;
use factorio_blueprint::{decode, BlueprintString};
use std::path::PathBuf;
#[derive(Debug, Parser)]
struct Args {
blueprint: PathBuf,
}
fn main() {
let args = Args::parse();
let s = std::fs::read_to_string(args.blueprint).unwrap();
let raw = decode(s.trim_end());
// println!("{}", &raw);
let bp = serde_json::from_str::<BlueprintString>(&raw).unwrap();
dbg!(&bp);
// let reencode = encode(&raw);
// println!("{}", &reencode);
}

View file

@ -0,0 +1,40 @@
use factorio_pathfinding::priority_queue::{fibonacci_heap::FibonacciHeap, PriorityQueue};
use std::fmt::Debug;
fn test_loop<P>()
where
P: PriorityQueue<u16> + Debug,
{
let mut input = String::new();
let mut p = P::new();
let mut handles = Vec::new();
loop {
input.clear();
let _ = std::io::stdin().read_line(&mut input);
let (cmd, arg) = input.trim().split_once(' ').unwrap_or((input.trim(), ""));
// dbg!(cmd, arg);
match cmd {
"i" => handles.push(p.insert(arg.parse::<u16>().unwrap())),
"m" => println!("{:?}", p.pop_min()),
"d" => {
let (a, b) = arg.split_once(' ').unwrap();
let h = &handles[a.parse::<usize>().unwrap()];
let n = b.parse::<u16>().unwrap();
p.decrease_key(h, |f| *f = n);
}
"p" => {
dbg!(&p);
}
_ => println!("Unknown command."),
}
}
}
fn main() {
test_loop::<FibonacciHeap<u16>>()
}

View file

@ -0,0 +1,44 @@
size:
x: 15
y: 15
blocks:
- size:
x: 3
y: 2
input:
- offset:
x: 1
y: 1
dir: Up
output:
- offset:
x: 1
y: 0
dir: Up
- size:
x: 5
y: 2
input:
output:
- offset:
x: 1
y: 1
dir: Down
- size:
x: 5
y: 7
input:
- offset:
x: 0
y: 1
dir: Right
output:
connections:
- startblock: 1
startpoint: 0
endblock: 0
endpoint: 0
- startblock: 0
startpoint: 0
endblock: 2
endpoint: 0

View file

@ -0,0 +1,61 @@
size:
x: 30
y: 30
blocks:
- size:
x: 3
y: 2
input:
- offset:
x: 1
y: 1
dir: Up
output:
- offset:
x: 1
y: 0
dir: Up
- size:
x: 5
y: 2
input:
output:
- offset:
x: 1
y: 1
dir: Down
- size:
x: 5
y: 7
input:
- offset:
x: 0
y: 1
dir: Right
output:
- size:
x: 5
y: 5
input:
- offset:
x: 0
y: 1
dir: Right
output:
- offset:
x: 0
y: 3
dir: Left
connections:
- startblock: 1
startpoint: 0
endblock: 0
endpoint: 0
- startblock: 0
startpoint: 0
endblock: 3
endpoint: 0
- startblock: 3
startpoint: 0
endblock: 2
endpoint: 0

View file

@ -0,0 +1,115 @@
size:
x: 50
y: 50
blocks:
- size:
x: 3
y: 2
input:
- offset:
x: 1
y: 1
dir: Up
output:
- offset:
x: 1
y: 0
dir: Up
- size:
x: 5
y: 2
input:
output:
- offset:
x: 1
y: 1
dir: Down
- size:
x: 5
y: 7
input:
- offset:
x: 0
y: 1
dir: Right
output:
- size:
x: 5
y: 5
input:
- offset:
x: 0
y: 1
dir: Right
output:
- offset:
x: 0
y: 3
dir: Left
- size:
x: 10
y: 10
input:
- offset:
x: 0
y: 1
dir: Right
- offset:
x: 0
y: 3
dir: Right
output:
- offset:
x: 9
y: 1
dir: Right
- offset:
x: 9
y: 3
dir: Right
- size:
x: 10
y: 5
input:
- offset:
x: 0
y: 1
dir: Right
- offset:
x: 0
y: 3
dir: Right
output:
- offset:
x: 9
y: 1
dir: Right
- offset:
x: 9
y: 3
dir: Right
connections:
- startblock: 1
startpoint: 0
endblock: 0
endpoint: 0
- startblock: 0
startpoint: 0
endblock: 3
endpoint: 0
- startblock: 3
startpoint: 0
endblock: 4
endpoint: 0
- startblock: 4
startpoint: 0
endblock: 5
endpoint: 0
- startblock: 4
startpoint: 1
endblock: 5
endpoint: 1
- startblock: 5
startpoint: 0
endblock: 2
endpoint: 0

View file

@ -0,0 +1,817 @@
use factorio_core::{pathfield::PathField, prelude::*, visualize::Visualize};
use std::time::Instant;
use crate::misc::Map;
#[derive(Default, Debug, Clone)]
pub struct BruteforceField {
pub blocked: bool,
underground_vertical: bool,
underground_horizontal: bool,
}
static MAX_UNDERGROUND_LENGTH: u8 = 6;
#[derive(Clone, Debug)]
pub struct BruteforceBuilder {
map: Map<BruteforceField>,
path: Vec<[(Position, Direction); 2]>,
}
impl BruteforceBuilder {
pub fn new(width: usize, height: usize) -> Self {
Self {
map: Map::new(width, height),
path: Vec::new(),
}
}
pub fn add_path(&mut self, start: (Position, Direction), end: (Position, Direction)) {
self.path.push([start, end]);
}
pub fn set_blocked(&mut self, x: usize, y: usize, blocked: bool) {
self.map.get_mut(x, y).blocked = blocked;
}
pub fn get_blocked(&self, x: usize, y: usize) -> bool {
self.map.get(x, y).blocked
}
pub fn set_underground(&mut self, x: usize, y: usize, dir: Option<Direction>) {
match dir {
Some(d) => {
if d.vertical() {
self.map.get_mut(x, y).underground_vertical = true;
} else {
self.map.get_mut(x, y).underground_horizontal = true;
}
}
None => {
self.map.get_mut(x, y).underground_vertical = false;
self.map.get_mut(x, y).underground_horizontal = false;
}
}
}
pub fn set_blocked_range(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, blocked: bool) {
for x in x1..=x2 {
for y in y1..=y2 {
self.set_blocked(x, y, blocked);
}
}
}
pub fn build(self) -> Bruteforce {
// dbg!(&self);
let mut b = Bruteforce {
map: self.map,
problems: Vec::new(),
pointer_stack: vec![0],
solution_count: 0,
count: 0,
};
for [start, end] in self.path {
// b.map
// .get_mut(start.0.x as usize, start.0.y as usize)
// .blocked = true;
// b.map.get_mut(end.0.x as usize, end.0.y as usize).blocked = true;
b.problems.push(Problem {
path: vec![PathField::Belt {
pos: start.0,
dir: start.1,
}],
end_pos: end.0,
end_dir: end.1,
finished: false,
})
}
b
}
}
#[derive(Clone)]
struct Problem {
path: Vec<PathField>,
end_pos: Position,
end_dir: Direction,
finished: bool,
}
#[derive(Clone)]
pub struct Bruteforce {
map: Map<BruteforceField>,
problems: Vec<Problem>,
pointer_stack: Vec<usize>,
solution_count: u128,
count: u128,
}
impl Bruteforce {
pub fn modify_pointer(&self) -> usize {
self.pointer_stack
.get(self.pointer_stack.len().saturating_sub(2))
.copied()
.unwrap_or(0)
}
fn add_pointer(&self) -> usize {
self.pointer_stack.last().copied().unwrap_or(0)
}
/// Path field must be valid and inside bounds.
fn internal_apply_path_field(&mut self, i: usize, path_field: PathField) {
match &path_field {
PathField::Belt { pos, dir: _ } => {
self.map.get_mut(pos.x as usize, pos.y as usize).blocked = true
}
PathField::Underground { pos, dir, len } => {
self.map.get_mut(pos.x as usize, pos.y as usize).blocked = true;
for i in 0..=*len {
let mid_pos = pos.in_direction(dir, i as PositionType);
if dir.vertical() {
self.map
.get_mut(mid_pos.x as usize, mid_pos.y as usize)
.underground_vertical = true;
} else {
self.map
.get_mut(mid_pos.x as usize, mid_pos.y as usize)
.underground_horizontal = true;
}
}
let end_pos = pos.in_direction(dir, *len as PositionType);
self.map
.get_mut(end_pos.x as usize, end_pos.y as usize)
.blocked = true;
}
}
self.problems[i].path.push(path_field);
}
fn internal_remove_path_field(&mut self, i: usize) {
match self.problems[i].path.pop().unwrap() {
PathField::Belt { pos, dir: _ } => {
self.map.get_mut(pos.x as usize, pos.y as usize).blocked = false
}
PathField::Underground { pos, dir, len } => {
self.map.get_mut(pos.x as usize, pos.y as usize).blocked = false;
for i in 0..=len {
let mid_pos = pos.in_direction(&dir, i as PositionType);
if dir.vertical() {
self.map
.get_mut(mid_pos.x as usize, mid_pos.y as usize)
.underground_vertical = false;
} else {
self.map
.get_mut(mid_pos.x as usize, mid_pos.y as usize)
.underground_horizontal = false;
}
}
let end_pos = pos.in_direction(&dir, len.into());
self.map
.get_mut(end_pos.x as usize, end_pos.y as usize)
.blocked = false;
}
}
}
fn apply_path_field(&mut self, path_field: PathField) {
self.internal_apply_path_field(self.add_pointer(), path_field);
// set finished
let i = self.add_pointer();
// dbg!(i, self.check_finish(i));
self.problems[i].finished = self.check_finish(i);
// advance pointer
let current_pointer = self.add_pointer();
let mut pointer = current_pointer;
for i in 1..self.problems.len() {
if !self.problems[(current_pointer + i) % self.problems.len()].finished {
pointer = (current_pointer + i) % self.problems.len();
break;
}
}
self.pointer_stack.push(pointer);
}
fn remove_path_field(&mut self) {
self.internal_remove_path_field(self.modify_pointer());
// remove finish
let i = self.modify_pointer();
self.problems[i].finished = false;
// restore pointer
self.pointer_stack.pop();
}
fn modify_path_field(&mut self, path_field: PathField) {
let i = self.modify_pointer();
let last = *self.problems[i].path.last().unwrap();
match (last, &path_field) {
(PathField::Belt { pos: _, dir: _ }, PathField::Belt { pos: _, dir: _ }) => {}
(PathField::Belt { pos: _, dir: _ }, PathField::Underground { pos, dir, len }) => {
let end_pos = pos.in_direction(dir, *len as PositionType);
self.map
.get_mut(end_pos.x as usize, end_pos.y as usize)
.blocked = true;
for l in 0..=*len {
let p = pos.in_direction(dir, l as PositionType);
match dir.vertical() {
true => {
self.map
.get_mut(p.x as usize, p.y as usize)
.underground_vertical = true
}
false => {
self.map
.get_mut(p.x as usize, p.y as usize)
.underground_horizontal = true
}
}
}
}
(
PathField::Underground {
pos: _,
dir: _,
len: _,
},
PathField::Belt { pos: _, dir: _ },
) => {
unreachable!()
}
(
PathField::Underground {
pos,
dir,
len: last_len,
},
PathField::Underground {
pos: _,
dir: _,
len: new_len,
},
) => {
let last_end_pos = pos.in_direction(&dir, last_len as PositionType);
let new_end_pos = pos.in_direction(&dir, *new_len as PositionType);
self.map
.get_mut(last_end_pos.x as usize, last_end_pos.y as usize)
.blocked = false;
self.map
.get_mut(new_end_pos.x as usize, new_end_pos.y as usize)
.blocked = true;
match last_len < *new_len {
true => {
for l in last_len + 1..=*new_len {
let p = pos.in_direction(&dir, l as PositionType);
match dir.vertical() {
true => {
self.map
.get_mut(p.x as usize, p.y as usize)
.underground_vertical = true
}
false => {
self.map
.get_mut(p.x as usize, p.y as usize)
.underground_horizontal = true
}
}
}
}
false => unreachable!(),
}
}
}
*self.problems[i].path.last_mut().unwrap() = path_field;
// set finished
self.problems[i].finished = self.check_finish(i);
}
fn check_finish(&self, i: usize) -> bool {
self.problems[i].path.last().unwrap().end_pos()
== (self.problems[i].end_pos, self.problems[i].end_dir)
// match self.problems[i].path.last().unwrap() {
// PathField::Belt { pos, dir } => Some(pos.in_direction(dir, 1))
// .filter(|p| {
// p == &self.problems[i].end_pos && dir != &self.problems[i].end_dir.reverse()
// })
// .is_some(),
// PathField::Underground { pos, dir, len } => {
// Some(pos.in_direction(dir, *len as PositionType + 1))
// .filter(|p| {
// p == &self.problems[i].end_pos && dir != &self.problems[i].end_dir.reverse()
// })
// .is_some()
// }
// }
}
fn modify_underground(&mut self, pos: &Position, dir: &Direction, len: u8) -> bool {
if len >= MAX_UNDERGROUND_LENGTH {
return false;
}
if len == 2
&& match dir.vertical() {
true => {
self.map
.get(pos.x as usize, pos.y as usize)
.underground_vertical
}
false => {
self.map
.get(pos.x as usize, pos.y as usize)
.underground_horizontal
}
}
{
return false;
}
let binding = pos.in_direction(dir, len as PositionType);
let end_pos = match binding.in_range(
&Position::new(0, 0),
&Position::new(
self.map.width as PositionType,
self.map.height as PositionType,
),
) {
Some(t) => t,
None => return false,
};
if match dir.vertical() {
true => {
self.map
.get(end_pos.x as usize, end_pos.y as usize)
.underground_vertical
}
false => {
self.map
.get(end_pos.x as usize, end_pos.y as usize)
.underground_horizontal
}
} {
return false;
}
if !self.map.get(end_pos.x as usize, end_pos.y as usize).blocked {
self.modify_path_field(PathField::Underground {
pos: *pos,
dir: *dir,
len,
});
return true;
}
self.modify_underground(pos, dir, len + 1)
}
// fn modify_underground(&mut self, pos: &Position, dir: &Direction, len: u8) -> bool {
// dbg!(pos, dir, len);
// if match dir.vertical() {
// true => self.map.get(pos.x, pos.y).underground_vertical,
// false => self.map.get(pos.x, pos.y).underground_horizontal,
// } {
// return false;
// }
// for l in len..MAX_UNDERGROUND_LENGTH {
// if let Some(p) = self.pos_in_direction(pos, dir, l as usize) {
// dbg!(l, &p);
// if !self.map.get(p.x, p.y).blocked {
// if !(1..l)
// .filter_map(|i| self.pos_in_direction(pos, dir, i as usize))
// .any(|local_pos| !match dir.vertical() {
// true => self.map.get(local_pos.x, local_pos.y).underground_vertical,
// false => {
// self.map
// .get(local_pos.x, local_pos.y)
// .underground_horizontal
// }
// })
// {
// dbg!(true);
// self.modify_path_field(PathField::Underground {
// pos: *pos,
// dir: *dir,
// len: l,
// });
// return true;
// } else {
// return false;
// }
// }
// }
// }
// false
// }
fn modify_remove(&mut self) -> bool {
if let Some([second_last, last]) = self.modify_path().last_chunk().copied() {
match last {
PathField::Belt { pos, dir } => {
if second_last.dir() == &dir {
self.modify_path_field(PathField::Belt {
pos,
dir: second_last.dir().clockwise(),
});
if self.is_next_free(&pos, &second_last.dir().clockwise()) {
return true;
}
return self.modify_remove();
}
if second_last.dir().clockwise() == dir {
self.modify_path_field(PathField::Belt {
pos,
dir: second_last.dir().counter_clockwise(),
});
if self.is_next_free(&pos, &second_last.dir().counter_clockwise()) {
return true;
}
return self.modify_remove();
}
if second_last.dir().counter_clockwise() == dir
&& self.modify_underground(&pos, second_last.dir(), 2)
{
let (p, d) = self.modify_path().last().unwrap().end_pos();
if self.is_next_free(&p, &d) {
return true;
}
return self.modify_remove();
}
}
PathField::Underground { pos, dir, len } => {
if self.modify_underground(&pos, &dir, len + 1) {
let (p, d) = self.modify_path().last().unwrap().end_pos();
if self.is_next_free(&p, &d) {
return true;
}
return self.modify_remove();
}
}
}
self.remove_path_field();
self.modify_remove()
} else {
false
}
}
fn modify_path(&self) -> &Vec<PathField> {
&self.problems[self.modify_pointer()].path
}
fn add_path(&self) -> &Vec<PathField> {
&self.problems[self.add_pointer()].path
}
// fn modify_path_mut(&mut self) -> &mut Vec<PathField> {
// let i = self.modify_pointer();
// &mut self.problems[i].path
// }
fn is_next_free(&self, pos: &Position, dir: &Direction) -> bool {
let i = self.modify_pointer();
self.check_finish(i)
|| pos
.in_direction(dir, 1)
.in_range(
&Position::new(0, 0),
&Position::new(
self.map.width as PositionType,
self.map.height as PositionType,
),
)
.filter(|&p| {
!self.map.get(p.x as usize, p.y as usize).blocked
|| self.problems[i].end_pos == *p
})
.is_some()
}
// Add an Path elemeent
fn add(&mut self) -> bool {
let (pos, dir) = self.add_path().last().unwrap().end_pos();
if let Some(&p) = pos.in_direction(&dir, 1).in_range(
&Position::new(0, 0),
&Position::new(
self.map.width as PositionType,
self.map.height as PositionType,
),
) {
if !self.map.get(p.x as usize, p.y as usize).blocked {
self.apply_path_field(PathField::Belt { pos: p, dir });
return self.is_next_free(&p, &dir);
}
}
false
}
pub fn next_state(&mut self) -> bool {
self.count += 1;
if self.add() {
return true;
}
self.modify_remove()
}
pub fn next_finish_state(&mut self, timeout: Option<Instant>) -> bool {
while self.next_state() {
if self.count % 10000 == 0 && timeout.is_some_and(|t| t < std::time::Instant::now()) {
return false;
}
if self.problems.iter().all(|p| p.finished) {
self.solution_count += 1;
return true;
}
}
false
}
pub fn get_paths(&self) -> Vec<Vec<PathField>> {
self.problems.iter().map(|p| p.path.clone()).collect()
}
pub fn cost(&self) -> f64 {
self.problems
.iter()
.flat_map(|p| {
p.path.iter().map(|f| match f {
PathField::Belt { pos: _, dir: _ } => 1.5,
PathField::Underground {
pos: _,
dir: _,
len: _,
} => 17.5,
})
})
.sum()
}
pub fn count(&self) -> u128 {
self.count
}
pub fn solution_count(&self) -> u128 {
self.solution_count
}
}
impl Visualize for Bruteforce {
fn visualize(&self) -> factorio_core::visualize::Visualization {
let mut v = factorio_core::visualize::Visualization::new(Position::new(
self.map.width as i32,
self.map.height as i32,
));
for x in 0..self.map.width {
for y in 0..self.map.height {
if self.map.get(x, y).blocked {
v.add_symbol(
Position::new(x as i32, y as i32),
factorio_core::visualize::Symbol::Block,
Some(factorio_core::visualize::Color::white()),
None,
);
}
}
}
for (i, problem) in self.problems.iter().enumerate() {
for p in &problem.path {
match p {
PathField::Belt { pos, dir } => {
v.add_symbol(
*pos,
factorio_core::visualize::Symbol::Arrow(*dir),
Some(factorio_core::visualize::Color::index(i)),
None,
);
}
PathField::Underground { pos, dir, len } => {
v.add_symbol(
*pos,
factorio_core::visualize::Symbol::ArrowEnter(*dir),
Some(factorio_core::visualize::Color::index(i)),
None,
);
v.add_symbol(
pos.in_direction(dir, *len as i32),
factorio_core::visualize::Symbol::ArrowExit(*dir),
Some(factorio_core::visualize::Color::index(i)),
None,
);
}
}
}
v.add_symbol(
problem.end_pos,
factorio_core::visualize::Symbol::Char(match problem.finished {
true => 'T',
false => 't',
}),
Some(factorio_core::visualize::Color::index(i)),
None,
);
}
v
}
}
#[cfg(test)]
mod test {
use super::problems;
macro_rules! test_bruteforce {
($i:ident $x:expr) => {
#[test]
fn $i() {
let mut b = problems::$i();
while b.next_finish_state(None) {}
assert_eq!(b.solution_count(), $x);
}
};
($i:ident $x:expr; $($is:ident $xs:expr);+) => {
test_bruteforce!($i $x);
test_bruteforce!($($is $xs);+);
};
}
test_bruteforce!(simple 9; mid 308986; snake 5);
}
pub mod problems {
use super::*;
pub fn simple() -> Bruteforce {
let mut b = BruteforceBuilder::new(6, 8);
b.add_path(
(Position::new(1, 0), Direction::Down),
(Position::new(0, 0), Direction::Up),
);
b.set_blocked(1, 0, true);
b.add_path(
(Position::new(4, 0), Direction::Down),
(Position::new(1, 7), Direction::Up),
);
b.set_blocked(4, 0, true);
b.set_blocked_range(0, 2, 5, 5, true);
b.build()
}
pub fn mid() -> Bruteforce {
let mut b = BruteforceBuilder::new(6, 6);
b.add_path(
(Position::new(0, 0), Direction::Down),
(Position::new(5, 5), Direction::Down),
);
b.set_blocked(0, 0, true);
b.add_path(
(Position::new(0, 5), Direction::Up),
(Position::new(5, 0), Direction::Up),
);
b.set_blocked(0, 0, true);
b.build()
}
pub fn snake() -> Bruteforce {
let mut p = BruteforceBuilder::new(13, 3);
p.add_path(
(Position::new(0, 0), Direction::Right),
(Position::new(12, 0), Direction::Right),
);
p.add_path(
(Position::new(0, 1), Direction::Right),
(Position::new(12, 1), Direction::Right),
);
p.add_path(
(Position::new(0, 2), Direction::Right),
(Position::new(12, 2), Direction::Right),
);
p.set_blocked_range(3, 2, 10, 2, true);
p.build()
}
pub fn weaving() -> Bruteforce {
let mut b = BruteforceBuilder::new(14, 6);
b.add_path(
(Position::new(0, 0), Direction::Right),
(Position::new(13, 0), Direction::Right),
);
b.add_path(
(Position::new(0, 1), Direction::Right),
(Position::new(13, 1), Direction::Right),
);
b.add_path(
(Position::new(0, 2), Direction::Right),
(Position::new(13, 2), Direction::Right),
);
b.add_path(
(Position::new(0, 3), Direction::Right),
(Position::new(13, 3), Direction::Right),
);
b.add_path(
(Position::new(0, 4), Direction::Right),
(Position::new(13, 4), Direction::Right),
);
b.add_path(
(Position::new(0, 5), Direction::Right),
(Position::new(13, 5), Direction::Right),
);
// b.set_blocked_range(7, 2, 10, 2, true);
// b.set_blocked_range(7, 3, 10, 4, true);
b.set_blocked_range(3, 2, 10, 3, true);
b.set_blocked(2, 0, true);
b.set_blocked(11, 0, true);
b.set_blocked(2, 5, true);
b.set_blocked(11, 5, true);
b.build()
}
pub fn debug() -> Bruteforce {
let mut b = BruteforceBuilder::new(5, 10);
b.set_blocked_range(0, 0, 4, 0, true);
b.set_blocked_range(0, 9, 4, 9, true);
b.set_blocked_range(0, 0, 0, 9, true);
b.set_blocked_range(4, 0, 4, 9, true);
// b.set_blocked_range(3, 2, 3, 3, true);
// b.set_blocked_range(3, 5, 3, 6, true);
b.add_path(
(Position::new(0, 2), Direction::Right),
(Position::new(3, 2), Direction::Right),
);
b.add_path(
(Position::new(0, 3), Direction::Right),
(Position::new(3, 5), Direction::Right),
);
b.add_path(
(Position::new(0, 6), Direction::Right),
(Position::new(3, 6), Direction::Right),
);
b.add_path(
(Position::new(0, 7), Direction::Right),
(Position::new(3, 3), Direction::Right),
);
b.build()
}
}

View file

@ -0,0 +1,620 @@
use crate::{belt_finding::brute_force::BruteforceBuilder, misc::Map};
use factorio_blueprint::{belt::convert_to_blueprint, misc::Beltspeed, BlueprintEntity};
use factorio_core::{pathfield::PathField, prelude::*, visualize::Visualize};
use std::{
ops::RangeInclusive,
time::{Duration, Instant},
};
use super::Problem;
#[derive(Default)]
struct Field {
blocked: bool,
}
pub struct ConflictAvoidance {
map: Map<Field>,
belts: Vec<Vec<PathField>>,
range: Option<(RangeInclusive<i32>, RangeInclusive<i32>)>,
}
#[derive(Debug, PartialEq, Eq)]
struct Candidate {
min: Position,
max: Position,
}
struct BruteForceEntry {
path_id: usize,
start: usize,
end: usize,
path: Vec<PathField>,
}
impl Candidate {
fn new(min: Position, max: Position) -> Self {
Self { min, max }
}
fn area(&self) -> PositionType {
(self.max.x - self.min.x) * (self.max.y - self.min.y)
}
fn extend_range(&mut self, conflict_map: &Map<usize>) {
for x in self.min.x..=self.max.x {
if self.min.y > 0 && *conflict_map.get(x as usize, self.min.y as usize) > 1 {
self.min.y -= 1;
return self.extend_range(conflict_map);
}
if (self.max.y as usize) < conflict_map.height - 1
&& *conflict_map.get(x as usize, self.max.y as usize) > 1
{
self.max.y += 1;
return self.extend_range(conflict_map);
}
}
for y in self.min.y..=self.max.y {
if self.min.x > 0 && *conflict_map.get(self.min.x as usize, y as usize) > 1 {
self.min.x -= 1;
return self.extend_range(conflict_map);
}
if (self.max.x as usize) < conflict_map.width - 1
&& *conflict_map.get(self.max.x as usize, y as usize) > 1
{
self.max.x += 1;
return self.extend_range(conflict_map);
}
}
}
}
impl ConflictAvoidance {
pub fn new(problem: &Problem) -> Self {
let mut map: Map<Field> = 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() {
// dbg!(&problem.path[i]);
let mut p = Vec::new();
p.push(PathField::Belt {
pos: problem.start[i].0,
dir: problem.start[i].1,
});
for (pos, dir) in &problem.path[i][1..] {
let start = p.last().unwrap().end_pos();
let start = start.0.in_direction(&start.1, 1);
// dbg!(start, pos);
if &start == pos {
p.push(PathField::Belt {
pos: *pos,
dir: *dir,
});
} else {
p.push(PathField::Underground {
pos: start,
dir: *dir,
len: u8::max(
(start.x - pos.x).unsigned_abs() as u8,
(start.y - pos.y).unsigned_abs() as u8,
),
})
}
}
// p.push(PathField::Belt {
// pos: *problem.path[i].last().unwrap(),
// dir: problem.end[i].1,
// });
// p.push(PathField::Belt {
// pos: problem.end[i].0,
// dir: problem.end[i].1,
// });
belts.push(p);
}
Self {
map,
belts,
range: None,
}
}
pub fn get_paths(&self) -> &[Vec<PathField>] {
&self.belts
}
fn try_bruteforce(
&self,
candidate: &Candidate,
timeout: Option<Instant>,
) -> Option<Vec<BruteForceEntry>> {
let xrange = candidate.min.x as usize..=candidate.max.x as usize;
let yrange = candidate.min.y as usize..=candidate.max.y as usize;
// 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();
let offset = Position::new(*xrange.start() as i32 - 1, *yrange.start() as i32 - 1);
// dbg!(&xrange, &yrange);
for (i, path) in self.belts.iter().enumerate() {
// index of first PathField where the next position is in the area
let start_index = path
.iter()
.map(|p| {
let (pos, dir) = p.end_pos();
pos.in_direction(&dir, 1)
})
.position(|pos| {
xrange.contains(&(pos.x as usize)) && yrange.contains(&(pos.y as usize))
});
// index of last PathField that ends inside the area
let end_index = path
.iter()
.rev()
.map(|p| p.end_pos().0)
.position(|pos| {
xrange.contains(&(pos.x as usize)) && yrange.contains(&(pos.y as usize))
})
.map(|rev_i| path.len() - rev_i - 1);
if let Some((start_index, end_index)) = Option::zip(start_index, end_index) {
// dbg!(start_index, end_index, path[start_index], path[end_index]);
let (start_pos, start_dir) = path[start_index].end_pos();
let (end_pos, end_dir) = path[end_index].end_pos();
// edge cases with underground into and end
if let Some(PathField::Underground { pos, dir, len }) = path.get(end_index + 1) {
if xrange.contains(&(pos.x as usize)) && yrange.contains(&(pos.y as usize)) {
let p = *pos - offset;
// println!("Blocked {:?}", p);
if b.get_blocked(p.x as usize, p.y as usize) {
return None;
}
b.set_blocked(p.x as usize, p.y as usize, true);
}
for l in 1..*len {
let p = pos.in_direction(dir, l as i32);
if xrange.contains(&(p.x as usize)) && yrange.contains(&(p.y as usize)) {
let p = p - offset;
b.set_underground(p.x as usize, p.y as usize, Some(*dir));
}
}
}
if xrange.contains(&(start_pos.x as usize))
&& yrange.contains(&(start_pos.y as usize))
{
let p = start_pos - offset;
// println!("Blocked {:?}", p);
// if b.get_blocked(p.x as usize, p.y as usize) {
// return None;
// }
b.set_blocked(p.x as usize, p.y as usize, true);
}
if let PathField::Underground { pos, dir, len } = path[start_index] {
for l in 1..len {
let p = pos.in_direction(&dir, l as i32);
if xrange.contains(&(p.x as usize)) && yrange.contains(&(p.y as usize)) {
let p = p - offset;
b.set_underground(p.x as usize, p.y as usize, Some(dir));
}
}
}
let start_pos_offset = start_pos - offset;
b.add_path((start_pos_offset, start_dir), (end_pos - offset, end_dir));
mapping.push((i, start_index, end_index));
} else if let Some(end_index) = end_index {
if let Some(PathField::Underground { pos, dir, len }) = path.get(end_index) {
let p = pos.in_direction(dir, *len as i32);
let p = p - offset;
b.set_blocked(p.x as usize, p.y as usize, true);
}
}
}
// println!("{:?}", mapping);
let mut b = b.build();
// self.print_visualization();
let mut min_cost = f64::INFINITY;
let mut solutions = Vec::new();
while b.next_finish_state(timeout) {
// println!("{}", b);
// b.print();
let c = b.cost();
if c < min_cost {
min_cost = c;
solutions = b.get_paths();
}
}
// b.print_visualization();
if b.solution_count() == 0 {
return None;
}
Some(
mapping
.into_iter()
.zip(solutions)
.map(|((path_id, start, end), path)| BruteForceEntry {
path_id,
start,
end,
path,
})
.collect(),
)
}
pub fn remove_conflict(&mut self, timeout: Option<Instant>) -> bool {
let mut conflicts: Map<usize> = 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[1..] {
match p {
PathField::Belt { pos, dir: _ } => {
*conflicts.get_mut(pos.x as usize, pos.y as usize) += 1
}
PathField::Underground { pos, dir, len } => {
*conflicts.get_mut(pos.x as usize, pos.y as usize) += 1;
let end = pos.in_direction(dir, *len as PositionType);
*conflicts.get_mut(end.x as usize, end.y as usize) += 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!();
// }
// self.print();
let mut candidates = Vec::new();
for y in 0..self.map.height {
for x in 0..self.map.width {
if *conflicts.get(x, y) > 1 {
let mut candidate = Candidate::new(
Position::new(x as PositionType, y as PositionType),
Position::new(x as PositionType, y as PositionType),
);
candidate.extend_range(&conflicts);
if !candidates.iter().any(|c| c == &candidate) {
candidates.push(candidate);
}
}
}
}
if candidates.is_empty() {
return false;
}
// dbg!(&candidates);
while timeout.is_none_or(|t| t > Instant::now()) {
candidates.sort_by_key(|c| -c.area());
// dbg!(&candidates);
let c = match candidates.pop() {
Some(c) => c,
None => {
return false;
}
};
self.range = Some((c.min.x..=c.max.x, c.min.y..=c.max.y));
let result = self.try_bruteforce(&c, timeout);
// dbg!(&solutions);
// println!("{}", b.solution_count());
let xrange = c.min.x as usize..=c.max.x as usize;
let yrange = c.min.y as usize..=c.max.y as usize;
let offset = Position::new(*xrange.start() as i32 - 1, *yrange.start() as i32 - 1);
if let Some(r) = result {
// println!("Here");
for BruteForceEntry {
path_id,
start,
end,
path,
} in r
{
let mut t = Vec::new();
// println!("{:?}", p);
t.extend_from_slice(&self.belts[path_id][0..=start]);
t.extend(path[1..].iter().map(|p| p.offset(&offset)));
t.extend_from_slice(&self.belts[path_id][end + 1..]);
// println!("{:?}", &t);
// println!();
// println!("{:?}", &self.belts[index]);
self.belts[path_id] = t;
}
return true;
}
let mut candidate = Candidate::new(
Position::new(
xrange.start().saturating_sub(1) as PositionType,
*yrange.start() as PositionType,
),
Position::new(*xrange.end() as PositionType, *yrange.end() as PositionType),
);
candidate.extend_range(&conflicts);
if candidate != c && !candidates.iter().any(|c| c == &candidate) {
candidates.push(candidate);
}
let mut candidate = Candidate::new(
Position::new(
*xrange.start() as PositionType,
yrange.start().saturating_sub(1) as PositionType,
),
Position::new(*xrange.end() as PositionType, *yrange.end() as PositionType),
);
candidate.extend_range(&conflicts);
if candidate != c && !candidates.iter().any(|c| c == &candidate) {
candidates.push(candidate);
}
let mut candidate = Candidate::new(
Position::new(
*xrange.start() as PositionType,
*yrange.start() as PositionType,
),
Position::new(
usize::min(xrange.end() + 1, self.map.width - 1) as PositionType,
*yrange.end() as PositionType,
),
);
candidate.extend_range(&conflicts);
if candidate != c && !candidates.iter().any(|c| c == &candidate) {
candidates.push(candidate);
}
let mut candidate = Candidate::new(
Position::new(
*xrange.start() as PositionType,
*yrange.start() as PositionType,
),
Position::new(
*xrange.end() as PositionType,
usize::min(yrange.end() + 1, self.map.height - 1) as PositionType,
),
);
candidate.extend_range(&conflicts);
if candidate != c && !candidates.iter().any(|c| c == &candidate) {
candidates.push(candidate);
}
}
false
}
pub fn remove_all_conflicts(&mut self, timeout: Option<Duration>) -> bool {
let end = timeout.map(|t| std::time::Instant::now() + t);
while self.remove_conflict(end) {}
let mut conflicts: Map<bool> = 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 {
if conflicts.get(x, y) == &true {
return false;
} else {
conflicts.set(x, y, true);
}
}
}
}
for path in &self.belts {
for p in &path[1..] {
match p {
PathField::Belt { pos, dir: _ } => {
if conflicts.get(pos.x as usize, pos.y as usize) == &true {
return false;
} else {
conflicts.set(pos.x as usize, pos.y as usize, true);
}
}
PathField::Underground { pos, dir, len } => {
if conflicts.get(pos.x as usize, pos.y as usize) == &true {
return false;
} else {
conflicts.set(pos.x as usize, pos.y as usize, true);
}
let end = pos.in_direction(dir, *len as PositionType);
if conflicts.get(end.x as usize, end.y as usize) == &true {
return false;
} else {
conflicts.set(end.x as usize, end.y as usize, true);
}
}
}
}
}
true
}
pub fn belt_blueprint(&self, speed: &Beltspeed, nextfree: &mut u32) -> Vec<BlueprintEntity> {
let mut res = Vec::new();
for path in &self.belts {
res.extend(convert_to_blueprint(&path[1..], speed, nextfree));
}
res
}
}
impl Visualize for ConflictAvoidance {
fn visualize(&self) -> factorio_core::visualize::Visualization {
let mut v = factorio_core::visualize::Visualization::new(Position::new(
self.map.width as i32,
self.map.height as i32,
));
// create conflict map
let mut conflicts: Map<usize> = 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;
v.add_symbol(
Position::new(x as i32, y as i32),
factorio_core::visualize::Symbol::Block,
Some(factorio_core::visualize::Color::white()),
None,
);
}
}
}
for path in &self.belts {
for p in &path[1..] {
match p {
PathField::Belt { pos, dir: _ } => {
*conflicts.get_mut(pos.x as usize, pos.y as usize) += 1
}
PathField::Underground { pos, dir, len } => {
*conflicts.get_mut(pos.x as usize, pos.y as usize) += 1;
let end = pos.in_direction(dir, *len as PositionType);
*conflicts.get_mut(end.x as usize, end.y as usize) += 1;
}
}
}
}
for (i, path) in self.belts.iter().enumerate() {
for p in path {
match p {
PathField::Belt { pos, dir } => {
v.add_symbol(
*pos,
factorio_core::visualize::Symbol::Arrow(*dir),
Some(factorio_core::visualize::Color::index(i)),
None,
);
}
PathField::Underground { pos, dir, len } => {
v.add_symbol(
*pos,
factorio_core::visualize::Symbol::ArrowEnter(*dir),
Some(factorio_core::visualize::Color::index(i)),
None,
);
v.add_symbol(
pos.in_direction(dir, *len as i32),
factorio_core::visualize::Symbol::ArrowExit(*dir),
Some(factorio_core::visualize::Color::index(i)),
None,
);
}
}
}
}
if let Some((xrange, yrange)) = self.range.clone() {
for x in xrange {
for y in yrange.clone() {
v.overwrite_background(
Position::new(x, y),
Some(factorio_core::visualize::Color::new(150, 150, 0)),
);
}
}
}
for x in 0..self.map.width {
for y in 0..self.map.height {
if conflicts.get(x, y) > &1 {
v.overwrite_background(
Position::new(x as i32, y as i32),
Some(factorio_core::visualize::Color::new(100, 80, 80)),
);
}
}
}
for path in &self.belts {
for p in &path[1..] {
match p {
PathField::Belt { pos, dir: _ } => {
*conflicts.get_mut(pos.x as usize, pos.y as usize) += 1
}
PathField::Underground { pos, dir, len } => {
*conflicts.get_mut(pos.x as usize, pos.y as usize) += 1;
let end = pos.in_direction(dir, *len as PositionType);
*conflicts.get_mut(end.x as usize, end.y as usize) += 1;
}
}
}
}
v
}
}

View file

@ -0,0 +1,568 @@
use crate::graph::wheighted_graph::shortest_path::dijkstra;
use crate::graph::wheighted_graph::WheightedGraph;
use crate::layout::Layout;
use crate::misc::Map;
use crate::priority_queue::BinaryHeap;
use factorio_core::{prelude::*, visualize::Visualize};
use serde::{Deserialize, Serialize};
pub mod brute_force;
pub mod conflict_avoidance;
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy)]
pub struct Field {
pub blocked: bool,
weight: f64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Problem {
map: Map<Field>,
start: Vec<(Position, Direction)>,
end: Vec<(Position, Direction)>,
path: Vec<Vec<(Position, Direction)>>,
}
impl Problem {
pub fn new(width: usize, height: usize) -> Self {
Self {
map: Map::new(width, height),
start: Vec::new(),
end: Vec::new(),
path: Vec::new(),
}
}
pub fn from_layout(l: &Layout) -> Self {
let mut p = Self::new(l.problem.size.x as usize, l.problem.size.y as usize);
for b in &l.blocks {
let aabb = b.get_aabb();
p.set_blocked_range(
aabb.min().x as usize,
aabb.min().y as usize,
aabb.max().x as usize,
aabb.max().y as usize,
true,
);
}
for c in &l.problem.connections {
let start_transform = l.blocks[c.startblock].block_to_world();
let startpos = l.problem.blocks[c.startblock].output[c.startpoint]
.offset
.transform(start_transform);
let startdir = l.problem.blocks[c.startblock].output[c.startpoint]
.dir
.transform(start_transform);
let end_transform = l.blocks[c.endblock].block_to_world();
let endpos = l.problem.blocks[c.endblock].input[c.endpoint]
.offset
.transform(end_transform);
let enddir = l.problem.blocks[c.endblock].input[c.endpoint]
.dir
.transform(end_transform);
p.add_connection(
(startpos, startdir),
(endpos.in_direction(&enddir, -1), enddir),
);
}
p
}
pub fn add_connection(&mut self, start: (Position, Direction), end: (Position, Direction)) {
self.start.push(start);
self.end.push(end);
self.path.push(Vec::new());
}
pub fn set_blocked(&mut self, x: usize, y: usize, blocked: bool) {
self.map.get_mut(x, y).blocked = blocked;
}
pub fn set_blocked_range(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, blocked: bool) {
for x in x1..=x2 {
for y in y1..=y2 {
self.set_blocked(x, y, blocked);
}
}
}
fn calculate_wheights(&mut self, without: usize) {
for x in 0..self.map.width {
for y in 0..self.map.height {
let mut weight = 0.0;
if self.map.get(x, y).blocked {
weight += 100.0;
}
self.map.get_mut(x, y).weight = weight;
}
}
for (i, path) in self.path.iter().enumerate() {
if i != without {
for p in path {
let weight = 1.0;
let x = p.0.x as usize;
let y = p.0.y as usize;
self.map.get_mut(x, y).weight += weight;
}
}
}
// for p in &self.start {
// self.map.get_mut(p.0.x as usize, p.0.y as usize).weight = f64::INFINITY;
// }
// for p in &self.end {
// self.map.get_mut(p.0.x as usize, p.0.y as usize).weight = f64::INFINITY;
// }
}
}
impl Visualize for Problem {
fn visualize(&self) -> factorio_core::visualize::Visualization {
let mut v = factorio_core::visualize::Visualization::new(Position::new(
self.map.width as i32,
self.map.height as i32,
));
for x in 0..self.map.width {
for y in 0..self.map.height {
if self.map.get(x, y).blocked {
v.add_symbol(
Position::new(x as i32, y as i32),
factorio_core::visualize::Symbol::Block,
Some(factorio_core::visualize::Color::white()),
None,
)
}
}
}
for (i, (p, _d)) in self.start.iter().enumerate() {
v.add_symbol(
*p,
factorio_core::visualize::Symbol::Char('S'),
Some(factorio_core::visualize::Color::index(i)),
None,
)
}
for (i, (p, _d)) in self.end.iter().enumerate() {
v.add_symbol(
*p,
factorio_core::visualize::Symbol::Char('T'),
Some(factorio_core::visualize::Color::index(i)),
None,
)
}
for (i, p) in self.path.iter().enumerate() {
for (pos, dir) in p {
v.add_symbol(
*pos,
factorio_core::visualize::Symbol::Arrow(*dir),
Some(factorio_core::visualize::Color::index(i)),
None,
)
}
}
v
}
}
struct MapInternal<'a> {
map: &'a Map<Field>,
end: (Position, Direction),
}
impl<'a> WheightedGraph for MapInternal<'a> {
type Node = (Position, Direction);
fn edge(&self, node: &Self::Node, num: usize) -> Option<(Self::Node, f64)> {
let next = node.0.in_direction(&node.1, 1);
next.in_range(
&Position::new(0, 0),
&Position::new(
self.map.width as PositionType,
self.map.height as PositionType,
),
)?;
if self.map.get(next.x as usize, next.y as usize).blocked && self.end != (next, node.1) {
return None;
}
let penalty = self.map.get(next.x as usize, next.y as usize).weight;
match num {
0 => Some(((next, node.1), 1.5 + penalty)),
1 => Some(((next, node.1.counter_clockwise()), 1.5 + penalty)),
2 => Some(((next, node.1.clockwise()), 1.5 + penalty)),
_ => {
let mut count = 2;
for l in 2..=6 {
let n = node.0.in_direction(&node.1, l);
n.in_range(
&Position::new(0, 0),
&Position::new(
self.map.width as PositionType,
self.map.height as PositionType,
),
)?;
if !self.map.get(n.x as usize, n.y as usize).blocked {
count += 1;
if count == num {
let penalty = penalty + self.map.get(n.x as usize, n.y as usize).weight;
return Some(((n, node.1), 35.0 + penalty));
}
}
}
None
}
}
}
}
impl Problem {
pub fn find_path(&mut self) -> bool {
for i in 0..self.start.len() {
self.calculate_wheights(i);
let m = MapInternal {
map: &self.map,
end: self.end[i],
};
let p = dijkstra::<MapInternal, BinaryHeap<_>>(&m, self.start[i], self.end[i]);
if let Some(p) = p {
self.path[i] = p;
} else {
return false;
}
}
true
}
}
pub mod problems {
use super::Problem;
use factorio_core::prelude::*;
pub fn simple() -> Problem {
let mut p = Problem::new(5, 3);
p.set_blocked_range(2, 0, 2, 2, true);
p.add_connection(
(Position::new(0, 1), Direction::Right),
(Position::new(4, 1), Direction::Right),
);
p
}
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(2, 7), Direction::Right),
(Position::new(13, 4), Direction::Right),
);
p.add_connection(
(Position::new(2, 8), Direction::Right),
(Position::new(13, 5), Direction::Right),
);
p.add_connection(
(Position::new(2, 4), Direction::Right),
(Position::new(13, 8), Direction::Right),
);
p.add_connection(
(Position::new(2, 5), Direction::Right),
(Position::new(13, 7), Direction::Right),
);
p
}
pub fn belt_madness_level_2() -> Problem {
let mut p = Problem::new(17, 13);
p.set_blocked(0, 3, true);
p.set_blocked_range(1, 2, 2, 5, true);
p.set_blocked_range(1, 7, 2, 10, true);
p.set_blocked(0, 9, true);
p.set_blocked(16, 3, true);
p.set_blocked_range(14, 2, 15, 5, true);
p.set_blocked_range(14, 7, 15, 10, true);
p.set_blocked(16, 9, true);
p.add_connection(
(Position::new(2, 4), Direction::Right),
(Position::new(13, 2), Direction::Right),
);
p.add_connection(
(Position::new(2, 7), Direction::Right),
(Position::new(13, 3), Direction::Right),
);
p.add_connection(
(Position::new(2, 10), Direction::Right),
(Position::new(13, 4), Direction::Right),
);
p.add_connection(
(Position::new(2, 9), Direction::Right),
(Position::new(13, 5), Direction::Right),
);
p.add_connection(
(Position::new(2, 2), Direction::Right),
(Position::new(13, 7), Direction::Right),
);
p.add_connection(
(Position::new(2, 3), Direction::Right),
(Position::new(13, 8), Direction::Right),
);
p.add_connection(
(Position::new(2, 5), Direction::Right),
(Position::new(13, 9), Direction::Right),
);
p.add_connection(
(Position::new(2, 8), Direction::Right),
(Position::new(13, 10), Direction::Right),
);
p
}
pub fn belt_madness_level_3() -> 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(2, 3), Direction::Right),
(Position::new(29, 7), Direction::Right),
);
p.add_connection(
(Position::new(2, 4), Direction::Right),
(Position::new(29, 9), Direction::Right),
);
p.add_connection(
(Position::new(2, 5), Direction::Right),
(Position::new(29, 8), Direction::Right),
);
p.add_connection(
(Position::new(2, 7), Direction::Right),
(Position::new(29, 3), Direction::Right),
);
p.add_connection(
(Position::new(2, 8), Direction::Right),
(Position::new(29, 5), Direction::Right),
);
p.add_connection(
(Position::new(2, 9), Direction::Right),
(Position::new(29, 4), Direction::Right),
);
p
}
pub fn belt_madness_level_5() -> Problem {
let mut p = Problem::new(31, 29);
// power stations
p.set_blocked_range(8, 8, 9, 9, true);
p.set_blocked_range(21, 8, 22, 9, true);
p.set_blocked_range(8, 19, 9, 20, true);
p.set_blocked_range(21, 19, 22, 20, true);
// solar panels
p.set_blocked_range(12, 11, 14, 13, true);
p.set_blocked_range(16, 11, 18, 13, true);
p.set_blocked_range(12, 15, 14, 17, true);
p.set_blocked_range(16, 15, 18, 17, true);
// Top
p.set_blocked_range(7, 0, 8, 2, true);
p.set_blocked(8, 3, true);
p.set_blocked(9, 4, true);
p.set_blocked_range(10, 5, 20, 5, true);
p.set_blocked(21, 4, true);
p.set_blocked(22, 3, true);
p.set_blocked_range(22, 0, 23, 2, true);
p.set_blocked_range(2, 1, 2, 2, true);
p.set_blocked_range(4, 1, 4, 2, true);
p.set_blocked_range(1, 4, 2, 4, true);
p.set_blocked_range(12, 1, 12, 2, true);
p.set_blocked_range(14, 1, 14, 2, true);
p.set_blocked_range(16, 1, 16, 2, true);
p.set_blocked_range(18, 1, 18, 2, true);
p.set_blocked_range(28, 4, 29, 4, true);
p.set_blocked_range(26, 1, 26, 2, true);
p.set_blocked_range(28, 1, 28, 2, true);
// Bottom
p.set_blocked_range(7, 26, 8, 28, true);
p.set_blocked(8, 25, true);
p.set_blocked(9, 24, true);
p.set_blocked_range(10, 23, 20, 23, true);
p.set_blocked(21, 24, true);
p.set_blocked(22, 25, true);
p.set_blocked_range(22, 26, 23, 28, true);
p.set_blocked_range(1, 26, 2, 26, true);
p.set_blocked_range(4, 26, 4, 27, true);
p.set_blocked_range(1, 24, 2, 24, true);
p.set_blocked_range(12, 26, 12, 27, true);
p.set_blocked_range(14, 26, 14, 27, true);
p.set_blocked_range(16, 26, 16, 27, true);
p.set_blocked_range(18, 26, 18, 27, true);
p.set_blocked_range(28, 24, 29, 24, true);
p.set_blocked_range(26, 26, 26, 27, true);
p.set_blocked_range(28, 26, 29, 26, true);
// Left
p.set_blocked_range(0, 7, 2, 8, true);
p.set_blocked(3, 8, true);
p.set_blocked(4, 9, true);
p.set_blocked_range(5, 10, 5, 18, true);
p.set_blocked(4, 19, true);
p.set_blocked(3, 20, true);
p.set_blocked_range(0, 20, 2, 21, true);
p.set_blocked_range(1, 11, 2, 11, true);
p.set_blocked_range(1, 13, 2, 13, true);
p.set_blocked_range(1, 15, 2, 15, true);
p.set_blocked_range(1, 17, 2, 17, true);
// Right
p.set_blocked_range(28, 7, 30, 8, true);
p.set_blocked(27, 8, true);
p.set_blocked(26, 9, true);
p.set_blocked_range(25, 10, 25, 18, true);
p.set_blocked(26, 19, true);
p.set_blocked(27, 20, true);
p.set_blocked_range(28, 20, 30, 21, true);
p.set_blocked_range(28, 11, 29, 11, true);
p.set_blocked_range(28, 13, 29, 13, true);
p.set_blocked_range(28, 15, 29, 15, true);
p.set_blocked_range(28, 17, 29, 17, true);
// Path
p.add_connection(
(Position::new(4, 2), Direction::Down),
(Position::new(26, 25), Direction::Down),
);
p.add_connection(
(Position::new(12, 2), Direction::Down),
(Position::new(18, 25), Direction::Down),
);
p.add_connection(
(Position::new(14, 2), Direction::Down),
(Position::new(3, 26), Direction::Left),
);
p.add_connection(
(Position::new(16, 2), Direction::Down),
(Position::new(14, 25), Direction::Down),
);
p.add_connection(
(Position::new(2, 4), Direction::Right),
(Position::new(27, 24), Direction::Right),
);
p.add_connection(
(Position::new(2, 11), Direction::Right),
(Position::new(27, 17), Direction::Right),
);
p.add_connection(
(Position::new(2, 13), Direction::Right),
(Position::new(27, 26), Direction::Right),
);
p.add_connection(
(Position::new(2, 15), Direction::Right),
(Position::new(27, 13), Direction::Right),
);
p.add_connection(
(Position::new(28, 15), Direction::Left),
(Position::new(2, 3), Direction::Up),
);
p.add_connection(
(Position::new(2, 17), Direction::Right),
(Position::new(18, 3), Direction::Up),
);
p.add_connection(
(Position::new(2, 24), Direction::Right),
(Position::new(26, 3), Direction::Up),
);
p.add_connection(
(Position::new(4, 26), Direction::Up),
(Position::new(27, 4), Direction::Right),
);
p.add_connection(
(Position::new(12, 26), Direction::Up),
(Position::new(27, 11), Direction::Right),
);
p.add_connection(
(Position::new(16, 26), Direction::Up),
(Position::new(28, 3), Direction::Up),
);
p
}
}

View file

@ -0,0 +1,101 @@
use clap::{Parser, Subcommand, ValueEnum};
use factorio_blueprint::{encode, misc::Beltspeed, Blueprint, BlueprintString};
use factorio_core::visualize::Visualize;
use factorio_pathfinding::belt_finding::{
conflict_avoidance::ConflictAvoidance, problems, Problem,
};
use std::{io, path::PathBuf};
#[derive(ValueEnum, Clone)]
enum Mode {
Solve,
ConflictAvoidance,
ConflictStep,
}
#[derive(Subcommand)]
enum ProblemCase {
Simple,
Level1,
Level2,
Level3,
Level5,
File { filename: PathBuf },
}
impl ProblemCase {
fn get_problem(&self) -> Problem {
match self {
ProblemCase::Simple => problems::simple(),
ProblemCase::Level1 => problems::belt_madness_level_1(),
ProblemCase::Level2 => problems::belt_madness_level_2(),
ProblemCase::Level3 => problems::belt_madness_level_3(),
ProblemCase::Level5 => problems::belt_madness_level_5(),
ProblemCase::File { filename } => {
let file = std::fs::File::open(filename).unwrap();
serde_json::from_reader(file).unwrap()
}
}
}
}
#[derive(Parser)]
struct Args {
#[arg(value_enum, default_value = "conflict-avoidance")]
mode: Mode,
#[command(subcommand)]
problem: ProblemCase,
}
fn output_blueprint(conflict: &ConflictAvoidance) {
let mut offset = 0;
let belts = conflict.belt_blueprint(&Beltspeed::Normal, &mut offset);
let bp = BlueprintString::Blueprint(
Blueprint::builder()
.entities(belts)
.label(format!("test"))
.build(),
);
println!("{}", encode(&serde_json::to_string(&bp).unwrap()));
}
fn main() {
let args = Args::parse();
let mut p = args.problem.get_problem();
match args.mode {
Mode::Solve => {
p.print_visualization();
p.find_path();
p.print_visualization();
}
Mode::ConflictAvoidance => {
p.print_visualization();
p.find_path();
p.find_path();
p.print_visualization();
let mut c = ConflictAvoidance::new(&p);
c.print_visualization();
while c.remove_conflict(None) {
c.print_visualization();
}
output_blueprint(&c);
}
Mode::ConflictStep => {
p.print_visualization();
p.find_path();
p.print_visualization();
let mut c = ConflictAvoidance::new(&p);
c.print_visualization();
while c.remove_conflict(None) {
c.print_visualization();
let mut s = String::new();
let _ = io::stdin().read_line(&mut s);
}
output_blueprint(&c);
}
}
}

View file

@ -0,0 +1,31 @@
use clap::Parser;
use factorio_core::visualize::image_grid;
use factorio_core::visualize::Visualize;
use factorio_pathfinding::belt_finding::{conflict_avoidance::ConflictAvoidance, Problem};
use std::{fs::File, path::PathBuf};
#[derive(Parser)]
struct Args {
inputfiles: Vec<PathBuf>,
outputfile: PathBuf,
}
fn main() {
let args = Args::parse();
let visualizations: Vec<_> = args
.inputfiles
.iter()
.map(|f| {
let f = File::open(f).unwrap();
let mut p = serde_json::from_reader::<File, Problem>(f).unwrap();
let c = ConflictAvoidance::new(&p);
c.visualize()
})
.collect();
let image = image_grid(&visualizations, 50, 50, 5);
image.save(args.outputfile).unwrap();
}

View file

@ -0,0 +1,98 @@
use clap::{Parser, Subcommand};
use factorio_core::visualize::Visualize;
use factorio_pathfinding::layout::{genetic_algorithm2, GeneticAlgorithm, PathLayout};
use miette::{Context, IntoDiagnostic, Result};
use rand::{rngs::SmallRng, SeedableRng};
use std::path::PathBuf;
#[derive(Debug, Parser)]
struct Args {
#[clap(short, long, default_value_t = 0)]
seed: u64,
problem: PathBuf,
#[command(subcommand)]
subcommand: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
V1,
V2,
Bench {
#[clap(short, long, default_value_t = 100)]
runs: usize,
#[clap(short, long, default_value_t = 100)]
mutations: usize,
},
}
fn main() -> Result<()> {
let args = Args::parse();
let mut rng = SmallRng::seed_from_u64(args.seed);
let file = std::fs::File::open(args.problem)
.into_diagnostic()
.context("Failed to open problem file.")?;
let p = serde_yaml::from_reader(file)
.into_diagnostic()
.context("Failed to parse yaml.")?;
match args.subcommand {
Commands::V1 => {
let mut g = GeneticAlgorithm::new(&p, 20, 2, 0, &mut rng);
for i in 0..100 {
println!("Generatrion {i}");
g.generation(&mut rng);
}
g.output_population();
}
Commands::V2 => {
let mut m: Option<PathLayout> = None;
for _ in 0..20 {
let g = genetic_algorithm2(&p, 10, 320, 10000, &mut rng);
g.print_visualization();
if m.as_ref().is_none_or(|m| g.score() < m.score()) {
m = Some(g);
}
}
m.unwrap().print_visualization();
}
Commands::Bench { runs, mutations } => {
let mut map = Vec::new();
let mut m: Option<PathLayout> = None;
let start = std::time::Instant::now();
for _ in 0..runs {
let g = genetic_algorithm2(&p, 1, mutations, mutations, &mut rng);
map.push(g.score());
if m.as_ref().is_none_or(|m| g.score() < m.score()) {
m = Some(g);
}
}
println!("Time: {:.2}", start.elapsed().as_secs_f32());
let mean = map.iter().sum::<usize>() / runs;
println!("Mean: {}", mean);
let min = map.iter().min().unwrap();
println!("Min: {}", min);
let max = map.iter().max().unwrap();
println!("Max: {}", max);
let stddev =
((map.iter().map(|v| (v - mean) * (v - mean)).sum::<usize>() / runs) as f64).sqrt();
println!("Stddev: {:.1}", stddev);
m.unwrap().print_visualization();
}
}
Ok(())
}

View file

@ -0,0 +1 @@
pub mod wheighted_graph;

View file

@ -0,0 +1,27 @@
use super::WheightedGraph;
pub struct WheightedAdjacencyList {
pub(crate) nodes: Vec<usize>,
pub(crate) edges: Vec<(usize, f64)>,
}
impl WheightedGraph for WheightedAdjacencyList {
type Node = usize;
fn num_edges(&self, node: &Self::Node) -> usize {
assert!(*node < self.nodes.len());
if *node == self.nodes.len() - 1 {
self.nodes.len() - self.nodes[0]
} else {
self.nodes[node + 1] - self.nodes[*node]
}
}
fn edge(&self, node: &Self::Node, num: usize) -> Option<(Self::Node, f64)> {
if num < self.num_edges(node) {
Some(self.edges[self.nodes[*node] + num])
} else {
None
}
}
}

View file

@ -0,0 +1,49 @@
pub mod adjacency_list;
pub mod shortest_path;
pub trait WheightedGraph: Sized {
type Node;
fn num_edges(&self, node: &Self::Node) -> usize {
self.edge_iter(node).count()
}
fn edge(&self, node: &Self::Node, num: usize) -> Option<(Self::Node, f64)>;
fn edge_iter<'a, 'b>(&'a self, node: &'b Self::Node) -> WheightedGraphEdgeIter<'a, 'b, Self> {
WheightedGraphEdgeIter::new(self, node)
}
}
pub struct WheightedGraphEdgeIter<'a, 'b, G>
where
G: WheightedGraph,
{
graph: &'a G,
node: &'b G::Node,
count: usize,
}
impl<'a, 'b, G> WheightedGraphEdgeIter<'a, 'b, G>
where
G: WheightedGraph,
{
pub fn new(graph: &'a G, node: &'b G::Node) -> Self {
Self {
graph,
node,
count: 0,
}
}
}
impl<'a, 'b, G> Iterator for WheightedGraphEdgeIter<'a, 'b, G>
where
G: WheightedGraph,
{
type Item = (G::Node, f64);
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
self.graph.edge(self.node, self.count - 1)
}
}

View file

@ -0,0 +1,235 @@
use std::{collections::HashMap, fmt::Debug, hash::Hash, hash::Hasher};
use crate::priority_queue::PriorityQueue;
use super::WheightedGraph;
#[derive(Debug, Clone, Copy)]
pub struct QueueObject<N> {
node: N,
score: f64,
}
impl<N> QueueObject<N> {
fn new(node: N, score: f64) -> Self {
Self { node, score }
}
}
impl<N> PartialOrd for QueueObject<N> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.score.partial_cmp(&other.score)
}
}
impl<N> PartialEq for QueueObject<N> {
fn eq(&self, other: &Self) -> bool {
self.score == other.score
}
}
impl<N> Hash for QueueObject<N>
where
N: Hash,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.node.hash(state);
}
}
#[derive(Debug)]
struct MapObject<N, H> {
parent: N,
score: f64,
key: Option<H>,
}
impl<N, H> MapObject<N, H> {
fn new(parent: N, score: f64, key: H) -> Self {
Self {
parent,
score,
key: Some(key),
}
}
}
pub fn dijkstra<G, P>(graph: &G, start: G::Node, end: G::Node) -> Option<Vec<G::Node>>
where
P: PriorityQueue<QueueObject<G::Node>> + Debug,
P::Handle: Debug,
G::Node: Eq + Hash + Clone + Debug,
G: WheightedGraph,
{
if start == end {
return Some(vec![start]);
}
let mut map: HashMap<G::Node, MapObject<G::Node, P::Handle>> = HashMap::new();
let mut q = P::new();
q.insert(QueueObject::new(start.clone(), 0.0));
while let Some(o) = q.pop_min() {
if let Some(m) = map.get_mut(&o.node) {
m.key = None;
}
if o.node == end {
break;
}
for (node, wheight) in graph.edge_iter(&o.node) {
let score = o.score + wheight;
if let Some(n) = map.get_mut(&node) {
if let Some(h) = &n.key {
if score < n.score {
n.parent = o.node.clone();
n.score = score;
q.decrease_key(h, |i| i.score = score);
}
}
} else {
let h = q.insert(QueueObject::new(node.clone(), o.score + wheight));
map.insert(node, MapObject::new(o.node.clone(), o.score + wheight, h));
}
}
// dbg!(&q);
}
map.get(&end)?;
let mut result = vec![end];
// dbg!(&map);
loop {
let parent = map.get(result.last().expect("last")).expect("get");
result.push(parent.parent.clone());
if parent.parent == start {
break;
}
}
result.reverse();
Some(result)
}
#[cfg(test)]
mod test {
use crate::{
graph::wheighted_graph::{
adjacency_list::WheightedAdjacencyList, shortest_path::dijkstra, WheightedGraph,
},
priority_queue::{fibonacci_heap::FibonacciHeap, BinaryHeap},
};
#[test]
fn trivial() {
let a = WheightedAdjacencyList {
nodes: vec![0, 1],
edges: vec![(1, 1.0)],
};
assert_eq!(
dijkstra::<WheightedAdjacencyList, BinaryHeap<_>>(&a, 0, 1),
Some(vec![0, 1])
);
}
#[test]
fn simple() {
let a = WheightedAdjacencyList {
nodes: vec![0, 2, 3, 5, 5, 7, 10],
edges: vec![
(1, 2.0),
(4, 10.0),
(2, 3.0),
(3, 2.0),
(5, 1.0),
(0, 4.0),
(2, 5.0),
(2, 9.0),
(3, 8.0),
(4, 0.0),
(5, 7.0),
],
};
assert_eq!(
dijkstra::<WheightedAdjacencyList, BinaryHeap<_>>(&a, 0, 4),
Some(vec![0, 1, 2, 5, 4])
);
assert_eq!(
dijkstra::<WheightedAdjacencyList, BinaryHeap<_>>(&a, 0, 6),
None
);
assert_eq!(
dijkstra::<WheightedAdjacencyList, FibonacciHeap<_>>(&a, 0, 4),
Some(vec![0, 1, 2, 5, 4])
);
assert_eq!(
dijkstra::<WheightedAdjacencyList, FibonacciHeap<_>>(&a, 0, 6),
None
);
}
struct Grid {
width: usize,
height: usize,
}
impl WheightedGraph for Grid {
type Node = (usize, usize);
fn num_edges(&self, node: &Self::Node) -> usize {
let mut c = 0;
if node.0 > 0 {
c += 1
}
if node.0 < self.width - 1 {
c += 1
}
if node.1 > 0 {
c += 1
}
if node.1 < self.height - 1 {
c += 1
}
c
}
fn edge(&self, node: &Self::Node, num: usize) -> Option<(Self::Node, f64)> {
dbg!(&node);
let edges = [
(node.0 > 0).then_some((node.0.saturating_sub(1), node.1)),
(node.0 < self.width - 1).then_some((node.0 + 1, node.1)),
(node.1 > 0).then_some((node.0, node.1.saturating_sub(1))),
(node.1 < self.height - 1).then_some((node.0, node.1 + 1)),
];
edges.iter().flatten().nth(num).map(|&p| (p, 1.0))
}
}
#[test]
fn grid() {
let g = Grid {
width: 600,
height: 600,
};
dbg!(dijkstra::<Grid, BinaryHeap<_>>(
&g,
(0, 0),
(g.width - 1, g.height - 1)
));
}
}

View file

@ -0,0 +1,573 @@
use crate::belt_finding::conflict_avoidance::ConflictAvoidance;
use factorio_core::pathfield::PathField;
use factorio_core::prelude::*;
use factorio_core::visualize::{image_grid, Color, Symbol, Visualization, Visualize};
use rand::{seq::SliceRandom, Rng};
use serde::{Deserialize, Serialize};
use std::sync::atomic::AtomicU32;
use std::time::Instant;
static OUTFILEINDEX: AtomicU32 = AtomicU32::new(0);
pub struct GeneticAlgorithm<'a> {
problem: &'a Problem,
population: Vec<PathLayout<'a>>,
population_size: usize,
population_keep: usize,
population_new: usize,
}
impl<'a> GeneticAlgorithm<'a> {
pub fn new<R: Rng + ?Sized>(
problem: &'a Problem,
population_size: usize,
population_keep: usize,
population_new: usize,
rng: &mut R,
) -> GeneticAlgorithm<'a> {
let mut population = Vec::new();
let start = Instant::now();
let mut count: usize = 0;
while population.len() < population_size {
count += 1;
if let Some(p) = PathLayout::new(Layout::new(problem, rng)) {
population.push(p);
}
}
println!("Layouts accepted: {}/{}", population_size, count);
population.sort_by_key(|p| p.score());
println!(
"Best score: {}. Time: {:.2}s",
population[0].score(),
start.elapsed().as_secs_f32()
);
population[0].print_visualization();
GeneticAlgorithm {
problem,
population,
population_size,
population_keep,
population_new,
}
}
pub fn generation<R: Rng + ?Sized>(&mut self, rng: &mut R) {
let start_new = Instant::now();
for i in self.population_keep..(self.population_keep + self.population_new) {
loop {
if let Some(p) = PathLayout::new(Layout::new(self.problem, rng)) {
self.population[i] = p;
break;
}
}
}
let duration_new = start_new.elapsed();
let start_mutate = Instant::now();
for i in (self.population_keep + self.population_new)..self.population_size {
let j = i - (self.population_keep + self.population_new);
loop {
if let Some(p) =
PathLayout::new(self.population[j % self.population_keep].layout.mutate(rng))
{
self.population[i] = p;
break;
}
}
}
let duration_mutate = start_mutate.elapsed();
self.population.sort_by_key(|p| p.score());
println!(
"Best score: {}. Time new: {:.2}s. Time mutate: {:.2}s",
self.population[0].score(),
duration_new.as_secs_f32(),
duration_mutate.as_secs_f32()
);
self.population[0].print_visualization();
let v: Vec<_> = self.population.iter().map(|p| p.visualize()).collect();
let img = image_grid(&v, v[0].size().x as u32, v[0].size().y as u32, 5);
let mut file = std::fs::File::create("generation.png").unwrap();
img.write_to(&mut file, image::ImageFormat::Png).unwrap();
}
pub fn output_population(&self) {
println!("Population:");
for (i, p) in self.population.iter().enumerate() {
println!("{i:3}: {}", p.score());
p.print_visualization();
}
}
}
pub fn genetic_algorithm2<'a, R: Rng + ?Sized>(
problem: &'a Problem,
new_layouts: usize,
mutation_timeout: usize,
max_mutations: usize,
rng: &'_ mut R,
) -> PathLayout<'a> {
let mut m = (0..new_layouts)
.map(|_| valid_path_layout(problem, rng))
.min_by_key(|p| p.score())
.unwrap();
// m.print_visualization();
let mut last_improvement = 0;
let mut count = 0;
while last_improvement < mutation_timeout && count < max_mutations {
last_improvement += 1;
count += 1;
if let Some(p) = PathLayout::new(m.layout.mutate(rng)) {
if p.score() < m.score() {
m = p;
// println!("Step: {count}");
// m.print_visualization();
last_improvement = 0;
}
}
}
m
}
pub fn valid_path_layout<'a, R: Rng + ?Sized>(
problem: &'a Problem,
rng: &'_ mut R,
) -> PathLayout<'a> {
loop {
if let Some(p) = PathLayout::new(Layout::new(problem, rng)) {
return p;
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct MacroBlock {
pub(crate) size: Position,
pub(crate) input: Vec<Interface>,
pub(crate) output: Vec<Interface>,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct Interface {
pub(crate) offset: Position,
pub(crate) dir: Direction,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct Connection {
pub(crate) startblock: usize,
pub(crate) startpoint: usize,
pub(crate) endblock: usize,
pub(crate) endpoint: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Problem {
pub(crate) size: Position,
pub(crate) blocks: Vec<MacroBlock>,
pub(crate) connections: Vec<Connection>,
}
// #[derive(Debug, Clone, Copy)]
// pub struct BlockHandle(usize);
#[derive(Debug, Clone)]
pub struct Layout<'a> {
pub(crate) problem: &'a Problem,
pub(crate) blocks: Vec<Block>,
}
pub struct PathLayout<'a> {
layout: Layout<'a>,
paths: Vec<Vec<PathField>>,
score: usize,
}
impl<'a> PathLayout<'a> {
pub fn new(layout: Layout<'a>) -> Option<PathLayout<'a>> {
let mut p = crate::belt_finding::Problem::from_layout(&layout);
if !p.find_path() {
return None;
}
let mut c = ConflictAvoidance::new(&p);
let start = std::time::Instant::now();
if !c.remove_all_conflicts(Some(std::time::Duration::from_secs(2))) {
if start.elapsed().as_secs_f32() > 0.5 {
// println!("Conflict avoidance: {:.2}", start.elapsed().as_secs_f32());
// c.print_visualization();
let file = std::fs::File::create(format!(
"out/{}.json",
OUTFILEINDEX.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
))
.unwrap();
serde_json::to_writer(file, &p).unwrap();
// println!("Saved slow solve.");
}
return None;
}
let paths = c.get_paths().to_vec();
let score = paths
.iter()
.map(|path| path.iter().skip(1).map(|p| p.cost()).sum::<usize>())
.sum();
Some(PathLayout {
layout,
paths,
score,
})
}
pub fn score(&self) -> usize {
self.score
}
}
impl<'a> Visualize for PathLayout<'a> {
fn visualize(&self) -> factorio_core::visualize::Visualization {
let mut v = self.layout.visualize();
let offset = self.layout.blocks.len();
for (i, path) in self.paths.iter().enumerate() {
for p in &path[1..] {
match p {
PathField::Belt { pos, dir } => {
v.add_symbol(
*pos,
Symbol::Arrow(*dir),
Some(Color::index(i + offset)),
None,
);
}
PathField::Underground { pos, dir, len } => {
v.add_symbol(
*pos,
Symbol::ArrowEnter(*dir),
Some(Color::index(i + offset)),
None,
);
v.add_symbol(
pos.in_direction(dir, *len as i32),
Symbol::ArrowEnter(*dir),
Some(Color::index(i + offset)),
None,
);
}
}
}
}
v
}
}
impl Problem {
pub fn new(size: Position) -> Self {
Self {
size,
blocks: Vec::new(),
connections: Vec::new(),
}
}
// pub fn add_block(&mut self, size: Position) -> BlockHandle {
// self.blocks.push(Block {
// size,
// input: Vec::new(),
// output: Vec::new(),
// });
// BlockHandle(self.blocks.len() - 1)
// }
// pub fn add_connection(
// &mut self,
// starthandle: BlockHandle,
// startoffset: Position,
// endhandle: BlockHandle,
// endoffset: Position,
// ) {
// let startinterface = self.blocks[starthandle.0].output.len();
// let endinterface = self.blocks[endhandle.0].input.len();
// self.blocks[starthandle.0].output.push(Interface {
// offset: startoffset,
// target: (endhandle.0, endinterface),
// });
// self.blocks[endhandle.0].input.push(Interface {
// offset: endoffset,
// target: (starthandle.0, startinterface),
// })
// }
}
impl Layout<'_> {
/// Create a new valid layout
pub fn new<'a, R: Rng + ?Sized>(problem: &'a Problem, rng: &'_ mut R) -> Layout<'a> {
let mut blocks = Vec::new();
assert!(Self::place_block(problem, &mut blocks, rng));
let mut l = Layout { problem, blocks };
l.center();
l
}
fn place_block<R: Rng + ?Sized>(
problem: &'_ Problem,
blocks: &mut Vec<Block>,
rng: &'_ mut R,
) -> bool {
if problem.blocks.len() == blocks.len() {
return true;
}
let b = &problem.blocks[blocks.len()];
for _ in 0..1000 {
let dir = rng.gen::<Direction>();
let pos = match dir {
Direction::Up => Position::new(
rng.gen_range(0..=(problem.size.x - b.size.x)),
rng.gen_range(0..=(problem.size.y - b.size.y)),
),
Direction::Right => Position::new(
rng.gen_range((b.size.y - 1)..problem.size.x),
rng.gen_range(0..=(problem.size.y - b.size.x)),
),
Direction::Down => Position::new(
rng.gen_range((b.size.x - 1)..problem.size.x),
rng.gen_range((b.size.y - 1)..problem.size.y),
),
Direction::Left => Position::new(
rng.gen_range(0..=(problem.size.x - b.size.y)),
rng.gen_range((b.size.x - 1)..problem.size.y),
),
};
let current = Block::new(pos, dir, problem.blocks[blocks.len()].size);
let current_aabb = current.get_aabb();
if blocks
.iter()
.all(|b| !AABB::collision(b.get_aabb(), current_aabb))
{
blocks.push(current);
if Self::place_block(problem, blocks, rng) {
return true;
}
blocks.pop();
}
}
false
}
pub fn get_aabb(&self) -> AABB {
self.blocks
.iter()
.map(|b| b.get_aabb())
.reduce(AABB::combine)
.expect("At least one block is required.")
}
pub fn center(&mut self) {
let aabb = self.get_aabb();
let rest = self.problem.size - aabb.size();
let new_min = Position::new(rest.x / 2, rest.y / 2);
let t = Transformation::new(Direction::Up, new_min - aabb.min());
for b in &mut self.blocks {
*b = b.transform(t);
}
}
/// Mutate existing layout, creating a valid layout
pub fn mutate<R: Rng + ?Sized>(&self, rng: &mut R) -> Self {
let mut s = self.clone();
#[allow(clippy::type_complexity)]
let r: &[(&dyn Fn(&mut Layout, &mut R) -> bool, _)] = &[
(&Self::mutate_replace::<R>, 30),
(&Self::mutate_flip::<R>, 50),
(&Self::mutate_jiggle::<R>, 160),
];
loop {
let p = r.choose_weighted(rng, |i| i.1).unwrap();
if p.0(&mut s, rng) && rng.gen_bool(0.5) {
break;
}
}
s.center();
s
}
fn mutate_replace<R: Rng + ?Sized>(layout: &mut Layout, rng: &mut R) -> bool {
let i = rng.gen_range(0..layout.blocks.len());
let dir = rng.gen::<Direction>();
let b = &layout.problem.blocks[i];
let pos = match dir {
Direction::Up => Position::new(
rng.gen_range(0..=(layout.problem.size.x - b.size.x)),
rng.gen_range(0..=(layout.problem.size.y - b.size.y)),
),
Direction::Right => Position::new(
rng.gen_range((b.size.y - 1)..layout.problem.size.x),
rng.gen_range(0..=(layout.problem.size.y - b.size.x)),
),
Direction::Down => Position::new(
rng.gen_range((b.size.x - 1)..layout.problem.size.x),
rng.gen_range((b.size.y - 1)..layout.problem.size.y),
),
Direction::Left => Position::new(
rng.gen_range(0..=(layout.problem.size.x - b.size.y)),
rng.gen_range((b.size.x - 1)..layout.problem.size.y),
),
};
let current = Block::new(pos, dir, b.size);
let current_aabb = current.get_aabb();
if layout
.blocks
.iter()
.enumerate()
.all(|(j, b)| j == i || !AABB::collision(b.get_aabb(), current_aabb))
{
layout.blocks[i] = current;
true
} else {
false
}
}
fn mutate_flip<R: Rng + ?Sized>(layout: &mut Layout, rng: &mut R) -> bool {
let i = rng.gen_range(0..layout.blocks.len());
let b = &mut layout.blocks[i];
let block = &layout.problem.blocks[i];
let new_pos = match b.dir() {
Direction::Up => b.pos() + block.size - Position::new(1, 1),
Direction::Right => b.pos() + Position::new(1 - block.size.y, block.size.x - 1),
Direction::Down => b.pos() - block.size + Position::new(1, 1),
Direction::Left => b.pos() + Position::new(block.size.y - 1, 1 - block.size.x),
};
let new_dir = b.dir().reverse();
*b = Block::new(new_pos, new_dir, b.size());
true
}
fn mutate_jiggle<R: Rng + ?Sized>(layout: &mut Layout, rng: &mut R) -> bool {
let i = rng.gen_range(0..layout.blocks.len());
let dir = rng.gen::<Direction>();
let step = [(1, 10), (2, 5), (3, 5)]
.choose_weighted(rng, |i| i.1)
.unwrap()
.0;
// let step = 1;
let b = &layout.blocks[i];
let current = Block::new(b.pos().in_direction(&dir, step), b.dir(), b.size());
let current_aabb = current.get_aabb();
if current_aabb.min().x < 0
|| current_aabb.min().y < 0
|| current_aabb.max().x >= layout.problem.size.x
|| current_aabb.max().y >= layout.problem.size.y
{
return false;
}
if layout
.blocks
.iter()
.enumerate()
.all(|(j, b)| j == i || !AABB::collision(b.get_aabb(), current_aabb))
{
layout.blocks[i] = current;
true
} else {
false
}
}
}
impl<'a> Visualize for Layout<'a> {
fn visualize(&self) -> Visualization {
let mut v = Visualization::new(self.problem.size);
for (i, (b, mb)) in self
.blocks
.iter()
.zip(self.problem.blocks.iter())
.enumerate()
{
let c = Color::index(i);
let aabb = b.get_aabb();
for x in aabb.min().x..=aabb.max().x {
for y in aabb.min().y..=aabb.max().y {
v.add_symbol(Position::new(x, y), Symbol::Block, Some(c), None);
}
}
v.add_symbol(b.pos(), Symbol::Char('X'), Some(c), None);
let transform = b.block_to_world();
for input in &mb.input {
v.add_symbol(
input.offset.transform(transform),
Symbol::Char('i'),
Some(c),
None,
);
}
for output in &mb.output {
v.add_symbol(
output.offset.transform(transform),
Symbol::Char('o'),
Some(c),
None,
);
}
}
v
}
}

View file

@ -0,0 +1,5 @@
pub mod belt_finding;
pub mod graph;
pub mod layout;
pub mod misc;
pub mod priority_queue;

View file

@ -0,0 +1,52 @@
#[derive(Debug)]
pub struct Arena<T> {
data: Vec<Option<T>>,
free: Vec<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArenaKey(usize);
impl Default for ArenaKey {
fn default() -> Self {
ArenaKey(usize::MAX)
}
}
impl<T> Arena<T> {
pub fn new() -> Self {
Self {
data: Vec::new(),
free: Vec::new(),
}
}
pub fn insert(&mut self, item: T) -> ArenaKey {
if let Some(i) = self.free.pop() {
self.data[i] = Some(item);
ArenaKey(i)
} else {
self.data.push(Some(item));
ArenaKey(self.data.len() - 1)
}
}
pub fn get(&self, key: &ArenaKey) -> &T {
self.data.get(key.0).unwrap().as_ref().unwrap()
}
pub fn get_mut(&mut self, key: &ArenaKey) -> &mut T {
self.data.get_mut(key.0).unwrap().as_mut().unwrap()
}
pub fn remove(&mut self, key: ArenaKey) -> T {
self.free.push(key.0);
self.data[key.0].take().unwrap()
}
}
impl<T> Default for Arena<T> {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,68 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Map<T> {
pub width: usize,
pub height: usize,
data: Vec<T>,
}
impl<T> Map<T>
where
T: Default,
{
pub fn new(width: usize, height: usize) -> Self {
let mut data = Vec::new();
for _ in 0..(width * height) {
data.push(T::default());
}
Self {
width,
height,
data,
}
}
fn index(&self, x: usize, y: usize) -> usize {
x + y * self.width
}
pub fn get(&self, x: usize, y: usize) -> &T {
assert!(
x < self.width,
"assertion failed: x < self.width; x: {}, self.width: {}",
x,
self.width
);
assert!(
y < self.height,
"assertion failed: y < self.height; y: {}, self.height: {}",
y,
self.height
);
let i = self.index(x, y);
self.data.get(i).unwrap()
}
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut T {
assert!(
x < self.width,
"assertion failed: x < self.width; x: {}, self.width: {}",
x,
self.width
);
assert!(
y < self.height,
"assertion failed: y < self.height; y: {}, self.height: {}",
y,
self.height
);
let i = self.index(x, y);
self.data.get_mut(i).unwrap()
}
pub fn set(&mut self, x: usize, y: usize, item: T) {
*self.get_mut(x, y) = item;
}
}

View file

@ -0,0 +1,5 @@
pub mod arena;
pub mod map;
pub use arena::*;
pub use map::*;

View file

@ -0,0 +1,278 @@
use std::fmt::Debug;
use super::PriorityQueue;
type Index = u32;
pub struct FibonacciHeap<I> {
min: Option<Index>,
data: Vec<Option<Node<I>>>,
free: Vec<Index>,
temp: [Option<Index>; 64],
}
impl<I: std::fmt::Debug> std::fmt::Debug for FibonacciHeap<I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct PrintData<'a, I>(&'a Vec<Option<Node<I>>>);
impl<'a, I: Debug> Debug for PrintData<'a, I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries(self.0.iter().enumerate().filter_map(|d| {
if let (i, Some(c)) = d {
Some((i, c))
} else {
None
}
}))
.finish()
}
}
f.debug_struct("FibonacciHeap")
.field("min", &self.min)
.field("data", &PrintData(&self.data))
.field("free", &self.free)
.finish()
}
}
#[derive(Debug)]
pub struct FibonacciHeapHandle(Index);
#[derive(Debug)]
struct Node<I> {
item: I,
parent: Index,
left: Index,
right: Index,
child: Index,
mark: bool,
rank: u16,
}
impl<I> Node<I> {
fn new(item: I) -> Self {
Self {
item,
parent: 0,
left: 0,
right: 0,
child: 0,
mark: false,
rank: 0,
}
}
}
impl<I> FibonacciHeap<I>
where
I: PartialOrd,
{
fn add_to_list(&mut self, list: Index, i: Index) {
let right = self.get(list).right;
let n = self.get_mut(i);
n.right = right;
n.left = list;
self.get_mut(right).left = i;
self.get_mut(list).right = i;
}
fn remove_from_list(&mut self, i: Index) -> Option<Index> {
let left = self.get(i).left;
if left == i {
return None;
}
let right = self.get(i).right;
self.get_mut(left).right = right;
self.get_mut(right).left = left;
Some(right)
}
fn insert_tree(&mut self, i: Index) {
if let Some(min) = self.min {
self.add_to_list(min, i);
self.get_mut(i).parent = i;
if self.get(i).item < self.get(min).item {
self.min = Some(i);
}
} else {
let m = self.get_mut(i);
m.parent = i;
m.left = i;
m.right = i;
self.min = Some(i);
}
}
fn union(&mut self, a: Index, b: Index) -> Index {
let min;
let max;
if self.get(a).item < self.get(b).item {
min = a;
max = b;
} else {
min = b;
max = a;
}
if self.get(min).rank == 0 {
self.get_mut(min).child = max;
self.get_mut(min).rank = 1;
let m = self.get_mut(max);
m.parent = min;
m.left = max;
m.right = max;
} else {
self.get_mut(max).parent = min;
self.add_to_list(self.get(min).child, max);
self.get_mut(min).rank += 1;
}
min
}
fn insert_temp(&mut self, i: Index) {
let rank = self.get(i).rank;
if let Some(t) = self.temp[rank as usize].take() {
let m = self.union(t, i);
self.insert_temp(m);
} else {
self.temp[rank as usize] = Some(i);
}
}
fn insert_data(&mut self, data: I) -> Index {
if let Some(i) = self.free.pop() {
self.data[i as usize] = Some(Node::new(data));
i
} else {
let i = self.data.len() as Index;
self.data.push(Some(Node::new(data)));
i
}
}
fn get(&self, i: Index) -> &Node<I> {
self.data.get(i as usize).unwrap().as_ref().unwrap()
}
fn get_mut(&mut self, i: Index) -> &mut Node<I> {
self.data.get_mut(i as usize).unwrap().as_mut().unwrap()
}
fn remove_data(&mut self, i: Index) -> I {
let n = self.data[i as usize].take().unwrap();
self.free.push(i);
n.item
}
fn cut(&mut self, i: Index) {
if self.get(i).item < self.get(self.min.unwrap()).item {
self.min = Some(i);
}
let parent = self.get(i).parent;
if parent == i {
return;
}
let c = self.remove_from_list(i);
if self.get(parent).rank > 1 {
self.get_mut(parent).child = c.unwrap();
}
self.get_mut(parent).rank -= 1;
self.add_to_list(self.min.unwrap(), i);
self.get_mut(i).mark = false;
self.get_mut(i).parent = i;
if self.get(parent).mark {
self.cut(parent);
} else {
self.get_mut(parent).mark = true;
}
}
}
impl<I> PriorityQueue<I> for FibonacciHeap<I>
where
I: PartialOrd,
{
type Handle = FibonacciHeapHandle;
fn new() -> Self {
FibonacciHeap {
min: None,
data: Vec::new(),
free: Vec::new(),
temp: [None; 64],
}
}
fn insert(&mut self, item: I) -> Self::Handle {
let i = self.insert_data(item);
self.insert_tree(i);
FibonacciHeapHandle(i)
}
fn pop_min(&mut self) -> Option<I> {
if let Some(min) = self.min.take() {
if self.get(min).rank != 0 {
let mut c = self.get(min).child;
loop {
let t = c;
c = self.get(c).right;
self.add_to_list(min, t);
self.get_mut(t).parent = t;
self.get_mut(t).mark = false;
if c == self.get(min).child {
break;
}
}
}
if let Some(m) = self.remove_from_list(min) {
let mut c = m;
loop {
let t = c;
c = self.get(c).right;
self.insert_temp(t);
if c == m {
break;
}
}
for i in 0..self.temp.len() {
if let Some(i) = self.temp[i].take() {
if let Some(min) = self.min {
self.add_to_list(min, i);
if self.get(i).item < self.get(min).item {
self.min = Some(i);
}
} else {
self.get_mut(i).left = i;
self.get_mut(i).right = i;
self.min = Some(i);
}
}
}
}
Some(self.remove_data(min))
} else {
None
}
}
fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut I)) {
let h = self.get_mut(handle.0);
f(&mut h.item);
self.cut(handle.0);
}
}

View file

@ -0,0 +1,232 @@
use std::fmt::Debug;
pub mod fibonacci_heap;
pub trait PriorityQueue<Item>
where
Item: PartialOrd,
{
type Handle;
fn new() -> Self;
// fn with_capacity() -> Self {
// Self::new()
// }
fn insert(&mut self, item: Item) -> Self::Handle;
fn pop_min(&mut self) -> Option<Item>;
fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item));
}
#[derive(Debug)]
pub struct BinaryHeap<Item> {
nextfree: usize,
data: Vec<BinaryHeapEntry<Item>>,
}
#[derive(Debug)]
struct BinaryHeapEntry<Item> {
id: usize,
item: Item,
}
impl<Item> BinaryHeap<Item>
where
Item: PartialOrd,
{
fn downheap(&mut self, index: usize) {
let left = 2 * index + 1;
let right = 2 * index + 2;
if right < self.data.len() {
let smaller = if self.data[left].item < self.data[right].item {
left
} else {
right
};
if self.data[index].item > self.data[smaller].item {
self.data.swap(index, smaller);
self.downheap(smaller);
}
} else if left < self.data.len() && self.data[index].item > self.data[left].item {
self.data.swap(index, left);
self.downheap(left);
}
}
fn upheap(&mut self, index: usize) {
if index > 0 {
let parent = (index - 1) / 2;
if self.data[parent].item > self.data[index].item {
self.data.swap(parent, index);
self.upheap(parent);
}
}
}
fn search(&self, id: usize) -> Option<usize> {
for (i, d) in self.data.iter().enumerate() {
if d.id == id {
return Some(i);
}
}
None
}
}
impl<Item> PriorityQueue<Item> for BinaryHeap<Item>
where
Item: PartialOrd + Clone,
{
type Handle = usize;
fn insert(&mut self, item: Item) -> Self::Handle {
self.data.push(BinaryHeapEntry {
id: self.nextfree,
item: item.clone(),
});
self.upheap(self.data.len() - 1);
self.nextfree += 1;
self.nextfree - 1
}
fn pop_min(&mut self) -> Option<Item> {
if self.data.is_empty() {
None
} else {
let d = self.data.swap_remove(0);
self.downheap(0);
Some(d.item)
}
}
fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)) {
if let Some(index) = self.search(*handle) {
f(&mut self.data[index].item);
self.upheap(index);
}
}
fn new() -> Self {
Self {
data: Vec::new(),
nextfree: 0,
}
}
}
#[derive(Debug)]
pub struct Trace<P> {
inner: P,
}
impl<P, I> PriorityQueue<I> for Trace<P>
where
I: PartialOrd + Clone + Debug,
P: PriorityQueue<I>,
{
type Handle = P::Handle;
fn new() -> Self {
println!("New priority queue.");
Self { inner: P::new() }
}
fn insert(&mut self, item: I) -> Self::Handle {
println!("Insert: {item:?}");
self.inner.insert(item)
}
fn pop_min(&mut self) -> Option<I> {
let min = self.inner.pop_min();
println!("Pop min: {min:?}");
min
}
fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut I)) {
self.inner.decrease_key(handle, |i| {
let old_i = i.clone();
f(i);
println!("Decrease key: {old_i:?} -> {i:?}");
})
}
}
#[cfg(test)]
mod tests {
use super::fibonacci_heap::FibonacciHeap;
use super::BinaryHeap;
use super::PriorityQueue;
macro_rules! test_generics {
($($fun_mul:ident )+ ; $gen:ident $($gen_mul:ident)+) => {
test_generics!(@internal_mod $($fun_mul )*; $gen);
test_generics!($($fun_mul )*; $($gen_mul )*);
};
($($fun_mul:ident )+ ;$gen:ident) => {
test_generics!(@internal_mod $($fun_mul )*; $gen);
};
(@internal_mod $($fun_mul:ident )+; $gen:ident) => {
#[allow(non_snake_case)]
mod $gen {
test_generics!(@internal $($fun_mul )*; $gen);
}
};
(@internal $fun:ident $($fun_mul:ident )+; $gen:ident) => {
test_generics!(@internal $fun; $gen);
test_generics!(@internal $($fun_mul )*; $gen);
};
(@internal $fun:ident; $gen:ident) => {
#[test]
fn $fun() {
super::super::$fun::<super::super::$gen<usize>>();
}
}
}
mod test_macro {
test_generics!(basic_generic decrease_key_generic; BinaryHeap FibonacciHeap);
}
fn basic_generic<T: PriorityQueue<usize>>() {
let mut q = T::new();
q.insert(2);
q.insert(3);
q.insert(10);
q.insert(12);
q.insert(9);
q.insert(6);
q.insert(4);
q.insert(5);
q.insert(8);
q.insert(7);
q.insert(11);
q.insert(1);
assert_eq!(q.pop_min(), Some(1));
assert_eq!(q.pop_min(), Some(2));
assert_eq!(q.pop_min(), Some(3));
assert_eq!(q.pop_min(), Some(4));
assert_eq!(q.pop_min(), Some(5));
assert_eq!(q.pop_min(), Some(6));
assert_eq!(q.pop_min(), Some(7));
assert_eq!(q.pop_min(), Some(8));
assert_eq!(q.pop_min(), Some(9));
assert_eq!(q.pop_min(), Some(10));
assert_eq!(q.pop_min(), Some(11));
assert_eq!(q.pop_min(), Some(12));
assert_eq!(q.pop_min(), None);
}
fn decrease_key_generic<T: PriorityQueue<usize>>() {
let mut q = T::new();
q.insert(4);
let h = q.insert(5);
q.decrease_key(&h, |i| *i -= 3);
assert_eq!(q.pop_min(), Some(2));
assert_eq!(q.pop_min(), Some(4));
}
}