1use rand::Rng;
4
5use crate::{
6 algorithm::{direction::Direction, r#move::r#move::Move},
7 puzzle::{size::Size, sliding_puzzle::SlidingPuzzle},
8};
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13pub trait Scrambler {
15 #[must_use]
17 fn is_valid_size(&self, size: Size) -> bool;
18
19 #[cfg(feature = "thread_rng")]
21 fn try_scramble<P: SlidingPuzzle>(&self, puzzle: &mut P) -> bool {
22 self.try_scramble_with_rng(puzzle, &mut rand::rng())
23 }
24
25 #[cfg(feature = "thread_rng")]
27 fn scramble<P: SlidingPuzzle>(&self, puzzle: &mut P) {
28 self.scramble_with_rng(puzzle, &mut rand::rng());
29 }
30
31 fn try_scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) -> bool {
34 if self.is_valid_size(puzzle.size()) {
35 self.scramble_with_rng(puzzle, rng);
36 true
37 } else {
38 false
39 }
40 }
41
42 fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R);
48}
49
50#[derive(Clone, Default, Debug, PartialEq, Eq)]
55#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
56pub struct RandomInvertibleState;
57
58impl Scrambler for RandomInvertibleState {
59 fn is_valid_size(&self, size: Size) -> bool {
60 size.width() > 1 && size.height() > 1
61 }
62
63 fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) {
64 puzzle.reset();
65
66 let n = puzzle.num_pieces();
67 let mut parity = false;
68 for i in 0..n - 2 {
69 let j = rng.random_range(i..n);
71
72 if i != j {
74 puzzle.swap_non_gap_pieces(i, j);
75 parity = !parity;
76 }
77 }
78
79 if parity {
81 puzzle.swap_non_gap_pieces(n - 2, n - 1);
82 }
83 }
84}
85
86#[derive(Clone, Default, Debug, PartialEq, Eq)]
89#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
90pub struct RandomState;
91
92impl Scrambler for RandomState {
93 fn is_valid_size(&self, _size: Size) -> bool {
94 true
95 }
96
97 fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) {
98 let (w, h) = puzzle.size().into();
99
100 if w == 1 {
101 puzzle.reset();
102 let d = rng.random_range(0..h);
103 puzzle.apply_move(Move::new(Direction::Down, d));
104 return;
105 }
106
107 if h == 1 {
108 puzzle.reset();
109 let r = rng.random_range(0..w);
110 puzzle.apply_move(Move::new(Direction::Right, r));
111 return;
112 }
113
114 RandomInvertibleState.scramble_with_rng(puzzle, rng);
115
116 let (d, r) = (rng.random_range(0..h), rng.random_range(0..w));
118
119 puzzle.apply_move(Move::new(Direction::Down, d));
120 puzzle.apply_move(Move::new(Direction::Right, r));
121 }
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
126#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
127pub struct RandomMoves {
128 pub moves: u64,
130 pub allow_backtracking: bool,
134 pub allow_illegal_moves: bool,
138}
139
140impl Scrambler for RandomMoves {
141 fn is_valid_size(&self, size: Size) -> bool {
142 if (size.width() == 1 || size.height() == 1)
146 && !self.allow_backtracking
147 && !self.allow_illegal_moves
148 {
149 self.moves < size.area()
150 } else {
151 true
152 }
153 }
154
155 fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) {
156 puzzle.reset();
157
158 let mut last_dir = None::<Direction>;
159 for _ in 0..self.moves {
160 let dir = {
161 let mut d = rng.random::<Direction>();
162 while (!self.allow_backtracking && last_dir == Some(d.inverse()))
163 || (!self.allow_illegal_moves && !puzzle.can_move_dir(d))
164 {
165 d = rng.random();
166 }
167 d
168 };
169
170 last_dir = Some(dir);
171 puzzle.try_move_dir(dir);
172 }
173 }
174}
175
176#[derive(Clone, Debug, PartialEq, Eq)]
179#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
180pub struct Cycle {
181 pub length: u64,
183}
184
185impl Scrambler for Cycle {
186 fn is_valid_size(&self, size: Size) -> bool {
187 size.width() > 1 && size.height() > 1
189 }
190
191 fn scramble_with_rng<P: SlidingPuzzle, R: Rng>(&self, puzzle: &mut P, rng: &mut R) {
192 puzzle.reset();
193
194 let n = puzzle.num_pieces();
195 let cycle_len = (self.length).min(if n.is_multiple_of(2) { n - 1 } else { n });
196 let max = if cycle_len % 2 == 0 { n - 2 } else { n };
197 let pieces = rand::seq::index::sample(rng, max as usize, cycle_len as usize);
198
199 for i in 1..cycle_len as usize {
200 puzzle.try_swap_pieces(pieces.index(0) as u64, pieces.index(i) as u64);
201 }
202
203 if self.length.is_multiple_of(2) {
204 puzzle.try_swap_pieces(n - 2, n - 1);
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use crate::puzzle::puzzle::Puzzle;
212
213 use super::*;
214
215 mod random_invertible_state {
216 use rand::SeedableRng as _;
217 use rand_xoshiro::Xoroshiro128StarStar;
218
219 use super::*;
220
221 const SEED: [u8; 16] = [
222 160, 108, 126, 255, 147, 210, 122, 252, 71, 77, 144, 13, 167, 11, 225, 93,
223 ];
224
225 #[test]
226 fn test_gap_in_bottom_right() {
227 let mut rng = Xoroshiro128StarStar::from_seed(SEED);
228
229 for s in 2..10 {
230 let mut p = Puzzle::new(Size::new(s, s).unwrap());
231
232 for _ in 0..100 {
233 RandomInvertibleState.scramble_with_rng(&mut p, &mut rng);
234 assert_eq!(p.gap_position_xy(), (s - 1, s - 1));
235 }
236 }
237 }
238 }
239
240 mod random_state {
241 use rand::SeedableRng as _;
242 use rand_xoshiro::Xoroshiro128StarStar;
243
244 use crate::puzzle::{label::label::RowGrids, solvable::Solvable as _};
245
246 use super::*;
247
248 const SEED: [u8; 16] = [
249 160, 108, 126, 255, 147, 210, 122, 252, 71, 77, 144, 13, 167, 11, 225, 93,
250 ];
251
252 #[test]
253 fn test_solvable() {
254 let mut rng = Xoroshiro128StarStar::from_seed(SEED);
255
256 for (w, h) in [(1, 1), (1, 4), (4, 1), (2, 2), (4, 4), (10, 2), (20, 20)] {
257 let mut p = Puzzle::new(Size::new(w, h).unwrap());
258
259 for _ in 0..100 {
260 RandomState.scramble_with_rng(&mut p, &mut rng);
261 assert!(RowGrids.is_solvable(&p));
262 }
263 }
264 }
265 }
266}
267
268#[cfg(all(feature = "nightly", test))]
269mod benchmarks {
270 extern crate test;
271
272 use rand::SeedableRng;
273 use rand_xoshiro::Xoroshiro128StarStar;
274 use test::Bencher;
275
276 use crate::puzzle::puzzle::Puzzle;
277
278 use super::*;
279
280 #[bench]
281 fn bench_random_state(b: &mut Bencher) {
282 let mut p = Puzzle::new(Size::new(100, 100).unwrap());
283 let mut rng = Xoroshiro128StarStar::seed_from_u64(0);
284
285 b.iter(|| RandomState.scramble_with_rng(&mut p, &mut rng));
286 }
287}