use exact_covers::{DlSolver, Solver};
use smallvec::SmallVec;
use std::collections::HashSet;
use std::iter;
use std::ops::ControlFlow;
type Position = (i8, i8, i8);
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
struct Polycube(
SmallVec<Position, { Self::INLINE_CAP }>,
);
impl Polycube {
const INLINE_CAP: usize = 5;
pub const fn from_const(positions: [Position; Self::INLINE_CAP]) -> Self {
Self(SmallVec::from_buf(positions))
}
pub fn transform(&self, f: impl FnMut(Position) -> Position) -> Self {
Self(self.0.iter().copied().map(f).collect())
}
pub fn aspect(&self) -> Self {
let x_min = self.0.iter().map(|(x, _, _)| x).min().unwrap();
let y_min = self.0.iter().map(|(_, y, _)| y).min().unwrap();
let z_min = self.0.iter().map(|(_, _, z)| z).min().unwrap();
self.transform(|(x, y, z)| (x - x_min, y - y_min, z - z_min))
}
pub fn base_placements(&self) -> HashSet<Polycube> {
let mut placements = HashSet::with_capacity(24);
let mut to_visit = Vec::with_capacity(8);
to_visit.push(self.aspect());
while let Some(shape) = to_visit.pop() {
let x_max = shape.0.iter().map(|(x, _, _)| x).max().unwrap();
let rotation = shape.transform(|(x, y, z)| (y, x_max - x, z));
if !placements.contains(&rotation) {
to_visit.push(rotation);
}
let reflection = shape.transform(|(x, y, z)| (y, z, x));
if !placements.contains(&reflection) {
to_visit.push(reflection);
}
placements.insert(shape);
}
placements
}
pub fn cubie_count(&self) -> usize {
self.0.len()
}
}
impl From<&[Position]> for Polycube {
fn from(positions: &[Position]) -> Self {
assert!(!positions.is_empty(), "a polycube has one or more cubies");
Self(SmallVec::from_slice(positions))
}
}
impl From<Vec<Position>> for Polycube {
fn from(positions: Vec<Position>) -> Self {
assert!(!positions.is_empty(), "a polycube has one or more cubies");
Self(positions.into())
}
}
const Y: Polycube = Polycube::from_const([(0, 2, 0), (1, 0, 0), (1, 1, 0), (1, 2, 0), (1, 3, 0)]);
fn is_in_bounds(polycube: &Polycube, l: i8, m: i8, n: i8) -> bool {
let x_min = *polycube.0.iter().map(|(x, _, _)| x).min().unwrap();
let y_min = *polycube.0.iter().map(|(_, y, _)| y).min().unwrap();
let z_min = *polycube.0.iter().map(|(_, _, z)| z).min().unwrap();
let x_max = *polycube.0.iter().map(|(x, _, _)| x).max().unwrap();
let y_max = *polycube.0.iter().map(|(_, y, _)| y).max().unwrap();
let z_max = *polycube.0.iter().map(|(_, _, z)| z).max().unwrap();
0 <= x_min && x_max < l && 0 <= y_min && y_max < m && 0 <= z_min && z_max < n
}
fn main() {
let (l, m, n) = (5i8, 5i8, 5i8);
let positions = (0..l)
.flat_map(|x| iter::repeat(x).zip(0..m))
.flat_map(|y| iter::repeat(y).zip(0..n))
.map(|((x, y), z)| (x, y, z))
.collect::<Vec<_>>();
let mut solver: DlSolver<Position, ()> = DlSolver::new(&positions, &[]);
let placements = Y.base_placements();
let mut first = true;
for placement in placements {
for (x_0, y_0, z_0) in &positions {
if first && (*x_0 > 2 || *y_0 > 2 || *z_0 > 2) {
continue;
}
let shifted = placement.transform(|(x, y, z)| (x + x_0, y + y_0, z + z_0));
if is_in_bounds(&shifted, l, m, n) {
solver.add_option(&shifted.0, []);
}
}
first = false;
}
let mut count = 0;
let mut polycube = Vec::with_capacity(Y.cubie_count());
solver.solve(|mut solution| {
print!("[");
while solution.next(&mut polycube) {
print!("[");
if let Some(((&last, _), elements)) = polycube.split_last() {
for (&cubie, _) in elements {
print!("[{},{},{}],", cubie.0, cubie.1, cubie.2);
}
print!("[{},{},{}]", last.0, last.1, last.2);
}
print!("],");
}
println!("]");
count += 1;
ControlFlow::Continue(())
});
println!("found {count} packings");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn y_base_placements() {
let placements = Y.base_placements();
assert_eq!(placements.len(), 24, "the Y pentacube has 24 3D-rotations");
for placement in &placements {
assert_eq!(
placement,
&placement.aspect(),
"a base placement is an aspect"
);
}
}
}