Refactor layouting into separate crate
This commit is contained in:
parent
c3bb980fcf
commit
5c8010c23b
15 changed files with 124 additions and 55 deletions
|
|
@ -24,7 +24,6 @@ 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"
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
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};
|
||||
|
|
@ -34,44 +33,44 @@ impl Problem {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_layout(l: &Layout) -> Self {
|
||||
let mut p = Self::new(l.problem.size.x as usize, l.problem.size.y as usize);
|
||||
// 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();
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
// 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),
|
||||
);
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// p
|
||||
// }
|
||||
|
||||
pub fn add_connection(&mut self, start: (Position, Direction), end: (Position, Direction)) {
|
||||
self.start.push(start);
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
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,574 +0,0 @@
|
|||
use crate::belt_finding::conflict_avoidance::ConflictAvoidance;
|
||||
use factorio_core::{
|
||||
pathfield::PathField,
|
||||
prelude::*,
|
||||
visualize::{image_grid, Color, Symbol, Visualization, Visualize},
|
||||
};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{sync::atomic::AtomicU32, 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 Visualize for PathLayout<'_> {
|
||||
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 Visualize for Layout<'_> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
pub mod belt_finding;
|
||||
pub mod graph;
|
||||
pub mod layout;
|
||||
pub mod misc;
|
||||
pub mod priority_queue;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue