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

use crate::cube::{
    subset::{corner_pair_between_bars, eo_bars_by_axes, face_containing_bar},
    Axis, Move,
};
use crate::method::{
    builder,
    shared::model::{edge::OrientedSliceEdge, orientation::MultiAxisCO},
    substep::{DominoReduction, DominoReductionCP, DominoReductionCSep, Solved},
    Breakdown, BreakdownError, Method, Substep,
};

#[derive(Copy, Clone)]
pub struct EOBar([OrientedSliceEdge; 12]);

impl EOBar {
    fn oriented(&self, axis: Axis) -> bool {
        match axis {
            Axis::X => self.0.iter().all(|e| e.eo.x),
            Axis::Y => self.0.iter().all(|e| e.eo.y),
            Axis::Z => self.0.iter().all(|e| e.eo.z),
        }
    }

    fn eobar(&self, eo_axis: Axis) -> Option<&'static [usize; 2]> {
        for slice_axis in eo_axis.complements() {
            let bars = eo_bars_by_axes(eo_axis, slice_axis).unwrap();
            for bar in bars {
                if bar.iter().all(|p| self.0[*p].from(slice_axis)) {
                    return Some(bar);
                }
            }
        }
        return None;
    }
}

impl Substep for EOBar {
    fn new() -> Self {
        use Axis::*;
        Self([X, Z, X, Z, Y, Y, Y, Y, X, Z, X, Z].map(|a| OrientedSliceEdge::new(a)))
    }

    fn after_moves(&self, moves: &[Move]) -> Self {
        let mut new = self.clone();
        for mv in moves {
            let cycle = mv.face.edge_cycle();
            let edges = cycle.map(|e| new.0[e]);
            for from in 0..4 {
                let to = (from as i32 + mv.amount).rem_euclid(4) as usize;
                new.0[cycle[to]] = if mv.amount % 2 == 0 {
                    edges[from]
                } else {
                    edges[from].flipped(mv.face.axis())
                }
            }
        }
        new
    }

    fn solved(&self) -> bool {
        for axis in [Axis::X, Axis::Y, Axis::Z] {
            if self.oriented(axis) && self.eobar(axis).is_some() {
                return true;
            }
        }
        false
    }

    fn comment() -> &'static str {
        "EO Bar"
    }
}

#[derive(Copy, Clone)]
pub struct EOStairs {
    pub co: MultiAxisCO,
    pub eobar: EOBar,
}

impl EOStairs {
    fn hs_bar(&self, eo_axis: Axis, eo_bar: &'static [usize; 2]) -> Option<&'static [usize; 2]> {
        let hs_bar_slice = face_containing_bar(eo_bar)?.axis();
        let eo_bar_slice = Axis::complement(hs_bar_slice, eo_axis)?;
        let hs_bars = eo_bars_by_axes(eo_axis, hs_bar_slice)?;
        let EOBar(edges) = &self.eobar;
        'finding_bar: for &hs_bar in hs_bars {
            let hs_bar_solved =
                edges[hs_bar[0]].from(eo_bar_slice) && edges[hs_bar[1]].from(eo_bar_slice);
            if !hs_bar_solved {
                continue 'finding_bar;
            };
            let corners = corner_pair_between_bars(eo_bar, hs_bar)?;
            if self.co.subset_solved(eo_bar_slice, corners) {
                return Some(hs_bar);
            }
        }
        None
    }
}

impl Substep for EOStairs {
    fn new() -> Self {
        Self {
            co: MultiAxisCO::new(),
            eobar: EOBar::new(),
        }
    }

    fn after_moves(&self, moves: &[Move]) -> Self {
        Self {
            co: self.co.after_moves(moves),
            eobar: self.eobar.after_moves(moves),
        }
    }

    fn solved(&self) -> bool {
        for axis in [Axis::X, Axis::Y, Axis::Z] {
            if self.eobar.oriented(axis) {
                if let Some(eobar) = self.eobar.eobar(axis) {
                    if self.hs_bar(axis, eobar).is_some() {
                        return true;
                    }
                }
            }
        }
        false
    }

    fn comment() -> &'static str {
        "EO + Hollow Stairs"
    }
}

pub struct EOStairsCO(EOStairs);

impl Substep for EOStairsCO {
    fn new() -> Self {
        Self(EOStairs::new())
    }

    fn after_moves(&self, moves: &[Move]) -> Self {
        let EOStairsCO(inner) = &self;
        EOStairsCO(inner.after_moves(moves))
    }

    fn solved(&self) -> bool {
        let EOStairsCO(inner) = &self;
        for axis in [Axis::X, Axis::Y, Axis::Z] {
            if inner.eobar.oriented(axis) {
                if let Some(eobar) = inner.eobar.eobar(axis) {
                    if let Some(hs_bar) = inner.hs_bar(axis, eobar) {
                        let bottom_face = face_containing_bar(hs_bar).unwrap();
                        let top_face = bottom_face.opposite();
                        let co = inner.co.for_axis(top_face.axis());
                        let top_corners = top_face.corner_cycle().map(|c| co[c]);
                        let bottom_corners = bottom_face.corner_cycle().map(|c| co[c]);
                        if (top_corners == [2, 1, 2, 1] || top_corners == [1, 2, 1, 2])
                            && bottom_corners == [0, 0, 0, 0]
                        {
                            return true;
                        }
                    }
                }
            }
        }
        false
    }

    fn comment() -> &'static str {
        "EO + Hollow Stairs + CO"
    }
}

pub struct EOFirst(builder::Custom);

impl EOFirst {
    pub fn new() -> Self {
        Self(crate::method![
            EOBar,
            EOStairs,
            EOStairsCO,
            DominoReduction,
            DominoReductionCSep,
            DominoReductionCP,
            Solved,
        ])
    }
}

impl Method for EOFirst {
    fn breakdown(&self, scramble: &[Move], solution: &[Move]) -> Result<Breakdown, BreakdownError> {
        self.0.breakdown(scramble, solution)
    }
}