1use model::{compute_model, variables::create_tileset};
2
3use crate::{utils::{get_n, DominoError, Model, Puzzle, ResultTranslator}, Solution, Tile};
4
5mod model;
6
7pub fn validate_puzzle(puzzle: &Puzzle, solution: &Solution) -> Result<(), DominoError> {
33 let string_model = compute_model(puzzle, solution)?;
35 let solver_result = Model::execute(string_model.clone());
38
39 if let Ok(translator) = solver_result {
42 let missing_tiles = puzzle.0.iter().filter(|tile| tile.is_none()).count() as f64;
44
45 let objective = translator.get_objective();
47 if objective == missing_tiles {
48 Ok(())
49 } else {
50 let solution: Vec<Option<Tile>> = model_solution_parse(translator, puzzle).expect("Failed to parse solution");
51 Err(DominoError::ModelError(
52 format!("Found another solution: {solution:?}")))
54 }
55 } else {
56 Err(DominoError::ModelError(
57 "Model failed execution".to_string(),
58 ))
59 }
60}
61
62fn model_solution_parse(
66 translator: ResultTranslator,
67 puzzle: &Puzzle,
68) -> Result<Vec<Option<Tile>>, DominoError> {
69 let variables: std::collections::HashMap<String, f64> = translator._get_variables();
70 let n: i32 = get_n(puzzle)?;
71 let tileset: Vec<(usize, usize)> = create_tileset(n as usize);
72 let tileset_digits: usize = (tileset.len() as f32).log10().floor() as usize + 1;
73 let sequence_digits: usize = (puzzle.0.len() as f32).log10().floor() as usize + 1;
74 let mut solution: Vec<Option<Tile>> = puzzle.0.clone();
75 for variable in variables.into_iter().filter(|variable| variable.1 == 1.0) {
76 let variable_label: String = variable.0;
77 let tile_index: usize = variable_label[1..1 + tileset_digits]
78 .parse::<usize>()
79 .unwrap();
80 let position_index: usize = variable_label
81 [1 + tileset_digits..1 + tileset_digits + sequence_digits]
82 .parse::<usize>()
83 .unwrap();
84 solution[position_index] =
85 Some((tileset[tile_index].0 as i32, tileset[tile_index].1 as i32).into());
86 }
87 Ok(solution)
88}
89
90#[cfg(test)]
91mod tests {
92
93 use super::validate_puzzle;
94
95 #[test]
96 fn test_validate_valid_puzzle_with_single_hole() {
97 let puzzle = vec![
98 Some((0, 0).into()),
99 Some((0, 1).into()),
100 Some((1, 1).into()),
101 Some((1, 2).into()),
102 Some((2, 2).into()),
103 None,
104 None,
105 None,
106 ];
107 let solution = vec![
108 (0, 0).into(),
109 (0, 1).into(),
110 (1, 1).into(),
111 (1, 2).into(),
112 (2, 2).into(),
113 (2, 3).into(),
114 (3, 3).into(),
115 (3, 0).into(),
116 ];
117 println!("Testing valid puzzle with single hole: {:?}", puzzle);
118 println!("Solve model:");
119 println!("Validate model:");
120 let result = validate_puzzle(&puzzle.into(), &solution.into());
121 println!("Validation result: {:?}", result);
122 assert!(result.is_ok());
123 }
124
125 #[test]
126 fn test_validate_valid_puzzle_with_multiple_holes() {
127 let puzzle = vec![
128 None,
129 Some((0, 1).into()),
130 None,
131 Some((1, 2).into()),
132 Some((2, 2).into()),
133 None,
134 None,
135 None,
136 ];
137 let solution = vec![
138 (0, 0).into(),
139 (0, 1).into(),
140 (1, 1).into(),
141 (1, 2).into(),
142 (2, 2).into(),
143 (2, 3).into(),
144 (3, 3).into(),
145 (3, 0).into(),
146 ];
147 println!("Testing valid puzzle with multiple holes: {:?}", puzzle);
148 assert!(validate_puzzle(&puzzle.into(), &solution).is_ok());
149 }
150
151 #[test]
152 fn test_validate_empty_puzzle() {
153 let puzzle = vec![];
154 let solution = vec![
155 (0, 0).into(),
156 (0, 1).into(),
157 (1, 1).into(),
158 (1, 2).into(),
159 (2, 2).into(),
160 (2, 3).into(),
161 (3, 3).into(),
162 (3, 0).into(),
163 ];
164 println!("Testing empty puzzle: {:?}", puzzle);
165 let result = validate_puzzle(&puzzle.into(), &solution);
166 println!("Validation result: {:?}", result);
167 assert!(result.is_err());
168 }
169
170 #[test]
171 fn test_validate_double_tiles_no_orientation() {
172 let puzzle = vec![Some((0, 0).into()), None, None, None, None, None, None];
173 let solution = vec![
174 (0, 0).into(),
175 (0, 1).into(),
176 (1, 1).into(),
177 (1, 2).into(),
178 (2, 2).into(),
179 (2, 3).into(),
180 (3, 3).into(),
181 (3, 0).into(),
182 ];
183 println!("Testing double tiles no orientation: {:?}", puzzle);
184 let result = validate_puzzle(&puzzle.into(), &solution);
185 println!("Validation result: {:?}", result);
186 assert!(result.is_err());
187 }
188
189 #[test]
190 fn test_validate_single_tile_orientation() {
191 let puzzle = vec![Some((0, 1).into()), None, None, None, None, None, None];
192 let solution = vec![
193 (0, 1).into(),
194 (1, 1).into(),
195 (1, 2).into(),
196 (2, 2).into(),
197 (2, 3).into(),
198 (3, 3).into(),
199 (3, 0).into(),
200 (0, 0).into(),
201 ];
202 println!("Testing single tile orientation: {:?}", puzzle);
203 let result = validate_puzzle(&puzzle.into(), &solution);
204 println!("Validation result: {:?}", result);
205 assert!(result.is_err());
206 }
207
208 #[test]
209 fn test_validate_invalid_puzzle_empty() {
210 let puzzle = vec![None; 8];
211 let solution = vec![
212 (0, 1).into(),
213 (1, 1).into(),
214 (1, 2).into(),
215 (2, 2).into(),
216 (2, 3).into(),
217 (3, 3).into(),
218 (3, 0).into(),
219 (0, 0).into(),
220 ];
221 println!("Testing invalid empty puzzle: {:?}", puzzle);
222 let result = validate_puzzle(&puzzle.into(), &solution);
223 println!("Validation result: {:?}", result);
224 assert!(result.is_err());
225 }
226
227 #[test]
228 fn test_validate_invalid_puzzle_invalid_size() {
229 let puzzle = vec![None; 9];
230 let result = validate_puzzle(&puzzle.into(), &vec![]);
231 println!("Validation result: {:?}", result);
232 assert!(result.is_err());
233 }
234
235 #[test]
236 fn test_validate_puzzle_with_ambiguous_solution() {
237 let puzzle = vec![
238 Some((0, 0).into()),
239 None,
240 None,
241 None,
242 None,
243 None,
244 None,
245 None,
246 ];
247 let solution = vec![
248 (0, 0).into(),
249 (0, 1).into(),
250 (1, 1).into(),
251 (1, 2).into(),
252 (2, 2).into(),
253 (2, 3).into(),
254 (3, 3).into(),
255 (3, 0).into(),
256 ];
257 println!("Testing puzzle with an ambiguous solution: {:?}", puzzle);
258 let result = validate_puzzle(&puzzle.into(), &solution);
259 println!("Validation result: {:?}", result);
260 assert!(result.is_err());
261 }
262}