Initial layout implementation.

This commit is contained in:
hal8174 2024-08-21 01:37:20 +02:00
parent 1596bf180d
commit f284b692cc
7 changed files with 374 additions and 6 deletions

88
Cargo.lock generated
View file

@ -100,6 +100,12 @@ version = "3.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cast" name = "cast"
version = "0.3.0" version = "0.3.0"
@ -274,6 +280,7 @@ dependencies = [
"clap 4.5.3", "clap 4.5.3",
"criterion", "criterion",
"flate2", "flate2",
"rand",
"serde", "serde",
"serde_json", "serde_json",
"termcolor", "termcolor",
@ -289,6 +296,17 @@ dependencies = [
"miniz_oxide", "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]] [[package]]
name = "half" name = "half"
version = "1.8.3" version = "1.8.3"
@ -342,9 +360,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.153" version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]] [[package]]
name = "log" name = "log"
@ -416,6 +434,15 @@ dependencies = [
"plotters-backend", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.79" version = "1.0.79"
@ -434,6 +461,36 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rayon" name = "rayon"
version = "1.9.0" version = "1.9.0"
@ -612,6 +669,12 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.92" version = "0.2.92"
@ -772,3 +835,24 @@ name = "windows_x86_64_msvc"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 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",
]

View file

@ -19,6 +19,7 @@ harness = false
base64 = "0.21.5" base64 = "0.21.5"
clap = { version = "4.4.8", features = ["derive"] } clap = { version = "4.4.8", features = ["derive"] }
flate2 = "1.0.28" flate2 = "1.0.28"
rand = { version = "0.8.5", features = ["small_rng"] }
serde = { version = "1.0.192", features = ["derive"] } serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108" serde_json = "1.0.108"
termcolor = "1.4.1" termcolor = "1.4.1"

25
examples/layout.rs Normal file
View file

@ -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();
}
}

View file

@ -1,6 +1,7 @@
use core::panic; use core::panic;
use std::io::{self, Write}; use std::io::{self, Write};
use rand::prelude::Distribution;
use termcolor::{ColorSpec, StandardStream, WriteColor}; use termcolor::{ColorSpec, StandardStream, WriteColor};
pub type PositionType = i32; pub type PositionType = i32;
@ -89,6 +90,19 @@ impl Direction {
} }
} }
impl Distribution<Direction> for rand::distributions::Standard {
fn sample<R: rand::Rng + ?Sized>(&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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Position { pub struct Position {
pub x: PositionType, 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 { impl std::ops::Sub for Position {
type Output = Position; type Output = Position;
@ -208,8 +232,8 @@ where
{ {
let stdout = &mut StandardStream::stdout(termcolor::ColorChoice::Always); let stdout = &mut StandardStream::stdout(termcolor::ColorChoice::Always);
let width_digits = width.ilog10() + 1; let width_digits = (width - 1).ilog10() + 1;
let height_digits = height.ilog10() + 1; let height_digits = (height - 1).ilog10() + 1;
// print header // print header
for i in 0..width_digits { for i in 0..width_digits {

View file

@ -128,7 +128,7 @@ impl Problem {
} }
} }
static COLORS: Cyclic<Color, 6> = Cyclic([ pub static COLORS: Cyclic<Color, 6> = Cyclic([
Color::Red, Color::Red,
Color::Green, Color::Green,
Color::Yellow, Color::Yellow,
@ -137,7 +137,7 @@ static COLORS: Cyclic<Color, 6> = Cyclic([
Color::Cyan, Color::Cyan,
]); ]);
struct Cyclic<T, const N: usize>([T; N]); pub struct Cyclic<T, const N: usize>([T; N]);
impl<T, const N: usize> Index<usize> for Cyclic<T, N> { impl<T, const N: usize> Index<usize> for Cyclic<T, N> {
type Output = T; type Output = T;

233
src/layout/mod.rs Normal file
View file

@ -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<Interface>,
output: Vec<Interface>,
}
#[derive(Debug)]
struct Interface {
offset: Position,
// dir: Direction,
target: (usize, usize),
}
#[derive(Debug)]
pub struct Problem {
size: Position,
blocks: Vec<Block>,
}
#[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<R: Rng + ?Sized>(
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::<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),
),
};
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<R: Rng + ?Sized>(&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<Option<usize>> =
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(), " ")
}
});
}
}

View file

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