twisted 0.1.3

A speedcubing and smart-cube library.
Documentation
// SPDX-FileCopyrightText: 2022 Declan Rixon <twisted.cubing@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only

// Exports
pub mod moves;
pub mod subset;

// External submodules
mod cycle;

use rand::distributions::{Distribution, Standard};
use rand::Rng;
use std::fmt;
use std::iter::Iterator;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Axis {
    X,
    Y,
    Z,
}

impl Axis {
    pub fn top(&self) -> Face {
        use Face::*;
        match self {
            Axis::X => R,
            Axis::Y => U,
            Axis::Z => F,
        }
    }

    pub fn bottom(&self) -> Face {
        use Face::*;
        match self {
            Axis::X => L,
            Axis::Y => D,
            Axis::Z => B,
        }
    }

    pub fn complements(&self) -> [Self; 2] {
        match self {
            Self::X => [Self::Y, Self::Z],
            Self::Y => [Self::X, Self::Z],
            Self::Z => [Self::X, Self::Y],
        }
    }

    pub fn complement(a: Self, b: Self) -> Option<Self> {
        match a {
            Self::X => match b {
                Self::X => None,
                Self::Y => Some(Self::Z),
                Self::Z => Some(Self::Y),
            },
            Self::Y => match b {
                Self::X => Some(Self::Z),
                Self::Y => None,
                Self::Z => Some(Self::X),
            },
            Self::Z => match b {
                Self::X => Some(Self::Y),
                Self::Y => Some(Self::X),
                Self::Z => None,
            },
        }
    }

    pub fn belt(&self) -> &'static [usize; 4] {
        match self {
            Axis::X => &[0, 8, 10, 2],
            Axis::Y => &[4, 5, 6, 7],
            Axis::Z => &[3, 1, 9, 11],
        }
    }
}

impl fmt::Display for Axis {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Axis::X => "x",
                Axis::Y => "y",
                Axis::Z => "z",
            }
        )
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Face {
    U,
    D,
    R,
    L,
    F,
    B,
}

impl Distribution<Face> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Face {
        use Face::*;
        [U, D, R, L, F, B][rng.gen_range(0..6)]
    }
}

impl Face {
    pub fn opposite(&self) -> Face {
        match self {
            Face::U => Face::D,
            Face::D => Face::U,
            Face::R => Face::L,
            Face::L => Face::R,
            Face::F => Face::B,
            Face::B => Face::F,
        }
    }

    pub fn is_top(&self) -> bool {
        match self {
            Face::U => true,
            Face::D => false,
            Face::R => true,
            Face::L => false,
            Face::F => true,
            Face::B => false,
        }
    }

    pub fn axis(&self) -> Axis {
        match self {
            Face::U => Axis::Y,
            Face::D => Axis::Y,
            Face::R => Axis::X,
            Face::L => Axis::X,
            Face::F => Axis::Z,
            Face::B => Axis::Z,
        }
    }

    pub fn corner_cycle(&self) -> &'static [usize; 4] {
        use cycle::corner::*;
        match &self {
            Face::U => &U,
            Face::D => &D,
            Face::R => &R,
            Face::L => &L,
            Face::F => &F,
            Face::B => &B,
        }
    }

    pub fn corner_cycle_matches(&self, cp: &[usize; 4]) -> bool {
        let mut cycle = self.corner_cycle().clone();
        if cycle == *cp {
            return true;
        }
        for _ in 1..4 {
            cycle.rotate_left(1);
            if cycle == *cp {
                return true;
            }
        }
        false
    }

    pub fn edge_cycle(&self) -> &'static [usize; 4] {
        use cycle::edge::*;
        match &self {
            Face::U => &U,
            Face::D => &D,
            Face::R => &R,
            Face::L => &L,
            Face::F => &F,
            Face::B => &B,
        }
    }
}

impl fmt::Display for Face {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Face::U => "U",
                Face::D => "D",
                Face::R => "R",
                Face::L => "L",
                Face::F => "F",
                Face::B => "B",
            }
        )
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Direction {
    Clockwise,
    CounterClockwise,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Move {
    pub face: Face,
    pub amount: i32,
}

impl fmt::Display for Move {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}{}{}",
            self.face,
            match self.amount {
                1 | -1 => "".to_string(),
                x => x.abs().to_string(),
            },
            if self.amount < 0 { "'" } else { "" },
        )
    }
}

impl Distribution<Move> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Move {
        let face = rng.gen();
        let amount = [-1, 1, 2][rng.gen_range(0..3)];
        Move { face, amount }
    }
}

pub struct RandomMoves {
    exclude_face: Option<Face>,
    exclude_axis: Option<Axis>,
    rng: rand::rngs::ThreadRng,
}

impl RandomMoves {
    pub fn new() -> Self {
        RandomMoves {
            rng: rand::thread_rng(),
            exclude_face: None,
            exclude_axis: None,
        }
    }
    pub fn new_from(last_face: Option<Face>, last_axis: Option<Axis>) -> Self {
        RandomMoves {
            rng: rand::thread_rng(),
            exclude_face: last_face,
            exclude_axis: last_axis,
        }
    }
}

impl Iterator for RandomMoves {
    type Item = Move;

    fn next(&mut self) -> Option<Self::Item> {
        'find_next: loop {
            let next: Move = self.rng.gen();
            if let Some(face) = self.exclude_face {
                if face == next.face {
                    continue 'find_next;
                }
            }
            if let Some(axis) = self.exclude_axis {
                if axis == next.face.axis() {
                    continue 'find_next;
                }
            }
            if let Some(face) = self.exclude_face {
                self.exclude_axis = if face == next.face.opposite() {
                    Some(next.face.axis())
                } else {
                    None
                }
            }
            self.exclude_face = Some(next.face);
            return Some(next);
        }
    }
}