Add Layout trait

This commit is contained in:
hal8174 2025-01-30 23:22:37 +01:00
parent 00eda50872
commit 295490858b
12 changed files with 407 additions and 41 deletions

View file

@ -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

View file

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

View file

@ -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<R: Rng, P: Pathfinder>(
&self,
input: &crate::LayoutInput,
pathfinder: &P,
rng: &mut R,
) -> Option<crate::LayoutResult> {
todo!()
}
}

View file

@ -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<Interface>,
pub output: Vec<Interface>,
}
#[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<MacroBlock>,
pub connections: Vec<Connection>,
}
#[derive(Debug)]
pub struct LayoutResult {
pub positions: Vec<Block>,
pub path_result: Vec<Vec<PathField>>,
}
pub trait Layouter {
fn layout<R: Rng, P: Pathfinder>(
&self,
input: &LayoutInput,
pathfinder: &P,
rng: &mut R,
) -> Option<LayoutResult>;
}

119
factorio-layout/src/misc.rs Normal file
View file

@ -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<Vec<Block>> {
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<Block>,
aabbs: &mut Vec<AABB>,
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::<Direction>();
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<Connection>, 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)
}

View file

@ -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<R: Rng, P: Pathfinder>(
&self,
input: &crate::LayoutInput,
pathfinder: &P,
rng: &mut R,
) -> Option<LayoutResult> {
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
}
}