use std::cmp::Ordering;
use thiserror::Error;
use crate::puzzle::{grids::Grids, label::rect_partition::Rect, size::Size};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LabelError {
#[error("PositionOutOfBounds: position {pos:?} is out of bounds on a {size} puzzle")]
PositionOutOfBounds {
size: Size,
pos: (u64, u64),
},
}
pub trait Label {
#[must_use]
fn position_label(&self, size: Size, pos: (u64, u64)) -> u64;
fn try_position_label(&self, size: Size, pos: (u64, u64)) -> Result<u64, LabelError> {
if size.is_within_bounds(pos) {
Ok(self.position_label(size, pos))
} else {
Err(LabelError::PositionOutOfBounds { size, pos })
}
}
#[must_use]
fn num_labels(&self, size: Size) -> u64;
#[must_use]
fn fixed_size(self, size: Size) -> FixedSize<Self>
where
Self: Sized,
{
FixedSize { label: self, size }
}
#[must_use]
fn fixed_size_ref(&self, size: Size) -> FixedSize<&Self>
where
Self: Sized,
{
FixedSize { label: self, size }
}
}
impl<L: Label + ?Sized> Label for &L {
fn position_label(&self, size: Size, pos: (u64, u64)) -> u64 {
(**self).position_label(size, pos)
}
fn num_labels(&self, size: Size) -> u64 {
(**self).num_labels(size)
}
}
impl<L: Label + ?Sized> Label for &mut L {
fn position_label(&self, size: Size, pos: (u64, u64)) -> u64 {
(**self).position_label(size, pos)
}
fn num_labels(&self, size: Size) -> u64 {
(**self).num_labels(size)
}
}
impl<L: Label + ?Sized> Label for Box<L> {
fn position_label(&self, size: Size, pos: (u64, u64)) -> u64 {
(**self).position_label(size, pos)
}
fn num_labels(&self, size: Size) -> u64 {
(**self).num_labels(size)
}
}
pub trait FixedSizeLabel {
#[must_use]
fn size(&self) -> Size;
#[must_use]
fn position_label(&self, pos: (u64, u64)) -> u64;
fn try_position_label(&self, pos: (u64, u64)) -> Result<u64, LabelError> {
let size = self.size();
if size.is_within_bounds(pos) {
Ok(self.position_label(pos))
} else {
Err(LabelError::PositionOutOfBounds { size, pos })
}
}
#[must_use]
fn num_labels(&self) -> u64;
}
impl<L: FixedSizeLabel + ?Sized> FixedSizeLabel for &L {
fn size(&self) -> Size {
(**self).size()
}
fn position_label(&self, pos: (u64, u64)) -> u64 {
(**self).position_label(pos)
}
fn num_labels(&self) -> u64 {
(**self).num_labels()
}
}
impl<L: FixedSizeLabel + ?Sized> FixedSizeLabel for &mut L {
fn size(&self) -> Size {
(**self).size()
}
fn position_label(&self, pos: (u64, u64)) -> u64 {
(**self).position_label(pos)
}
fn num_labels(&self) -> u64 {
(**self).num_labels()
}
}
impl<L: FixedSizeLabel + ?Sized> FixedSizeLabel for Box<L> {
fn size(&self) -> Size {
(**self).size()
}
fn position_label(&self, pos: (u64, u64)) -> u64 {
(**self).position_label(pos)
}
fn num_labels(&self) -> u64 {
(**self).num_labels()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FixedSize<L: Label> {
label: L,
size: Size,
}
impl<L: Label> FixedSize<L> {
pub fn inner(&self) -> &L {
&self.label
}
pub fn into_inner(self) -> L {
self.label
}
}
impl<L: Label> FixedSizeLabel for FixedSize<L> {
fn size(&self) -> Size {
self.size
}
fn position_label(&self, pos: (u64, u64)) -> u64 {
self.label.position_label(self.size, pos)
}
fn num_labels(&self) -> u64 {
self.label.num_labels(self.size)
}
}
pub trait BijectiveLabel: Label {}
macro_rules! define_label {
($($(#[$annot:meta])* $name:ident),* $(,)?) => {
$(
$(#[$annot])*
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct $name;
)*
};
}
define_label!(
Trivial,
RowGrids,
Rows,
Fringe,
FringeGrids,
SquareFringe,
SplitFringe,
SplitSquareFringe,
Diagonals,
LastTwoRows,
SplitLastTwoRows,
ConcentricRectangles,
Spiral,
SpiralGrids,
Checkerboard,
);
impl BijectiveLabel for RowGrids {}
impl BijectiveLabel for FringeGrids {}
impl BijectiveLabel for SpiralGrids {}
impl Label for Trivial {
fn position_label(&self, _size: Size, _pos: (u64, u64)) -> u64 {
0
}
fn num_labels(&self, _size: Size) -> u64 {
1
}
}
impl Label for RowGrids {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
x + size.width() * y
}
fn num_labels(&self, size: Size) -> u64 {
size.area()
}
}
impl Label for Rows {
fn position_label(&self, _size: Size, (_, y): (u64, u64)) -> u64 {
y
}
fn num_labels(&self, size: Size) -> u64 {
size.height()
}
}
impl Label for Fringe {
fn position_label(&self, _size: Size, (x, y): (u64, u64)) -> u64 {
x.min(y)
}
fn num_labels(&self, size: Size) -> u64 {
size.width().min(size.height())
}
}
impl Label for FringeGrids {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
let fringe = x.min(y);
let vertical_part = x < y;
let (width, height) = size.into();
let previous_fringes = fringe * (width + height - fringe);
let current_fringe = if vertical_part {
width - fringe + y - x - 1
} else {
x - y
};
previous_fringes + current_fringe
}
fn num_labels(&self, size: Size) -> u64 {
size.area()
}
}
impl Label for SquareFringe {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
let (width, height) = size.into();
match width.cmp(&height) {
Ordering::Less => {
let diff = height - width;
if y < diff {
y
}
else {
diff + Fringe.position_label(size, (x, y - diff))
}
}
Ordering::Equal => Fringe.position_label(size, (x, y)),
Ordering::Greater => self.position_label(size.transpose(), (y, x)),
}
}
fn num_labels(&self, size: Size) -> u64 {
let (width, height) = size.into();
Fringe.num_labels(size) + width.abs_diff(height)
}
}
impl Label for SplitFringe {
fn position_label(&self, _size: Size, (x, y): (u64, u64)) -> u64 {
let fringe = x.min(y);
let vertical_part = x < y;
2 * fringe + u64::from(vertical_part)
}
fn num_labels(&self, size: Size) -> u64 {
let (width, height) = size.into();
2 * width.min(height) - u64::from(height <= width)
}
}
impl Label for SplitSquareFringe {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
let (width, height) = size.into();
let d = width.abs_diff(height);
match width.cmp(&height) {
Ordering::Less => {
if y < d {
y
} else {
d + SplitFringe.position_label(size, (x, y - d))
}
}
Ordering::Equal => SplitFringe.position_label(size, (x, y)),
Ordering::Greater => {
if x < d {
x
} else {
d + SplitFringe.position_label(size, (x - d, y))
}
}
}
}
fn num_labels(&self, size: Size) -> u64 {
let (width, height) = size.into();
let diff = width.abs_diff(height);
diff + SplitFringe.num_labels(size.shrink_to_square())
}
}
impl Label for Diagonals {
fn position_label(&self, _size: Size, (x, y): (u64, u64)) -> u64 {
x + y
}
fn num_labels(&self, size: Size) -> u64 {
let (width, height) = size.into();
width + height - 1
}
}
impl Label for LastTwoRows {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
let h = size.height().saturating_sub(2);
if y < h {
y
} else {
h + x
}
}
fn num_labels(&self, size: Size) -> u64 {
size.width() + size.height().saturating_sub(2)
}
}
impl Label for SplitLastTwoRows {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
if y < size.height().saturating_sub(2) {
y
} else {
x
}
}
fn num_labels(&self, size: Size) -> u64 {
let (width, height) = size.into();
width.max(height.saturating_sub(2))
}
}
impl Label for ConcentricRectangles {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
let (width, height) = size.into();
x.min(y).min(width - 1 - x).min(height - 1 - y)
}
fn num_labels(&self, size: Size) -> u64 {
let (width, height) = size.into();
width.min(height).div_ceil(2)
}
}
impl Label for Spiral {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
let rect_label = ConcentricRectangles.position_label(size, (x, y));
let (width, height) = size.into();
let (rx, ry, rw, rh) = (
x - rect_label,
y - rect_label,
width - 2 * rect_label,
height - 2 * rect_label,
);
let rect_side = if rw.min(rh) == 1 || (ry == 0 && rx < rw - 1) {
0
} else if rx == rw - 1 && ry < rh - 1 {
1
} else if ry == rh - 1 && rx > 0 {
2
} else {
3
};
4 * rect_label + rect_side
}
fn num_labels(&self, size: Size) -> u64 {
let (width, height) = size.into();
4 * (width.min(height) / 2) + width.min(height) % 2
}
}
impl Label for SpiralGrids {
fn position_label(&self, size: Size, (x, y): (u64, u64)) -> u64 {
let rect_label = ConcentricRectangles.position_label(size, (x, y));
let (width, height) = size.into();
let (rx, ry, rw, rh) = (
x - rect_label,
y - rect_label,
width - 2 * rect_label,
height - 2 * rect_label,
);
let num_outer_pieces = 2 * rect_label * (width + height - 2 * rect_label);
let rect_pieces = if ry == 0 && rx < rw - 1 {
rx
} else if rx == rw - 1 && ry < rh - 1 {
rw - 1 + ry
} else if ry == rh - 1 && rx > 0 {
2 * (rw + rh - 2) - (rh - 1 + rx)
} else {
2 * (rw + rh - 2) - ry
};
num_outer_pieces + rect_pieces
}
fn num_labels(&self, size: Size) -> u64 {
size.area()
}
}
impl Label for Checkerboard {
fn position_label(&self, _size: Size, (x, y): (u64, u64)) -> u64 {
(x + y) % 2
}
fn num_labels(&self, _size: Size) -> u64 {
2
}
}
macro_rules! trivial_grids {
($($name:ident),* $(,)?) => {
$(impl Grids for $name {
fn grid_containing_pos(&self, _size: Size, pos: (u64, u64)) -> Rect {
Rect::new(pos, (pos.0 + 1, pos.1 + 1)).unwrap()
}
})*
};
}
trivial_grids!(RowGrids, FringeGrids, SpiralGrids);
#[cfg(test)]
mod tests {
macro_rules! test_label {
($label:ty, $($w:literal x $h:literal : $labels:expr),+ $(,)?) => {
paste::paste! {
mod [< $label:snake >] {
use crate::puzzle::{label::label::{Label as _, $label}, size::Size};
$(#[test]
fn [< test_ $label:snake _ $w x $h >] () {
let size = Size::new($w, $h).unwrap();
let labels = (0..size.area())
.map(|i| {
#[allow(clippy::modulo_one)]
let position = (i % $w, i / $w);
$label.position_label(size, position)
})
.collect::<Vec<_>>();
let num_labels = $label.num_labels(size);
let expected_num_labels = $labels.iter().max().unwrap() + 1;
assert_eq!(labels, $labels);
assert_eq!(num_labels, expected_num_labels);
})*
}
}
};
}
test_label!(
Trivial,
4 x 4: [
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
],
);
test_label!(
RowGrids,
1 x 4: [
0,
1,
2,
3,
],
4 x 1: [
0, 1, 2, 3,
],
4 x 4: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
],
4 x 6: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 22, 23,
],
6 x 4: [
0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23,
],
);
test_label!(
Rows,
1 x 4: [
0,
1,
2,
3,
],
4 x 1: [
0, 0, 0, 0,
],
4 x 4: [
0, 0, 0, 0,
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
],
4 x 6: [
0, 0, 0, 0,
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
4, 4, 4, 4,
5, 5, 5, 5,
],
6 x 4: [
0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3,
],
);
test_label!(
Fringe,
1 x 4: [
0,
0,
0,
0,
],
4 x 1: [
0, 0, 0, 0,
],
4 x 4: [
0, 0, 0, 0,
0, 1, 1, 1,
0, 1, 2, 2,
0, 1, 2, 3,
],
4 x 6: [
0, 0, 0, 0,
0, 1, 1, 1,
0, 1, 2, 2,
0, 1, 2, 3,
0, 1, 2, 3,
0, 1, 2, 3,
],
6 x 4: [
0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1,
0, 1, 2, 2, 2, 2,
0, 1, 2, 3, 3, 3
],
);
test_label!(
FringeGrids,
1 x 4: [
0,
1,
2,
3,
],
4 x 1: [
0, 1, 2, 3,
],
4 x 4: [
0, 1, 2, 3,
4, 7, 8, 9,
5, 10, 12, 13,
6, 11, 14, 15,
],
4 x 6: [
0, 1, 2, 3,
4, 9, 10, 11,
5, 12, 16, 17,
6, 13, 18, 21,
7, 14, 19, 22,
8, 15, 20, 23,
],
6 x 4: [
0, 1, 2, 3, 4, 5,
6, 9, 10, 11, 12, 13,
7, 14, 16, 17, 18, 19,
8, 15, 20, 21, 22, 23,
],
);
test_label!(
SquareFringe,
1 x 4: [
0,
1,
2,
3,
],
4 x 1: [
0, 1, 2, 3,
],
4 x 4: [
0, 0, 0, 0,
0, 1, 1, 1,
0, 1, 2, 2,
0, 1, 2, 3,
],
4 x 6: [
0, 0, 0, 0,
1, 1, 1, 1,
2, 2, 2, 2,
2, 3, 3, 3,
2, 3, 4, 4,
2, 3, 4, 5,
],
6 x 4: [
0, 1, 2, 2, 2, 2,
0, 1, 2, 3, 3, 3,
0, 1, 2, 3, 4, 4,
0, 1, 2, 3, 4, 5,
],
);
test_label!(
SplitFringe,
1 x 4: [
0,
1,
1,
1,
],
4 x 1: [
0, 0, 0, 0,
],
4 x 4: [
0, 0, 0, 0,
1, 2, 2, 2,
1, 3, 4, 4,
1, 3, 5, 6,
],
4 x 6: [
0, 0, 0, 0,
1, 2, 2, 2,
1, 3, 4, 4,
1, 3, 5, 6,
1, 3, 5, 7,
1, 3, 5, 7,
],
6 x 4: [
0, 0, 0, 0, 0, 0,
1, 2, 2, 2, 2, 2,
1, 3, 4, 4, 4, 4,
1, 3, 5, 6, 6, 6,
],
);
test_label!(
SplitSquareFringe,
1 x 4: [
0,
1,
2,
3,
],
4 x 1: [
0, 1, 2, 3,
],
4 x 4: [
0, 0, 0, 0,
1, 2, 2, 2,
1, 3, 4, 4,
1, 3, 5, 6,
],
4 x 6: [
0, 0, 0, 0,
1, 1, 1, 1,
2, 2, 2, 2,
3, 4, 4, 4,
3, 5, 6, 6,
3, 5, 7, 8,
],
6 x 4: [
0, 1, 2, 2, 2, 2,
0, 1, 3, 4, 4, 4,
0, 1, 3, 5, 6, 6,
0, 1, 3, 5, 7, 8,
],
);
test_label!(
Diagonals,
1 x 4: [
0,
1,
2,
3,
],
4 x 1: [
0, 1, 2, 3,
],
4 x 4: [
0, 1, 2, 3,
1, 2, 3, 4,
2, 3, 4, 5,
3, 4, 5, 6,
],
4 x 6: [
0, 1, 2, 3,
1, 2, 3, 4,
2, 3, 4, 5,
3, 4, 5, 6,
4, 5, 6, 7,
5, 6, 7, 8,
],
6 x 4: [
0, 1, 2, 3, 4, 5,
1, 2, 3, 4, 5, 6,
2, 3, 4, 5, 6, 7,
3, 4, 5, 6, 7, 8,
],
);
test_label!(
LastTwoRows,
1 x 4: [
0,
1,
2,
2,
],
4 x 1: [
0, 1, 2, 3,
],
4 x 4: [
0, 0, 0, 0,
1, 1, 1, 1,
2, 3, 4, 5,
2, 3, 4, 5,
],
4 x 6: [
0, 0, 0, 0,
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
4, 5, 6, 7,
4, 5, 6, 7,
],
6 x 4: [
0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1,
2, 3, 4, 5, 6, 7,
2, 3, 4, 5, 6, 7,
],
4 x 2: [
0, 1, 2, 3,
0, 1, 2, 3,
],
2 x 4: [
0, 0,
1, 1,
2, 3,
2, 3,
],
);
test_label!(
SplitLastTwoRows,
1 x 4: [
0,
1,
0,
0,
],
4 x 1: [
0, 1, 2, 3,
],
4 x 4: [
0, 0, 0, 0,
1, 1, 1, 1,
0, 1, 2, 3,
0, 1, 2, 3,
],
4 x 6: [
0, 0, 0, 0,
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
0, 1, 2, 3,
0, 1, 2, 3,
],
6 x 4: [
0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1,
0, 1, 2, 3, 4, 5,
0, 1, 2, 3, 4, 5,
],
4 x 2: [
0, 1, 2, 3,
0, 1, 2, 3,
],
2 x 4: [
0, 0,
1, 1,
0, 1,
0, 1,
],
);
test_label!(
ConcentricRectangles,
1 x 4: [
0,
0,
0,
0,
],
4 x 1: [
0, 0, 0, 0,
],
4 x 4: [
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 0, 0, 0,
],
4 x 6: [
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 0, 0, 0,
],
6 x 4: [
0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0,
],
5 x 5: [
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 1, 2, 1, 0,
0, 1, 1, 1, 0,
0, 0, 0, 0, 0,
],
7 x 8: [
0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0,
0, 1, 2, 2, 2, 1, 0,
0, 1, 2, 3, 2, 1, 0,
0, 1, 2, 3, 2, 1, 0,
0, 1, 2, 2, 2, 1, 0,
0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0,
],
);
test_label!(
Spiral,
1 x 4: [
0,
0,
0,
0,
],
4 x 1: [
0, 0, 0, 0,
],
4 x 4: [
0, 0, 0, 1,
3, 4, 5, 1,
3, 7, 6, 1,
3, 2, 2, 2,
],
4 x 6: [
0, 0, 0, 1,
3, 4, 5, 1,
3, 7, 5, 1,
3, 7, 5, 1,
3, 7, 6, 1,
3, 2, 2, 2,
],
6 x 4: [
0, 0, 0, 0, 0, 1,
3, 4, 4, 4, 5, 1,
3, 7, 6, 6, 6, 1,
3, 2, 2, 2, 2, 2,
],
);
test_label!(
SpiralGrids,
1 x 4: [
0,
1,
2,
3,
],
4 x 1: [
0, 1, 2, 3,
],
4 x 4: [
0, 1, 2, 3,
11, 12, 13, 4,
10, 15, 14, 5,
9, 8, 7, 6,
],
4 x 6: [
0, 1, 2, 3,
15, 16, 17, 4,
14, 23, 18, 5,
13, 22, 19, 6,
12, 21, 20, 7,
11, 10, 9, 8,
],
6 x 4: [
0, 1, 2, 3, 4, 5,
15, 16, 17, 18, 19, 6,
14, 23, 22, 21, 20, 7,
13, 12, 11, 10, 9, 8,
],
5 x 7: [
0, 1, 2, 3, 4,
19, 20, 21, 22, 5,
18, 31, 32, 23, 6,
17, 30, 33, 24, 7,
16, 29, 34, 25, 8,
15, 28, 27, 26, 9,
14, 13, 12, 11, 10,
],
7 x 5: [
0, 1, 2, 3, 4, 5, 6,
19, 20, 21, 22, 23, 24, 7,
18, 31, 32, 33, 34, 25, 8,
17, 30, 29, 28, 27, 26, 9,
16, 15, 14, 13, 12, 11, 10,
],
);
test_label!(
Checkerboard,
1 x 4: [
0,
1,
0,
1,
],
4 x 1: [
0, 1, 0, 1,
],
4 x 4: [
0, 1, 0, 1,
1, 0, 1, 0,
0, 1, 0, 1,
1, 0, 1, 0,
],
4 x 6: [
0, 1, 0, 1,
1, 0, 1, 0,
0, 1, 0, 1,
1, 0, 1, 0,
0, 1, 0, 1,
1, 0, 1, 0,
],
6 x 4: [
0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0,
],
);
}