domino_lib/solve/
mod.rs

1mod model;
2
3use model::{compute_model, variables::create_tileset};
4
5use crate::{
6    utils::{get_n, Model},
7    DominoError, Puzzle, Solution, Tile,
8};
9
10/// Attempts to solve a given puzzle using a recursive backtracking approach.
11///
12/// This function clones the puzzle, determines the missing tiles, and then attempts to solve
13/// the puzzle by filling in the missing tiles while ensuring valid adjacency constraints.
14///
15/// # Arguments
16///
17/// * `puzzle` - A reference to the `Puzzle` structure representing the current puzzle state.
18///
19/// # Returns
20///
21/// * `Ok(Solution)` - If a valid solution is found.
22/// * `Err(DominoError::UnsolvablePuzzle)` - If no solution exists.
23/// * `Err(DominoError::InvalidPuzzle)` - If the puzzle input is invalid.
24pub fn solve_puzzle(puzzle: &Puzzle) -> Result<Solution, DominoError> {
25    let model_string = compute_model(puzzle)?;
26    // println!("Model: {}", model_string);
27    // Execute the model to obtain a solver result.
28    let solver_result = Model::execute(model_string.clone());
29
30    let n = get_n(puzzle)?;
31    let tileset: Vec<Tile> = create_tileset(n as usize)
32        .iter()
33        .map(|tuple| Tile((*tuple).0 as i32, (*tuple).1 as i32).into())
34        .collect();
35    let tileset_digits = (tileset.len() as f32).log10().floor() as usize + 1;
36    let sequence_digits = (puzzle.0.len() as f32).log10().floor() as usize + 1;
37    if let Ok(translator) = solver_result {
38        let mut solution = puzzle.clone();
39        let variables = translator._get_variables();
40        let labels: Vec<String> = variables
41            .iter()
42            .filter_map(|entry: (&String, &f64)| {
43                if entry.1 == &1.0 {
44                    Some(entry.0.clone())
45                } else {
46                    None
47                }
48            })
49            .collect();
50        // println!("Labels: {labels:?}");
51        labels.iter().for_each(|label| {
52            let tile_index: usize = label[1..1 + tileset_digits].parse().unwrap();
53            let position_index: usize = label
54                [1 + tileset_digits..1 + tileset_digits + sequence_digits]
55                .parse()
56                .unwrap();
57            solution.0[position_index] = Some(tileset[tile_index])
58        });
59        Ok(solution.0.iter().map(|option| option.unwrap()).collect())
60    } else {
61        Err(DominoError::ModelError(
62            "Model failed execution".to_string(),
63        ))
64    }
65}