use crate::{
algorithm::direction::Direction,
puzzle::{
sliding_puzzle::SlidingPuzzle as _,
small::{sealed::SmallPuzzle as _, Puzzle4x4},
},
};
#[derive(Clone, Copy, Debug)]
pub(super) struct FourBitPuzzle {
pub(super) puzzle: Puzzle4x4,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ReducedFourBitPuzzle {
pub(super) pieces: u64,
pub(super) gap: u8,
}
impl FourBitPuzzle {
pub(super) fn new() -> Self {
Self {
puzzle: Puzzle4x4::new(),
}
}
#[allow(dead_code)]
unsafe fn with_pieces_and_gap_unchecked(pieces: u64, gap: u8) -> Self {
Self {
puzzle: Puzzle4x4::with_pieces_and_gap_unchecked(pieces, gap),
}
}
pub(super) fn pieces(&self) -> u64 {
self.puzzle.pieces()
}
#[allow(dead_code)]
fn gap(&self) -> u8 {
self.puzzle.gap()
}
fn piece_array(&self) -> [u8; 16] {
let raw_pieces = self.puzzle.pieces();
let mut pieces = [0; 16];
for (i, piece) in pieces.iter_mut().enumerate() {
*piece = ((raw_pieces >> (4 * i)) & 0xF) as u8;
}
pieces
}
pub(super) fn conjugate_with_transpose(&self) -> Self {
let pieces = self.piece_array();
let pos = |i| pieces.iter().position(|&x| x == i).unwrap() as u64;
let mut new = self.puzzle;
new.swap_pieces(pos(2), pos(5));
new.swap_pieces(pos(3), pos(9));
new.swap_pieces(pos(4), pos(13));
new.swap_pieces(pos(7), pos(10));
new.swap_pieces(pos(8), pos(14));
new.swap_pieces(pos(12), pos(15));
new.swap_pieces(1, 4);
new.swap_pieces(2, 8);
new.swap_pieces(3, 12);
new.swap_pieces(6, 9);
new.swap_pieces(7, 13);
new.swap_pieces(11, 14);
Self { puzzle: new }
}
pub(super) fn reduced(&self) -> ReducedFourBitPuzzle {
let raw_pieces = self.puzzle.pieces();
let mut pieces = 0;
for i in 0..16 {
let piece = ((raw_pieces >> (4 * i)) & 0xF) as usize;
let reduced_piece = ReducedFourBitPuzzle::SOLVED >> (4 * ((piece + 15) % 16)) & 0xF;
pieces |= reduced_piece << (4 * i);
}
ReducedFourBitPuzzle {
pieces,
gap: self.puzzle.gap(),
}
}
#[inline(always)]
pub(super) fn do_move(&mut self, dir: Direction) -> bool {
self.puzzle.try_move_dir(dir)
}
}
impl ReducedFourBitPuzzle {
const SOLVED: u64 = 0x0443443322332211;
pub(super) fn new() -> Self {
Self {
pieces: Self::SOLVED,
gap: 15,
}
}
unsafe fn with_pieces_and_gap_unchecked(pieces: u64, gap: u8) -> Self {
Self { pieces, gap }
}
pub(super) unsafe fn from_piece_array_unchecked(piece_array: [u8; 16]) -> Self {
let mut pieces = 0;
let mut gap = 0;
for (i, &piece) in piece_array.iter().enumerate() {
pieces |= (piece as u64) << (4 * i);
if piece == 0 {
gap = i as u8;
}
}
unsafe { Self::with_pieces_and_gap_unchecked(pieces, gap) }
}
pub(super) fn pieces(&self) -> u64 {
self.pieces
}
#[inline(always)]
pub(super) fn do_move(&mut self, dir: Direction) -> bool {
let gap = self.gap as usize;
let dir = dir as usize;
let shift = Puzzle4x4::SHIFTS[gap][dir] as u64;
let piece = ((self.pieces >> shift) & 0xF) as usize;
let mask = Puzzle4x4::MOVE_MASKS[gap][dir][piece];
self.pieces ^= mask;
let next_gap = Puzzle4x4::GAPS[gap][dir];
self.gap = next_gap;
next_gap != gap as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_do_move_1() {
let mut puzzle = FourBitPuzzle::new();
assert!(puzzle.do_move(Direction::Down));
assert_eq!(puzzle.pieces(), 0xCFED0BA987654321);
assert_eq!(puzzle.gap(), 11);
assert!(puzzle.do_move(Direction::Down));
assert_eq!(puzzle.pieces(), 0xCFED8BA907654321);
assert_eq!(puzzle.gap(), 7);
assert!(puzzle.do_move(Direction::Down));
assert_eq!(puzzle.pieces(), 0xCFED8BA947650321);
assert_eq!(puzzle.gap(), 3);
assert!(!puzzle.do_move(Direction::Down));
assert_eq!(puzzle.pieces(), 0xCFED8BA947650321);
assert_eq!(puzzle.gap(), 3);
}
#[test]
fn test_do_move_2() {
let mut puzzle = FourBitPuzzle::new();
puzzle.do_move(Direction::Right);
assert_eq!(puzzle.pieces(), 0xF0EDCBA987654321);
}
#[test]
fn test_reduced() {
let puzzle = FourBitPuzzle::new();
let reduced = puzzle.reduced();
assert_eq!(reduced.pieces, ReducedFourBitPuzzle::SOLVED);
}
#[test]
fn test_reduced_2() {
#[allow(clippy::undocumented_unsafe_blocks)]
let puzzle = unsafe { FourBitPuzzle::with_pieces_and_gap_unchecked(0xd46f9b8ac0e51732, 6) };
let reduced = puzzle.reduced();
assert_eq!(reduced.pieces, 0x3234342340431221);
assert_eq!(reduced.gap, 6);
}
}