Refactor into different crates
This commit is contained in:
parent
94473c64e0
commit
dfdeae5638
82 changed files with 624 additions and 647 deletions
31
factorio-pathfinding/Cargo.toml
Normal file
31
factorio-pathfinding/Cargo.toml
Normal 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"
|
||||
33
factorio-pathfinding/benches/bruteforce.rs
Normal file
33
factorio-pathfinding/benches/bruteforce.rs
Normal 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);
|
||||
91
factorio-pathfinding/examples/brute_force.rs
Normal file
91
factorio-pathfinding/examples/brute_force.rs
Normal 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
|
||||
// );
|
||||
// }
|
||||
}
|
||||
24
factorio-pathfinding/examples/decode_blueprint.rs
Normal file
24
factorio-pathfinding/examples/decode_blueprint.rs
Normal 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);
|
||||
}
|
||||
40
factorio-pathfinding/examples/priority_queue_test.rs
Normal file
40
factorio-pathfinding/examples/priority_queue_test.rs
Normal 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>>()
|
||||
}
|
||||
44
factorio-pathfinding/layout.yml
Normal file
44
factorio-pathfinding/layout.yml
Normal 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
|
||||
61
factorio-pathfinding/layout2.yml
Normal file
61
factorio-pathfinding/layout2.yml
Normal 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
|
||||
115
factorio-pathfinding/layout3.yml
Normal file
115
factorio-pathfinding/layout3.yml
Normal 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
|
||||
817
factorio-pathfinding/src/belt_finding/brute_force.rs
Normal file
817
factorio-pathfinding/src/belt_finding/brute_force.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
620
factorio-pathfinding/src/belt_finding/conflict_avoidance.rs
Normal file
620
factorio-pathfinding/src/belt_finding/conflict_avoidance.rs
Normal 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
|
||||
}
|
||||
}
|
||||
568
factorio-pathfinding/src/belt_finding/mod.rs
Normal file
568
factorio-pathfinding/src/belt_finding/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
101
factorio-pathfinding/src/bin/beltfinding.rs
Normal file
101
factorio-pathfinding/src/bin/beltfinding.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
factorio-pathfinding/src/bin/create_beltfinding_image.rs
Normal file
31
factorio-pathfinding/src/bin/create_beltfinding_image.rs
Normal 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();
|
||||
}
|
||||
98
factorio-pathfinding/src/bin/layout.rs
Normal file
98
factorio-pathfinding/src/bin/layout.rs
Normal 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(())
|
||||
}
|
||||
1
factorio-pathfinding/src/graph/mod.rs
Normal file
1
factorio-pathfinding/src/graph/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod wheighted_graph;
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
49
factorio-pathfinding/src/graph/wheighted_graph/mod.rs
Normal file
49
factorio-pathfinding/src/graph/wheighted_graph/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
235
factorio-pathfinding/src/graph/wheighted_graph/shortest_path.rs
Normal file
235
factorio-pathfinding/src/graph/wheighted_graph/shortest_path.rs
Normal 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)
|
||||
));
|
||||
}
|
||||
}
|
||||
573
factorio-pathfinding/src/layout/mod.rs
Normal file
573
factorio-pathfinding/src/layout/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
5
factorio-pathfinding/src/lib.rs
Normal file
5
factorio-pathfinding/src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod belt_finding;
|
||||
pub mod graph;
|
||||
pub mod layout;
|
||||
pub mod misc;
|
||||
pub mod priority_queue;
|
||||
52
factorio-pathfinding/src/misc/arena.rs
Normal file
52
factorio-pathfinding/src/misc/arena.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
68
factorio-pathfinding/src/misc/map.rs
Normal file
68
factorio-pathfinding/src/misc/map.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
5
factorio-pathfinding/src/misc/mod.rs
Normal file
5
factorio-pathfinding/src/misc/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod arena;
|
||||
pub mod map;
|
||||
|
||||
pub use arena::*;
|
||||
pub use map::*;
|
||||
278
factorio-pathfinding/src/priority_queue/fibonacci_heap.rs
Normal file
278
factorio-pathfinding/src/priority_queue/fibonacci_heap.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
232
factorio-pathfinding/src/priority_queue/mod.rs
Normal file
232
factorio-pathfinding/src/priority_queue/mod.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue