From f284b692cc6b3d90506a9446ef020f9b6623f875 Mon Sep 17 00:00:00 2001 From: hal8174 Date: Wed, 21 Aug 2024 01:37:20 +0200 Subject: [PATCH] Initial layout implementation. --- Cargo.lock | 88 +++++++++++++- Cargo.toml | 1 + examples/layout.rs | 25 ++++ src/belt_finding/common.rs | 28 ++++- src/belt_finding/mod.rs | 4 +- src/layout/mod.rs | 233 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 7 files changed, 374 insertions(+), 6 deletions(-) create mode 100644 examples/layout.rs create mode 100644 src/layout/mod.rs diff --git a/Cargo.lock b/Cargo.lock index eadc986..78a6dbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,12 @@ version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cast" version = "0.3.0" @@ -274,6 +280,7 @@ dependencies = [ "clap 4.5.3", "criterion", "flate2", + "rand", "serde", "serde_json", "termcolor", @@ -289,6 +296,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "half" version = "1.8.3" @@ -342,9 +360,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "log" @@ -416,6 +434,15 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -434,6 +461,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rayon" version = "1.9.0" @@ -612,6 +669,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -772,3 +835,24 @@ name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index cc9364c..d5f56d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ harness = false base64 = "0.21.5" clap = { version = "4.4.8", features = ["derive"] } flate2 = "1.0.28" +rand = { version = "0.8.5", features = ["small_rng"] } serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" termcolor = "1.4.1" diff --git a/examples/layout.rs b/examples/layout.rs new file mode 100644 index 0000000..ddcbf3a --- /dev/null +++ b/examples/layout.rs @@ -0,0 +1,25 @@ +use factorio_blueprint::{ + belt_finding::common::Position, + layout::{Layout, Problem}, +}; +use rand::SeedableRng; + +fn main() { + let mut p = Problem::new(Position::new(10, 10)); + + let b1 = p.add_block(Position::new(3, 2)); + let b2 = p.add_block(Position::new(5, 2)); + let b3 = p.add_block(Position::new(5, 7)); + + p.add_connection(b1, Position::new(0, 0), b2, Position::new(0, 0)); + p.add_connection(b2, Position::new(3, 1), b3, Position::new(4, 6)); + + for i in 0..10 { + let mut rng = rand::rngs::SmallRng::seed_from_u64(i); + + let l = Layout::new(&p, &mut rng); + + println!("Seed: {i}, Score {}", l.score()); + l.print(); + } +} diff --git a/src/belt_finding/common.rs b/src/belt_finding/common.rs index fde0ded..d496a1e 100644 --- a/src/belt_finding/common.rs +++ b/src/belt_finding/common.rs @@ -1,6 +1,7 @@ use core::panic; use std::io::{self, Write}; +use rand::prelude::Distribution; use termcolor::{ColorSpec, StandardStream, WriteColor}; pub type PositionType = i32; @@ -89,6 +90,19 @@ impl Direction { } } +impl Distribution for rand::distributions::Standard { + fn sample(&self, rng: &mut R) -> Direction { + let a = [ + Direction::Up, + Direction::Right, + Direction::Down, + Direction::Left, + ]; + let r = rng.gen_range(0..4); + a[r] + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Position { pub x: PositionType, @@ -132,6 +146,16 @@ impl Position { } } +impl std::ops::Add for Position { + type Output = Position; + + fn add(mut self, rhs: Self) -> Self::Output { + self.x += rhs.x; + self.y += rhs.y; + self + } +} + impl std::ops::Sub for Position { type Output = Position; @@ -208,8 +232,8 @@ where { let stdout = &mut StandardStream::stdout(termcolor::ColorChoice::Always); - let width_digits = width.ilog10() + 1; - let height_digits = height.ilog10() + 1; + let width_digits = (width - 1).ilog10() + 1; + let height_digits = (height - 1).ilog10() + 1; // print header for i in 0..width_digits { diff --git a/src/belt_finding/mod.rs b/src/belt_finding/mod.rs index c030b4c..9015a1d 100644 --- a/src/belt_finding/mod.rs +++ b/src/belt_finding/mod.rs @@ -128,7 +128,7 @@ impl Problem { } } -static COLORS: Cyclic = Cyclic([ +pub static COLORS: Cyclic = Cyclic([ Color::Red, Color::Green, Color::Yellow, @@ -137,7 +137,7 @@ static COLORS: Cyclic = Cyclic([ Color::Cyan, ]); -struct Cyclic([T; N]); +pub struct Cyclic([T; N]); impl Index for Cyclic { type Output = T; diff --git a/src/layout/mod.rs b/src/layout/mod.rs new file mode 100644 index 0000000..19dcdc1 --- /dev/null +++ b/src/layout/mod.rs @@ -0,0 +1,233 @@ +use crate::{ + belt_finding::{ + common::{print_map, Direction, Position}, + COLORS, + }, + misc::Map, +}; + +use rand::Rng; +use termcolor::ColorSpec; + +#[derive(Debug)] +struct Block { + size: Position, + input: Vec, + output: Vec, +} + +#[derive(Debug)] +struct Interface { + offset: Position, + // dir: Direction, + target: (usize, usize), +} + +#[derive(Debug)] +pub struct Problem { + size: Position, + blocks: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub struct BlockHandle(usize); + +#[derive(Debug)] +pub struct Layout<'a> { + problem: &'a Problem, + blocks: Vec<(Position, Direction)>, +} + +impl Problem { + pub fn new(size: Position) -> Self { + Self { + size, + blocks: 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)); + + Layout { problem, blocks } + } + + fn place_block( + problem: &'_ Problem, + blocks: &mut Vec<(Position, Direction)>, + 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::(); + + 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), + ), + }; + + if blocks + .iter() + .enumerate() + .all(|(i, (p, d))| !Self::collision((&problem.blocks[i], *p, *d), (b, pos, dir))) + { + blocks.push((pos, dir)); + + if Self::place_block(problem, blocks, rng) { + return true; + } + + blocks.pop(); + } + } + + false + } + + /// Mutate existing layout, creating a valid layout + pub fn mutate(&self, rng: &mut R) -> Self { + todo!() + } + + fn collision( + block1: (&Block, Position, Direction), + block2: (&Block, Position, Direction), + ) -> bool { + let (npos1, nsize1) = Self::normalize_pos(block1); + let (npos2, nsize2) = Self::normalize_pos(block2); + + npos1.x < npos2.x + nsize2.x + && npos1.x + nsize1.x > npos2.x + && npos1.y < npos2.y + nsize2.y + && npos1.y + nsize1.y > npos2.y + } + + fn normalize_pos(block: (&Block, Position, Direction)) -> (Position, Position) { + let npos = match block.2 { + Direction::Up => block.1, + Direction::Right => block.1.in_direction(&Direction::Left, block.0.size.y - 1), + Direction::Down => block.1 - (block.0.size - Position::new(1, 1)), + Direction::Left => block.1.in_direction(&Direction::Up, block.0.size.x - 1), + }; + let nsize = match block.2 { + Direction::Up | Direction::Down => block.0.size, + Direction::Right | Direction::Left => Position { + x: block.0.size.y, + y: block.0.size.x, + }, + }; + + (npos, nsize) + } + + pub fn score(&self) -> i32 { + let mut sum = 0; + for ((pos, dir), b) in self.blocks.iter().zip(self.problem.blocks.iter()) { + for i in &b.output { + let startpos = Self::transform(*pos, *dir, i.offset); + let endpos = Self::transform( + self.blocks[i.target.0].0, + self.blocks[i.target.0].1, + self.problem.blocks[i.target.0].input[i.target.1].offset, + ); + + sum += (startpos.x - endpos.x).abs() + (startpos.y - endpos.y).abs(); + } + } + + sum + } + + fn transform(pos: Position, dir: Direction, offset: Position) -> Position { + match dir { + Direction::Up => pos + offset, + Direction::Right => pos + Position::new(-offset.y, offset.x), + Direction::Down => pos - offset, + Direction::Left => pos + Position::new(offset.y, -offset.x), + } + } + + pub fn print(&self) { + let mut m: Map> = + Map::new(self.problem.size.x as usize, self.problem.size.y as usize); + + for (i, ((p, d), b)) in self + .blocks + .iter() + .zip(self.problem.blocks.iter()) + .enumerate() + { + let (npos, nsize) = Self::normalize_pos((b, *p, *d)); + + for x in npos.x..(npos.x + nsize.x) { + for y in npos.y..(npos.y + nsize.y) { + m.set(x as usize, y as usize, Some(i)); + } + } + } + + let _ = print_map(self.problem.size.x, self.problem.size.y, |x, y| { + if let Some(i) = m.get(x as usize, y as usize) { + let mut color = ColorSpec::new(); + color.set_fg(Some(COLORS[*i])); + (color, "#") + } else { + (ColorSpec::new(), " ") + } + }); + } +} diff --git a/src/lib.rs b/src/lib.rs index da96512..9f3bbe5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod belt_finding; pub mod blueprint; pub mod graph; +pub mod layout; pub mod misc; pub mod priority_queue;