domino_lib/generate_valid/mod.rs
1use crate::utils::find_eulerian_cycle;
2use crate::{ComplexityClass, DominoError};
3use crate::{Graph, Puzzle, Solution, Tile};
4use rand::seq::{IteratorRandom, SliceRandom};
5use rand::thread_rng;
6use reinsert::reinsert_tile_and_check;
7use std::vec;
8
9mod reinsert;
10
11/// Generates a valid puzzle of size `n` that can later be customized by complexity class and randomness.
12///
13/// This function adopts a functional style by returning a series of closures, each progressively refining the puzzle generation process.
14/// The first closure takes a `ComplexityClass`, and the second closure takes a boolean (`random`).
15/// These parameters are intended to be used within the function, guiding the puzzle generation based on the chosen complexity and randomness.
16///
17/// # Arguments
18/// * `n` - The number of tiles in the puzzle.
19///
20/// # Returns
21/// A function that, given a `ComplexityClass`, returns another function that generates a `Puzzle`.
22pub fn generate_valid_puzzle(
23 n: usize,
24) -> Box<dyn Fn(ComplexityClass) -> Result<Puzzle, DominoError>> {
25 Box::new(move |c: ComplexityClass| -> Result<Puzzle, DominoError> {
26 let graph = Graph::regular(n);
27
28 // The Eulerian path is represented as a sequence of nodes traversed.
29 // The solution is built by grouping nodes in pairs and constructing tiles from each pair.
30 let solution: Solution = find_eulerian_cycle(&graph)(true)
31 .windows(2)
32 .map(|arc| {
33 Tile(
34 arc[0].clone().try_into().unwrap(),
35 arc[1].clone().try_into().unwrap(),
36 )
37 })
38 .collect();
39
40 // Removes recursively tiles and ensures the puzzle is still valid after removal
41 // Repeats this until the classification class is not matched then returns the puzzle
42 let puzzle = generate_puzzle(solution)(c);
43 puzzle
44 })
45}
46
47/// Generates a puzzle based on a given solution and complexity class.
48///
49/// This function returns a closure that takes a `ComplexityClass` and returns another closure,
50/// which, when called with a boolean (`random`), generates a puzzle.
51///
52/// # Arguments
53/// * `solution` - The reference solution from which to generate the puzzle.
54///
55/// # Returns
56/// A function that, given a `ComplexityClass`, returns another function that generates a `Puzzle`.
57fn generate_puzzle(
58 solution: Solution,
59) -> Box<dyn Fn(ComplexityClass) -> Result<Puzzle, DominoError>> {
60 Box::new(move |c: ComplexityClass| {
61 // Create an empty puzzle structure with all positions set to `None`.
62 let starting_puzzle: Vec<Option<Tile>> = vec![None; solution.clone().len()];
63
64 // Set the initial removed tiles to the totality of the tiles in the sequence.
65 let mut removed_tiles = solution.clone();
66 removed_tiles.shuffle(&mut thread_rng());
67
68 // The first tile to be reinserted
69 let anchor: usize = (0..solution.len())
70 .into_iter()
71 .choose(&mut thread_rng())
72 .unwrap();
73
74 // Attempt to reinsert removed tiles while checking puzzle validity.
75 let puzzle: Result<Puzzle, DominoError> =
76 reinsert_tile_and_check(starting_puzzle, solution.clone(), removed_tiles, c, anchor);
77 puzzle
78 })
79}
80
81// #[cfg(test)]
82// mod tests {
83// use super::*;
84// use crate::graphs::ComplexityClass;
85// use crate::Puzzle;
86
87// /// Helper function to check if a puzzle is valid.
88// fn is_valid_puzzle(puzzle: &Puzzle) -> bool {
89// // Ensure the puzzle contains at least one tile.
90// !puzzle.is_empty() && puzzle.iter().any(|tile| tile.is_some())
91// }
92
93// /// Test puzzle generation for different values of `n` (n=3).
94// #[test]
95// fn test_generate_valid_puzzle_n3() {
96// let generator = generate_valid_puzzle(3);
97// let result = generator(ComplexityClass(1));
98// assert!(result.is_ok(), "Puzzle generation should succeed for n=3");
99// let puzzle = result.unwrap();
100// assert!(
101// is_valid_puzzle(&puzzle),
102// "Generated puzzle should be valid for n=3"
103// );
104// }
105
106// /// Test puzzle generation for different values of `n` (n=5).
107// #[test]
108// fn test_generate_valid_puzzle_n5() {
109// let generator = generate_valid_puzzle(5);
110// let result = generator(ComplexityClass(3));
111// assert!(result.is_ok(), "Puzzle generation should succeed for n=5");
112// let puzzle = result.unwrap();
113// assert!(
114// is_valid_puzzle(&puzzle),
115// "Generated puzzle should be valid for n=5"
116// );
117// }
118
119// /// Test puzzle generation for a given complexity class (ComplexityClass(1)).
120// #[test]
121// fn test_puzzle_complexity_class1() {
122// let generator = generate_valid_puzzle(4);
123// let result = generator(ComplexityClass(1));
124// assert!(
125// result.is_ok(),
126// "Puzzle generation should succeed for ComplexityClass(1)"
127// );
128// let puzzle = result.unwrap();
129// assert!(
130// is_valid_puzzle(&puzzle),
131// "Generated puzzle should be valid for ComplexityClass(1)"
132// );
133// }
134
135// /// Test puzzle generation for a given complexity class (ComplexityClass(3)).
136// #[test]
137// fn test_generate_puzzle_complexity_class3() {
138// let generator = generate_valid_puzzle(4);
139// let result = generator(ComplexityClass(3));
140// assert!(
141// result.is_ok(),
142// "Puzzle generation should succeed for ComplexityClass(3)"
143// );
144// let puzzle = result.unwrap();
145// assert!(
146// is_valid_puzzle(&puzzle),
147// "Generated puzzle should be valid for ComplexityClass(3)"
148// );
149// }
150// }