Add initial automatic roboport placement

This commit is contained in:
Henning Lindemann 2025-03-18 22:46:28 +01:00
parent 1680fef14d
commit caea696dd7
6 changed files with 203 additions and 1 deletions

View file

@ -537,6 +537,8 @@ pub fn generate_factory<L: Layouter, P: Pathfinder + Sync, R: Rng + SeedableRng
b.add_blueprint(multistation_blueprint);
b.add_roboport_network();
b.connect_power_networks();
b

View file

@ -11,6 +11,7 @@ use factorio_core::{
use std::{collections::HashMap, sync::atomic::AtomicUsize};
mod power_connection;
mod roboports;
mod visualize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -153,6 +154,7 @@ pub enum EntityType {
Rail {
rail_type: RailType,
},
Roboport,
Unknown {
name: String,
size: Position,
@ -254,6 +256,10 @@ impl Entity {
Self::new_quarter_direction(EntityType::Rail { rail_type }, position, direction.into())
}
pub fn new_roboport(position: Position) -> Self {
Self::new(EntityType::Roboport, position, Direction::Up)
}
pub fn new_unknown(
name: impl AsRef<str>,
position: Position,
@ -299,6 +305,7 @@ impl Entity {
size: _,
} => name.clone(),
EntityType::Rail { rail_type } => rail_type.string(),
EntityType::Roboport => "roboport".to_string(),
}
}
@ -348,6 +355,7 @@ impl Entity {
} => *size,
EntityType::ElectricPole(electric_pole_type) => electric_pole_type.size(),
EntityType::Rail { rail_type } => rail_type.size(),
EntityType::Roboport => Position::new(8, 8),
_ => Position::new(2, 2),
}
}

View file

@ -0,0 +1,50 @@
use factorio_core::{misc::PositionMap, prelude::Position};
use factorio_graph::{
priority_queue::binary_heap::FastBinaryHeap, set_cover::greedy_set_cover_priority_queue,
};
use crate::abstraction::Entity;
use super::Blueprint;
impl Blueprint {
pub fn add_roboport_network(&mut self) {
let aabb = self.get_aabb().unwrap();
let universe = self.entities.len();
let map = self.placibility_map();
let mut roboports = Vec::new();
let mut sets = Vec::new();
for (x, y) in (aabb.min().x..=aabb.max().x)
.filter(move |x| x.rem_euclid(2) == 0)
.flat_map(|x| {
(aabb.min().y..=aabb.max().y)
.filter(move |y| y.rem_euclid(2) == 0)
.map(move |y| (x, y))
})
{
if map.placeable(Position::new(x, y), Position::new(8, 8)) {
let mut coverage = Vec::new();
for (i, (_, e)) in self.entities.iter().enumerate() {
if e.position.x.abs_diff(x) <= 110 && e.position.y.abs_diff(y) <= 110 {
coverage.push(i);
}
}
roboports.push(Position::new(x, y));
sets.push(coverage);
}
}
dbg!(universe, sets.len());
let res = greedy_set_cover_priority_queue::<_, FastBinaryHeap<_>>(universe, &sets);
for r in res {
self.add_entity(Entity::new_roboport(roboports[r]));
}
}
}

View file

@ -1,2 +1,3 @@
pub mod priority_queue;
pub mod set_cover;
pub mod wheighted_graph;

View file

@ -18,9 +18,9 @@ where
#[cfg(test)]
mod tests {
use super::PriorityQueue;
use super::binary_heap::{BinaryHeap, FastBinaryHeap};
use super::fibonacci_heap::FibonacciHeap;
use super::PriorityQueue;
macro_rules! test_generics {
($($fun_mul:ident )+ ; $gen:ident $($gen_mul:ident)+) => {

View file

@ -0,0 +1,141 @@
use std::ops::Deref;
use crate::priority_queue::PriorityQueue;
#[derive(Debug, Clone, Copy)]
pub struct SetUncovered {
setIndex: usize,
uncoveredElements: usize,
}
impl PartialEq for SetUncovered {
fn eq(&self, other: &Self) -> bool {
self.uncoveredElements.eq(&other.uncoveredElements)
}
}
impl PartialOrd for SetUncovered {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.uncoveredElements
.partial_cmp(&other.uncoveredElements)
.map(|o| o.reverse())
}
}
pub fn greedy_set_cover_simple<S>(universe: usize, sets: &[S]) -> Vec<usize>
where
S: Deref<Target = [usize]>,
{
assert!(sets.len() > 0);
let mut r = Vec::new();
let mut covered = vec![false; universe];
let mut covered_count = 0;
while covered_count < universe {
let i = sets
.iter()
.enumerate()
.filter(|(i, _)| !r.contains(i))
.max_by_key(|&(_, s)| s.iter().filter(|&&e| !covered[e]).count())
.unwrap()
.0;
r.push(i);
for &e in sets[i].deref() {
if !covered[e] {
covered_count += 1;
covered[e] = true;
}
}
}
r
}
pub fn greedy_set_cover_priority_queue<S, P>(universe: usize, sets: &[S]) -> Vec<usize>
where
S: Deref<Target = [usize]>,
P: PriorityQueue<SetUncovered>,
{
let mut covered = vec![false; universe];
let mut covered_count = 0;
let mut p = P::new();
let mut handles = sets
.iter()
.enumerate()
.map(|(i, s)| {
Some(p.insert(SetUncovered {
setIndex: i,
uncoveredElements: s.len(),
}))
})
.collect::<Vec<_>>();
let mut r = Vec::new();
while covered_count < universe {
let SetUncovered {
setIndex: i,
uncoveredElements: _,
} = p.pop_min().unwrap();
r.push(i);
handles[i] = None;
for (h, s) in handles
.iter()
.zip(sets.iter())
.filter_map(|(h, s)| h.as_ref().map(|h| (h, s)))
{
let mut decrease = 0;
for &e in sets[i].deref() {
if !covered[e] && s.contains(&e) {
decrease += 1;
}
}
// dbg!(decrease, i, s.deref());
p.decrease_key(h, |e| e.uncoveredElements -= decrease);
}
for &e in sets[i].deref() {
if !covered[e] {
covered_count += 1;
covered[e] = true;
}
}
}
r
}
#[cfg(test)]
mod test {
use crate::{
priority_queue::binary_heap::FastBinaryHeap, set_cover::greedy_set_cover_priority_queue,
};
use super::greedy_set_cover_simple;
#[test]
fn wikipedia() {
let u = 5;
let s = vec![vec![0, 1, 2], vec![1, 3], vec![2, 3], vec![3, 4]];
let mut r = greedy_set_cover_simple::<_>(u, &s);
r.sort();
assert_eq!(&r, &[0, 3]);
let mut r2 = greedy_set_cover_priority_queue::<_, FastBinaryHeap<_>>(u, &s);
r2.sort();
assert_eq!(&r, &r2);
}
}