Refactor power connection to work with different electric poles

This commit is contained in:
hal8174 2025-03-05 18:35:52 +01:00
parent b4ab291884
commit 642f815f9d
8 changed files with 394 additions and 144 deletions

View file

@ -1,12 +1,16 @@
use factorio_core::{
aabb::AABB,
beltoptions::Beltspeed,
misc::PositionMap,
pathfield::PathField,
prelude::*,
quaterdirection::QuaterDirection,
visualize::{Color, Visualization},
};
use std::{collections::HashMap, sync::atomic::AtomicUsize};
use std::{
collections::{HashMap, HashSet},
sync::atomic::AtomicUsize,
};
use crate::{BlueprintEntity, BlueprintPosition};
@ -68,6 +72,13 @@ impl ElectricPoleType {
ElectricPoleType::Big | ElectricPoleType::Substation => Position::new(4, 4),
}
}
fn alignment(&self) -> PositionType {
match self {
ElectricPoleType::Small | ElectricPoleType::Medium => 1,
ElectricPoleType::Big | ElectricPoleType::Substation => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -359,12 +370,13 @@ impl Entity {
let halve_size = self.size() / 2;
match self.direction {
DirectionType::Dir(direction) => match direction {
Direction::Up | Direction::Down => {
AABB::new(self.position - halve_size, self.position + halve_size)
}
Direction::Up | Direction::Down => AABB::new(
self.position - halve_size,
self.position + halve_size - Position::new(1, 1),
),
Direction::Right | Direction::Left => AABB::new(
self.position - Position::new(halve_size.y, halve_size.x),
self.position + Position::new(halve_size.y, halve_size.x),
self.position + Position::new(halve_size.y, halve_size.x) - Position::new(1, 1),
),
},
DirectionType::QuarterDir(_) => {
@ -415,7 +427,7 @@ impl Entity {
None,
),
ElectricPoleType::Big => {
for (dx, dy) in [(-1, -1), (1, -1), (-1, 1), (1, 1)] {
for (dx, dy) in [(-2, -2), (0, -2), (-2, 0), (0, 0)] {
v.add_symbol(
(self.position + Position::new(dx, dy)) / 2 + offset,
factorio_core::visualize::Symbol::Char('l'),
@ -588,6 +600,51 @@ impl Blueprint {
.reduce(|a, b| a.combine(b))
.unwrap()
}
pub fn placeable(&self, pos: Position, size: Position) -> bool {
let aabb = AABB::new(pos - size / 2, pos + size / 2 - Position::new(1, 1));
!self
.entities
.iter()
.any(|(_, e)| AABB::collision(e.get_aabb(), aabb))
}
pub fn placibility_map(&self) -> Placibility {
let mut blocked = PositionMap::new(self.get_aabb().unwrap(), &false);
// dbg!(self.get_aabb(), self.get_aabb().unwrap().size());
for (_, e) in self.entities.iter() {
let aabb = e.get_aabb();
for y in aabb.min().y..=aabb.max().y {
for x in aabb.min().x..=aabb.max().x {
blocked[Position::new(x, y)] = true;
}
}
}
Placibility { blocked }
}
}
#[derive(Debug)]
struct Placibility {
blocked: PositionMap<bool>,
}
impl Placibility {
pub fn placeable(&self, pos: Position, size: Position) -> bool {
let aabb = AABB::new(pos - size / 2, pos + size / 2 - Position::new(1, 1));
for y in aabb.min().y..=aabb.max().y {
for x in aabb.min().x..=aabb.max().x {
if !self.blocked.get_aabb().contains_pos(Position::new(x, y))
|| self.blocked[Position::new(x, y)] == true
{
return false;
}
}
}
true
}
}
impl Default for Blueprint {

View file

@ -8,7 +8,7 @@ use super::*;
#[derive(Debug)]
struct PowerGraph {
nodes: HashMap<Position, (Vec<Position>, f64)>,
nodes: HashMap<(Position, ElectricPoleType), (Vec<(Position, ElectricPoleType)>, f64)>,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
@ -17,134 +17,215 @@ enum NodeType {
Out,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
struct Node {
pos: Position,
electric_pole_type: ElectricPoleType,
node_type: NodeType,
}
impl WheightedGraph for PowerGraph {
type Node = (Position, NodeType);
type Node = Node;
fn edge(&self, node: &Self::Node, num: usize) -> Option<(Self::Node, f64)> {
match node.1 {
match node.node_type {
NodeType::In => {
if num == 0 {
self.nodes
.get(&node.0)
.map(|v| ((node.0, NodeType::Out), v.1))
.get(&(node.pos, node.electric_pole_type))
.map(|v| {
(
Node {
pos: node.pos,
electric_pole_type: node.electric_pole_type,
node_type: NodeType::Out,
},
v.1,
)
})
} else {
None
}
}
NodeType::Out => self
.nodes
.get(&node.0)
.and_then(|v| v.0.get(num).cloned())
.map(|v| ((v, NodeType::In), 0.0)),
.get(&(node.pos, node.electric_pole_type))
.and_then(|v| {
v.0.get(num).cloned().map(|(pos, electric_pole_type)| {
(
Node {
pos,
electric_pole_type,
node_type: NodeType::In,
},
0.0,
)
})
}),
}
}
}
fn wire_reach(electric_pole_type: ElectricPoleType) -> PositionType {
match electric_pole_type {
ElectricPoleType::Small => 15,
ElectricPoleType::Medium => 18,
ElectricPoleType::Big => 64,
ElectricPoleType::Substation => todo!(),
}
}
fn power_connections(
pos: Position,
electric_pole_type: ElectricPoleType,
) -> impl Iterator<Item = (Position, ElectricPoleType)> {
[
ElectricPoleType::Small,
ElectricPoleType::Medium,
ElectricPoleType::Big,
]
.into_iter()
.flat_map(move |other_electric_pole_type| {
let reach = PositionType::min(
wire_reach(electric_pole_type),
wire_reach(other_electric_pole_type),
);
let alignment = other_electric_pole_type.alignment();
((pos.x - reach)..=(pos.x + reach))
.filter(move |x| x.rem_euclid(2) == alignment)
.flat_map(move |x| {
((pos.y - reach)..=(pos.y + reach))
.filter(move |y| y.rem_euclid(2) == alignment)
.map(move |y| (x, y))
})
.filter(move |(x, y)| {
(x - pos.x) * (x - pos.x) + (y - pos.y) * (y - pos.y) <= reach * reach
})
.map(move |(x, y)| (Position::new(x, y), other_electric_pole_type))
})
}
impl Blueprint {
pub fn connect_power_networks(&mut self) {
let power_poles = self
let mut power_pole_map = self
.entities
.iter()
.filter(|&(_, e)| matches!(e.entity, EntityType::ElectricPole(_)))
.cloned()
.collect::<Vec<_>>();
.filter_map(|(k, e)| match e.entity {
EntityType::ElectricPole(electric_pole_type) => {
Some(((e.position, electric_pole_type), *k))
}
_ => None,
})
.collect::<HashMap<_, _>>();
let aabb = self.get_aabb().unwrap();
let size = (aabb.size() - Position::new(1, 1)) / 2;
let offset = aabb.min() / -2;
let mut nodes = HashMap::new();
let mut blocked =
factorio_core::misc::Map::new_with(size.x as usize, size.y as usize, Some(1.0));
let placibility = self.placibility_map();
// dbg!(&placibility);
for (_, e) in &self.entities {
let entity_aabb = e.get_aabb();
for y in aabb.min().y..=aabb.max().y {
for x in aabb.min().x..=aabb.max().x {
for electric_pole_type in [
ElectricPoleType::Small,
ElectricPoleType::Medium,
ElectricPoleType::Big,
] {
// dbg!(x, y, electric_pole_type);
let alignment = electric_pole_type.alignment();
if x.rem_euclid(2) == alignment && y.rem_euclid(2) == alignment {
let cost = if placibility
.placeable(Position::new(x, y), electric_pole_type.size())
{
Some(match electric_pole_type {
ElectricPoleType::Small => 0.8,
ElectricPoleType::Medium => 1.0,
ElectricPoleType::Big => 1.5,
ElectricPoleType::Substation => todo!(),
})
} else if power_pole_map
.contains_key(&(Position::new(x, y), electric_pole_type))
{
Some(0.0)
} else {
None
};
let entity_min = entity_aabb.min() / 2;
let entity_max = entity_aabb.max() / 2;
for y in entity_min.y..entity_max.y {
for x in entity_min.x..entity_max.x {
*blocked.get_mut((x + offset.x) as usize, (y + offset.y) as usize) = None;
}
}
}
for (_, p) in &power_poles {
let pos = (p.position - Position::new(1, 1)) / 2 + offset;
*blocked.get_mut(pos.x as usize, pos.y as usize) = Some(0.0);
}
let mut graph = PowerGraph {
nodes: HashMap::new(),
};
for y_source in 0..blocked.height {
for x_source in 0..blocked.width {
if let &Some(w) = blocked.get(x_source, y_source) {
let pos = Position::new(x_source as PositionType, y_source as PositionType);
let mut edges = Vec::new();
for (xx, yy) in (-9..=9)
.flat_map(|dx| (-9..=9).map(move |dy| (dx, dy)))
.filter(|&(dx, dy)| dx * dx + dy * dy <= 9 * 9)
.filter_map(|(dx, dy)| {
x_source
.checked_add_signed(dx)
.filter(|&x| x < blocked.width)
.zip(
y_source
.checked_add_signed(dy)
.filter(|&y| y < blocked.height),
)
})
{
if let &Some(w) = blocked.get(xx, yy) {
edges.push(Position::new(xx as PositionType, yy as PositionType));
if let Some(cost) = cost {
let v = power_connections(Position::new(x, y), electric_pole_type)
// .filter(|&(pos, _)| aabb.contains_pos(pos))
// .inspect(|d| {
// if y == 261 {
// dbg!(d);
// }
// })
// .filter(|&(pos, electric_pole_type)| {
// placibility.placeable(pos, electric_pole_type.size())
// || power_pole_map.contains_key(&(pos, electric_pole_type))
// })
.collect();
nodes.insert((Position::new(x, y), electric_pole_type), (v, cost));
}
}
if !edges.is_empty() {
graph.nodes.insert(pos, (edges, w));
}
}
}
}
let pole_positions = power_poles
.iter()
.map(|(_, e)| {
(
(e.position - Position::new(1, 1)) / 2 + offset,
NodeType::Out,
)
let graph = PowerGraph { nodes };
// dbg!(&graph);
let pole_positions = power_pole_map
.keys()
.map(|&(pos, electric_pole_type)| Node {
pos,
electric_pole_type,
node_type: NodeType::Out,
})
.collect::<Vec<_>>();
// dbg!(&pole_positions);
// let pole_positions = [
// Node {
// pos: Position::new(-25, 279),
// electric_pole_type: ElectricPoleType::Medium,
// node_type: NodeType::Out,
// },
// Node {
// pos: Position::new(239, 257),
// electric_pole_type: ElectricPoleType::Medium,
// node_type: NodeType::Out,
// },
// ];
if let Some(res) =
steiner_tree::takaheshi_matsuyama::<_, FastBinaryHeap<_>>(&graph, &pole_positions)
{
let mut power_pole_map = power_poles
.iter()
.map(|(k, e)| ((e.position - Position::new(1, 1)) / 2 + offset, *k))
.collect::<HashMap<_, _>>();
for path in res {
let path_iter = path.iter().filter_map(|(p, n)| match n {
NodeType::In => None,
NodeType::Out => Some(p),
});
for &p in path_iter.clone() {
let path_iter = path.iter().filter_map(
|Node {
pos,
electric_pole_type,
node_type,
}| match node_type {
NodeType::In => None,
NodeType::Out => Some((*pos, *electric_pole_type)),
},
);
for p in path_iter.clone() {
match power_pole_map.entry(p) {
std::collections::hash_map::Entry::Occupied(_occupied_entry) => (),
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
let k = self.add_entity(Entity::new_electric_pole(
ElectricPoleType::Medium,
(p - offset) * 2 + Position::new(1, 1),
));
let k = self.add_entity(Entity::new_electric_pole(p.1, p.0));
vacant_entry.insert(k);
}
}
}
for (p, n) in path_iter.clone().zip(path_iter.skip(1)) {
self.add_wire(power_pole_map[n], 5, power_pole_map[p], 5);
self.add_wire(power_pole_map[&n], 5, power_pole_map[&p], 5);
}
}
}

View file

@ -6,7 +6,7 @@ use factorio_core::{
impl Visualize for super::Blueprint {
fn visualize(&self) -> factorio_core::visualize::Visualization {
let aabb = self.get_aabb().unwrap();
let mut v = Visualization::new((aabb.size() - Position::new(1, 1)) / 2);
let mut v = Visualization::new((aabb.size() + Position::new(1, 1)) / 2);
let offset = aabb.min() / -2;
@ -18,9 +18,9 @@ impl Visualize for super::Blueprint {
let entity_aabb = e.get_aabb();
let entity_min = entity_aabb.min() / 2;
let entity_max = entity_aabb.max() / 2;
for y in entity_min.y..entity_max.y {
for x in entity_min.x..entity_max.x {
let entity_max = (entity_aabb.max() - Position::new(1, 1)) / 2;
for y in entity_min.y..=entity_max.y {
for x in entity_min.x..=entity_max.x {
v.overwrite_background(Position::new(x, y) + offset, Some(Color::gray(32)));
}
}
@ -29,3 +29,26 @@ impl Visualize for super::Blueprint {
v
}
}
impl Visualize for super::Placibility {
fn visualize(&self) -> Visualization {
let aabb = self.blocked.get_aabb();
let mut v = Visualization::new(aabb.size());
for y in aabb.min().y..=aabb.max().y {
for x in aabb.min().x..=aabb.max().x {
if self.blocked[Position::new(x, y)] {
v.add_symbol(
Position::new(x, y) - aabb.min(),
factorio_core::visualize::Symbol::Block,
Some(Color::white()),
None,
);
}
}
}
v
}
}