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// }