use rand::Rng;
use crate::{
algorithm::{direction::Direction, r#move::r#move::Move},
puzzle::{size::Size, sliding_puzzle::SlidingPuzzle},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub trait Scrambler {
#[must_use]
fn is_valid_size(&self, size: Size) -> bool;
#[cfg(feature = "thread_rng")]
fn try_scramble<P: SlidingPuzzle>(&self, puzzle: &mut P) -> bool {
self.try_scramble_with_rng(puzzle, &mut rand::rng())
}
#[cfg(feature = "thread_rng")]
fn scramble<P: SlidingPuzzle>(&self, puzzle: &mut P) {
self.scramble_with_rng(puzzle, &mut rand::rng());
}
fn try_scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) -> bool {
if self.is_valid_size(puzzle.size()) {
self.scramble_with_rng(puzzle, rng);
true
} else {
false
}
}
fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R);
}
#[derive(Clone, Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RandomInvertibleState;
impl Scrambler for RandomInvertibleState {
fn is_valid_size(&self, size: Size) -> bool {
size.width() > 1 && size.height() > 1
}
fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) {
puzzle.reset();
let n = puzzle.num_pieces();
let mut parity = false;
for i in 0..n - 2 {
let j = rng.random_range(i..n);
if i != j {
puzzle.swap_non_gap_pieces(i, j);
parity = !parity;
}
}
if parity {
puzzle.swap_non_gap_pieces(n - 2, n - 1);
}
}
}
#[derive(Clone, Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RandomState;
impl Scrambler for RandomState {
fn is_valid_size(&self, _size: Size) -> bool {
true
}
fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) {
let (w, h) = puzzle.size().into();
if w == 1 {
puzzle.reset();
let d = rng.random_range(0..h);
puzzle.apply_move(Move::new(Direction::Down, d));
return;
}
if h == 1 {
puzzle.reset();
let r = rng.random_range(0..w);
puzzle.apply_move(Move::new(Direction::Right, r));
return;
}
RandomInvertibleState.scramble_with_rng(puzzle, rng);
let (d, r) = (rng.random_range(0..h), rng.random_range(0..w));
puzzle.apply_move(Move::new(Direction::Down, d));
puzzle.apply_move(Move::new(Direction::Right, r));
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RandomMoves {
pub moves: u64,
pub allow_backtracking: bool,
pub allow_illegal_moves: bool,
}
impl Scrambler for RandomMoves {
fn is_valid_size(&self, size: Size) -> bool {
if (size.width() == 1 || size.height() == 1)
&& !self.allow_backtracking
&& !self.allow_illegal_moves
{
self.moves < size.area()
} else {
true
}
}
fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) {
puzzle.reset();
let mut last_dir = None::<Direction>;
for _ in 0..self.moves {
let dir = {
let mut d = rng.random::<Direction>();
while (!self.allow_backtracking && last_dir == Some(d.inverse()))
|| (!self.allow_illegal_moves && !puzzle.can_move_dir(d))
{
d = rng.random();
}
d
};
last_dir = Some(dir);
puzzle.try_move_dir(dir);
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Cycle {
pub length: u64,
}
impl Scrambler for Cycle {
fn is_valid_size(&self, size: Size) -> bool {
size.width() > 1 && size.height() > 1
}
fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) {
puzzle.reset();
let n = puzzle.num_pieces();
let cycle_len = (self.length).min(if n.is_multiple_of(2) { n - 1 } else { n });
let max = if cycle_len % 2 == 0 { n - 2 } else { n };
let pieces = rand::seq::index::sample(rng, max as usize, cycle_len as usize);
for i in 1..cycle_len as usize {
puzzle.try_swap_pieces(pieces.index(0) as u64, pieces.index(i) as u64);
}
if self.length.is_multiple_of(2) {
puzzle.try_swap_pieces(n - 2, n - 1);
}
}
}
#[cfg(test)]
mod tests {
use crate::puzzle::puzzle::Puzzle;
use super::*;
mod random_invertible_state {
use rand::SeedableRng as _;
use rand_xoshiro::Xoroshiro128StarStar;
use super::*;
const SEED: [u8; 16] = [
160, 108, 126, 255, 147, 210, 122, 252, 71, 77, 144, 13, 167, 11, 225, 93,
];
#[test]
fn test_gap_in_bottom_right() {
let mut rng = Xoroshiro128StarStar::from_seed(SEED);
for s in 2..10 {
let mut p = Puzzle::new(Size::new(s, s).unwrap());
for _ in 0..100 {
RandomInvertibleState.scramble_with_rng(&mut p, &mut rng);
assert_eq!(p.gap_position_xy(), (s - 1, s - 1));
}
}
}
}
mod random_state {
use rand::SeedableRng as _;
use rand_xoshiro::Xoroshiro128StarStar;
use crate::puzzle::{label::label::RowGrids, solvable::Solvable as _};
use super::*;
const SEED: [u8; 16] = [
160, 108, 126, 255, 147, 210, 122, 252, 71, 77, 144, 13, 167, 11, 225, 93,
];
#[test]
fn test_solvable() {
let mut rng = Xoroshiro128StarStar::from_seed(SEED);
for (w, h) in [(1, 1), (1, 4), (4, 1), (2, 2), (4, 4), (10, 2), (20, 20)] {
let mut p = Puzzle::new(Size::new(w, h).unwrap());
for _ in 0..100 {
RandomState.scramble_with_rng(&mut p, &mut rng);
assert!(RowGrids.is_solvable(&p));
}
}
}
}
}
#[cfg(all(feature = "nightly", test))]
mod benchmarks {
extern crate test;
use rand::SeedableRng;
use rand_xoshiro::Xoroshiro128StarStar;
use test::Bencher;
use crate::puzzle::puzzle::Puzzle;
use super::*;
#[bench]
fn bench_random_state(b: &mut Bencher) {
let mut p = Puzzle::new(Size::new(100, 100).unwrap());
let mut rng = Xoroshiro128StarStar::seed_from_u64(0);
b.iter(|| RandomState.scramble_with_rng(&mut p, &mut rng));
}
}