From 295490858b65cf2fe3855e919389412b32fdcd24 Mon Sep 17 00:00:00 2001 From: hal8174 Date: Thu, 30 Jan 2025 23:22:37 +0100 Subject: [PATCH] Add Layout trait --- Cargo.lock | 94 ++++++++++----- factorio-core/src/beltoptions.rs | 3 +- factorio-layout/new_layout.yaml | 49 ++++++++ factorio-layout/src/bin/new_layout.rs | 42 +++++++ factorio-layout/src/genetic_algorithm_v1.rs | 0 factorio-layout/src/genetic_algorithm_v2.rs | 21 ++++ factorio-layout/src/lib.rs | 54 +++++++++ factorio-layout/src/misc.rs | 119 +++++++++++++++++++ factorio-layout/src/valid_layout.rs | 44 +++++++ factorio-pathfinding/src/belt_finding/mod.rs | 12 +- factorio-pathfinding/src/examples.rs | 6 +- factorio-pathfinding/src/lib.rs | 4 +- 12 files changed, 407 insertions(+), 41 deletions(-) create mode 100644 factorio-layout/new_layout.yaml create mode 100644 factorio-layout/src/bin/new_layout.rs create mode 100644 factorio-layout/src/genetic_algorithm_v1.rs create mode 100644 factorio-layout/src/genetic_algorithm_v2.rs create mode 100644 factorio-layout/src/misc.rs create mode 100644 factorio-layout/src/valid_layout.rs diff --git a/Cargo.lock b/Cargo.lock index f8796c5..b6bb6a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,9 +259,9 @@ checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" @@ -327,9 +327,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -337,9 +337,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -449,9 +449,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "csv" @@ -552,7 +552,7 @@ version = "0.1.0" dependencies = [ "base64 0.22.1", "bon", - "clap 4.5.26", + "clap 4.5.27", "factorio-core", "flate2", "serde", @@ -563,7 +563,7 @@ dependencies = [ name = "factorio-blueprint-generator" version = "0.1.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.27", "factorio-blueprint", "factorio-core", "factorio-pathfinding", @@ -574,7 +574,7 @@ dependencies = [ name = "factorio-core" version = "0.1.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.27", "criterion", "image", "proptest", @@ -588,7 +588,7 @@ dependencies = [ name = "factorio-layout" version = "0.1.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.27", "factorio-core", "factorio-pathfinding", "image", @@ -605,7 +605,7 @@ version = "0.1.0" dependencies = [ "base64 0.21.7", "bon", - "clap 4.5.26", + "clap 4.5.27", "criterion", "factorio-blueprint", "factorio-core", @@ -659,7 +659,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] @@ -762,9 +774,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", @@ -862,9 +874,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libfuzzer-sys" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", @@ -1241,7 +1253,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -1366,9 +1378,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.8.0", "errno", @@ -1397,9 +1409,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -1442,9 +1454,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.135" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -1560,13 +1572,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", - "getrandom", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys", @@ -1693,9 +1705,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-linebreak" @@ -1763,6 +1775,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1943,13 +1964,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.24" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/factorio-core/src/beltoptions.rs b/factorio-core/src/beltoptions.rs index f8d3ae9..a181583 100644 --- a/factorio-core/src/beltoptions.rs +++ b/factorio-core/src/beltoptions.rs @@ -1,6 +1,7 @@ use clap::ValueEnum; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize, Deserialize)] pub enum Beltspeed { Normal, Fast, diff --git a/factorio-layout/new_layout.yaml b/factorio-layout/new_layout.yaml new file mode 100644 index 0000000..0b7eb88 --- /dev/null +++ b/factorio-layout/new_layout.yaml @@ -0,0 +1,49 @@ + +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 + lanes: 1 + beltspeed: Normal + - startblock: 0 + startpoint: 0 + endblock: 2 + endpoint: 0 + lanes: 1 + beltspeed: Normal diff --git a/factorio-layout/src/bin/new_layout.rs b/factorio-layout/src/bin/new_layout.rs new file mode 100644 index 0000000..0757be3 --- /dev/null +++ b/factorio-layout/src/bin/new_layout.rs @@ -0,0 +1,42 @@ +use std::path::PathBuf; + +use clap::Parser; +use factorio_core::prelude::Position; +use factorio_layout::{Layouter, valid_layout::ValidLayout}; +use factorio_pathfinding::belt_finding::ConflictAvoidance; +use rand::{SeedableRng, rngs::SmallRng}; + +#[derive(Debug, Parser)] +struct Args { + #[clap(short, long, default_value_t = 0)] + seed: u64, + + path: PathBuf, + // #[command(subcommand)] + // subcommand: Commands, +} + +fn main() { + let args = Args::parse(); + + let file = std::fs::File::open(args.path).unwrap(); + + let problem = serde_yaml::from_reader(file).unwrap(); + + let l = ValidLayout { + max_tries: 100, + retries: 10, + start_size: Position::new(10, 10), + growth: Position::new(2, 2), + }; + + let p = ConflictAvoidance { + timeout: Some(std::time::Duration::from_millis(100)), + }; + + let mut rng = SmallRng::seed_from_u64(args.seed); + + let r = l.layout(&problem, &p, &mut rng); + + dbg!(r); +} diff --git a/factorio-layout/src/genetic_algorithm_v1.rs b/factorio-layout/src/genetic_algorithm_v1.rs new file mode 100644 index 0000000..e69de29 diff --git a/factorio-layout/src/genetic_algorithm_v2.rs b/factorio-layout/src/genetic_algorithm_v2.rs new file mode 100644 index 0000000..66348b1 --- /dev/null +++ b/factorio-layout/src/genetic_algorithm_v2.rs @@ -0,0 +1,21 @@ +use factorio_pathfinding::Pathfinder; +use rand::Rng; + +use crate::Layouter; + +pub struct GeneticAlgorithmV2 { + pub new_layouts: usize, + pub mutation_timeout: usize, + pub max_mutations: usize, +} + +impl Layouter for GeneticAlgorithmV2 { + fn layout( + &self, + input: &crate::LayoutInput, + pathfinder: &P, + rng: &mut R, + ) -> Option { + todo!() + } +} diff --git a/factorio-layout/src/lib.rs b/factorio-layout/src/lib.rs index dd64619..7368c99 100644 --- a/factorio-layout/src/lib.rs +++ b/factorio-layout/src/lib.rs @@ -1 +1,55 @@ +use factorio_core::{beltoptions::Beltspeed, pathfield::PathField, prelude::*}; +use factorio_pathfinding::Pathfinder; +use rand::Rng; +use serde::{Deserialize, Serialize}; + +pub mod block; +pub mod genetic_algorithm_v1; +pub mod genetic_algorithm_v2; pub mod layout; +pub mod misc; +pub mod valid_layout; + +#[derive(Debug, Serialize, Deserialize)] +pub struct MacroBlock { + pub size: Position, + pub input: Vec, + pub output: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Interface { + pub offset: Position, + pub dir: Direction, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Connection { + pub startblock: usize, + pub startpoint: usize, + pub endblock: usize, + pub endpoint: usize, + pub lanes: usize, + pub beltspeed: Beltspeed, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LayoutInput { + pub blocks: Vec, + pub connections: Vec, +} + +#[derive(Debug)] +pub struct LayoutResult { + pub positions: Vec, + pub path_result: Vec>, +} + +pub trait Layouter { + fn layout( + &self, + input: &LayoutInput, + pathfinder: &P, + rng: &mut R, + ) -> Option; +} diff --git a/factorio-layout/src/misc.rs b/factorio-layout/src/misc.rs new file mode 100644 index 0000000..004d92d --- /dev/null +++ b/factorio-layout/src/misc.rs @@ -0,0 +1,119 @@ +use crate::LayoutInput; +use factorio_core::prelude::*; +use factorio_pathfinding::{Connection, PathInput, examples::HashMapMap}; +use rand::Rng; + +pub fn initally_set_blocks( + input: &LayoutInput, + size: Position, + retries: usize, + rng: &mut impl Rng, +) -> Option> { + let mut blocks = Vec::new(); + + let mut aabbs = Vec::new(); + + if place_block(input, size, retries, &mut blocks, &mut aabbs, rng) { + Some(blocks) + } else { + None + } +} + +fn place_block( + input: &LayoutInput, + size: Position, + retries: usize, + blocks: &mut Vec, + aabbs: &mut Vec, + rng: &mut impl Rng, +) -> bool { + if input.blocks.len() == blocks.len() { + return true; + } + + let b = &input.blocks[blocks.len()]; + for _ in 0..retries { + let dir = rng.r#gen::(); + + let pos = match dir { + Direction::Up => Position::new( + rng.gen_range(0..=(size.x - b.size.x)), + rng.gen_range(0..=(size.y - b.size.y)), + ), + Direction::Right => Position::new( + rng.gen_range((b.size.y - 1)..size.x), + rng.gen_range(0..=(size.y - b.size.x)), + ), + Direction::Down => Position::new( + rng.gen_range((b.size.x - 1)..size.x), + rng.gen_range((b.size.y - 1)..size.y), + ), + Direction::Left => Position::new( + rng.gen_range(0..=(size.x - b.size.y)), + rng.gen_range((b.size.x - 1)..size.y), + ), + }; + + let current = Block::new(pos, dir, b.size); + let current_aabb = current.get_aabb(); + + if aabbs.iter().all(|&b| !AABB::collision(b, current_aabb)) { + blocks.push(current); + aabbs.push(current_aabb); + + if place_block(input, size, retries, blocks, aabbs, rng) { + return true; + } + + blocks.pop(); + aabbs.pop(); + } + } + + false +} + +pub fn path_input_from_blocks_positions( + input: &LayoutInput, + size: Position, + block_positions: &[Block], +) -> (Vec, HashMapMap) { + let mut map = HashMapMap::new(size); + + let mut connections = Vec::new(); + + for b in block_positions { + let aabb = b.get_aabb(); + + map.set_blocked_range(aabb.min(), aabb.max(), true); + } + + for c in &input.connections { + let start_transform = block_positions[c.startblock].block_to_world(); + let start_pos = input.blocks[c.startblock].output[c.startpoint] + .offset + .transform(start_transform); + let start_dir = input.blocks[c.startblock].output[c.startpoint] + .dir + .transform(start_transform); + let end_transform = block_positions[c.endblock].block_to_world(); + let end_pos = input.blocks[c.endblock].input[c.endpoint] + .offset + .transform(end_transform); + let end_dir = input.blocks[c.endblock].input[c.endpoint] + .dir + .transform(end_transform); + + connections.push(Connection { + start_pos, + start_dir, + end_pos, + end_dir, + beltspeed: c.beltspeed, + lanes: c.lanes, + }); + } + + (connections, map) +} diff --git a/factorio-layout/src/valid_layout.rs b/factorio-layout/src/valid_layout.rs new file mode 100644 index 0000000..ad87e7b --- /dev/null +++ b/factorio-layout/src/valid_layout.rs @@ -0,0 +1,44 @@ +use factorio_core::prelude::{Position, PositionType}; +use factorio_pathfinding::Pathfinder; +use rand::Rng; + +use crate::{ + LayoutResult, Layouter, + misc::{initally_set_blocks, path_input_from_blocks_positions}, +}; + +pub struct ValidLayout { + pub max_tries: usize, + pub retries: usize, + pub start_size: Position, + pub growth: Position, +} + +impl Layouter for ValidLayout { + fn layout( + &self, + input: &crate::LayoutInput, + pathfinder: &P, + rng: &mut R, + ) -> Option { + for i in 0..self.max_tries { + let size = self.start_size + i as PositionType * self.growth; + + if let Some(blocks) = initally_set_blocks(input, size, self.retries, rng) { + let (connections, map) = path_input_from_blocks_positions(input, size, &blocks); + + if let Some(paths) = pathfinder.find_paths(factorio_pathfinding::PathInput { + connections: &connections, + map, + }) { + return Some(LayoutResult { + positions: blocks, + path_result: paths, + }); + } + } + } + + None + } +} diff --git a/factorio-pathfinding/src/belt_finding/mod.rs b/factorio-pathfinding/src/belt_finding/mod.rs index d271cdf..103380c 100644 --- a/factorio-pathfinding/src/belt_finding/mod.rs +++ b/factorio-pathfinding/src/belt_finding/mod.rs @@ -14,13 +14,13 @@ pub mod brute_force; pub mod conflict_avoidance; pub struct ConflictAvoidance { - timeout: Option, + pub timeout: Option, } impl SinglePathfinder for ConflictAvoidance { fn find_paths( &self, - input: SinglePathInput<'_, M>, + input: SinglePathInput, ) -> Option>> { let (pos, size) = input.map.area(); let mut p = Problem::new(size.x as usize, size.y as usize); @@ -34,11 +34,17 @@ impl SinglePathfinder for ConflictAvoidance { } for i in input.connections { - p.add_connection((i.start_pos, i.start_dir), (i.end_pos, i.end_dir)); + p.add_connection( + (i.start_pos, i.start_dir), + (i.end_pos.in_direction(&i.end_dir, -1), i.end_dir), + ); } + p.print_visualization(); + if p.find_path() { let mut c = conflict_avoidance::ConflictAvoidance::new(&p); + c.print_visualization(); if c.remove_all_conflicts(self.timeout) { Some(c.get_paths().to_vec()) diff --git a/factorio-pathfinding/src/examples.rs b/factorio-pathfinding/src/examples.rs index a4daa32..26f27c0 100644 --- a/factorio-pathfinding/src/examples.rs +++ b/factorio-pathfinding/src/examples.rs @@ -12,14 +12,14 @@ pub struct HashMapMap { } impl HashMapMap { - fn new(size: impl Into) -> Self { + pub fn new(size: impl Into) -> Self { Self { map: HashSet::new(), size: size.into(), } } - fn set_blocked_range( + pub fn set_blocked_range( &mut self, min_pos: impl Into, max_pos: impl Into, @@ -35,7 +35,7 @@ impl HashMapMap { } } - fn set_blocked(&mut self, pos: impl Into, block: bool) { + pub fn set_blocked(&mut self, pos: impl Into, block: bool) { let pos = pos.into(); if block { self.map.insert(pos); diff --git a/factorio-pathfinding/src/lib.rs b/factorio-pathfinding/src/lib.rs index 30ab709..965beea 100644 --- a/factorio-pathfinding/src/lib.rs +++ b/factorio-pathfinding/src/lib.rs @@ -7,8 +7,8 @@ pub mod misc; pub mod priority_queue; pub struct PathInput<'c, M> { - connections: &'c [Connection], - map: M, + pub connections: &'c [Connection], + pub map: M, } pub struct Connection {