use model::{compute_model, variables::create_tileset};
use crate::{utils::{get_n, DominoError, Model, Puzzle, ResultTranslator}, Solution, Tile};
mod model;
pub fn validate_puzzle(puzzle: &Puzzle, solution: &Solution) -> Result<(), DominoError> {
let string_model = compute_model(puzzle, solution)?;
let solver_result = Model::execute(string_model.clone());
if let Ok(translator) = solver_result {
let missing_tiles = puzzle.0.iter().filter(|tile| tile.is_none()).count() as f64;
let objective = translator.get_objective();
if objective == missing_tiles {
Ok(())
} else {
let solution: Vec<Option<Tile>> = model_solution_parse(translator, puzzle).expect("Failed to parse solution");
Err(DominoError::ModelError(
format!("Found another solution: {solution:?}") ))
}
} else {
Err(DominoError::ModelError(
"Model failed execution".to_string(),
))
}
}
fn model_solution_parse(
translator: ResultTranslator,
puzzle: &Puzzle,
) -> Result<Vec<Option<Tile>>, DominoError> {
let variables: std::collections::HashMap<String, f64> = translator._get_variables();
let n: i32 = get_n(puzzle)?;
let tileset: Vec<(usize, usize)> = create_tileset(n as usize);
let tileset_digits: usize = (tileset.len() as f32).log10().floor() as usize + 1;
let sequence_digits: usize = (puzzle.0.len() as f32).log10().floor() as usize + 1;
let mut solution: Vec<Option<Tile>> = puzzle.0.clone();
for variable in variables.into_iter().filter(|variable| variable.1 == 1.0) {
let variable_label: String = variable.0;
let tile_index: usize = variable_label[1..1 + tileset_digits]
.parse::<usize>()
.unwrap();
let position_index: usize = variable_label
[1 + tileset_digits..1 + tileset_digits + sequence_digits]
.parse::<usize>()
.unwrap();
solution[position_index] =
Some((tileset[tile_index].0 as i32, tileset[tile_index].1 as i32).into());
}
Ok(solution)
}
#[cfg(test)]
mod tests {
use super::validate_puzzle;
#[test]
fn test_validate_valid_puzzle_with_single_hole() {
let puzzle = vec![
Some((0, 0).into()),
Some((0, 1).into()),
Some((1, 1).into()),
Some((1, 2).into()),
Some((2, 2).into()),
None,
None,
None,
];
let solution = vec![
(0, 0).into(),
(0, 1).into(),
(1, 1).into(),
(1, 2).into(),
(2, 2).into(),
(2, 3).into(),
(3, 3).into(),
(3, 0).into(),
];
println!("Testing valid puzzle with single hole: {:?}", puzzle);
println!("Solve model:");
println!("Validate model:");
let result = validate_puzzle(&puzzle.into(), &solution.into());
println!("Validation result: {:?}", result);
assert!(result.is_ok());
}
#[test]
fn test_validate_valid_puzzle_with_multiple_holes() {
let puzzle = vec![
None,
Some((0, 1).into()),
None,
Some((1, 2).into()),
Some((2, 2).into()),
None,
None,
None,
];
let solution = vec![
(0, 0).into(),
(0, 1).into(),
(1, 1).into(),
(1, 2).into(),
(2, 2).into(),
(2, 3).into(),
(3, 3).into(),
(3, 0).into(),
];
println!("Testing valid puzzle with multiple holes: {:?}", puzzle);
assert!(validate_puzzle(&puzzle.into(), &solution).is_ok());
}
#[test]
fn test_validate_empty_puzzle() {
let puzzle = vec![];
let solution = vec![
(0, 0).into(),
(0, 1).into(),
(1, 1).into(),
(1, 2).into(),
(2, 2).into(),
(2, 3).into(),
(3, 3).into(),
(3, 0).into(),
];
println!("Testing empty puzzle: {:?}", puzzle);
let result = validate_puzzle(&puzzle.into(), &solution);
println!("Validation result: {:?}", result);
assert!(result.is_err());
}
#[test]
fn test_validate_double_tiles_no_orientation() {
let puzzle = vec![Some((0, 0).into()), None, None, None, None, None, None];
let solution = vec![
(0, 0).into(),
(0, 1).into(),
(1, 1).into(),
(1, 2).into(),
(2, 2).into(),
(2, 3).into(),
(3, 3).into(),
(3, 0).into(),
];
println!("Testing double tiles no orientation: {:?}", puzzle);
let result = validate_puzzle(&puzzle.into(), &solution);
println!("Validation result: {:?}", result);
assert!(result.is_err());
}
#[test]
fn test_validate_single_tile_orientation() {
let puzzle = vec![Some((0, 1).into()), None, None, None, None, None, None];
let solution = vec![
(0, 1).into(),
(1, 1).into(),
(1, 2).into(),
(2, 2).into(),
(2, 3).into(),
(3, 3).into(),
(3, 0).into(),
(0, 0).into(),
];
println!("Testing single tile orientation: {:?}", puzzle);
let result = validate_puzzle(&puzzle.into(), &solution);
println!("Validation result: {:?}", result);
assert!(result.is_err());
}
#[test]
fn test_validate_invalid_puzzle_empty() {
let puzzle = vec![None; 8];
let solution = vec![
(0, 1).into(),
(1, 1).into(),
(1, 2).into(),
(2, 2).into(),
(2, 3).into(),
(3, 3).into(),
(3, 0).into(),
(0, 0).into(),
];
println!("Testing invalid empty puzzle: {:?}", puzzle);
let result = validate_puzzle(&puzzle.into(), &solution);
println!("Validation result: {:?}", result);
assert!(result.is_err());
}
#[test]
fn test_validate_invalid_puzzle_invalid_size() {
let puzzle = vec![None; 9];
let result = validate_puzzle(&puzzle.into(), &vec![]);
println!("Validation result: {:?}", result);
assert!(result.is_err());
}
#[test]
fn test_validate_puzzle_with_ambiguous_solution() {
let puzzle = vec![
Some((0, 0).into()),
None,
None,
None,
None,
None,
None,
None,
];
let solution = vec![
(0, 0).into(),
(0, 1).into(),
(1, 1).into(),
(1, 2).into(),
(2, 2).into(),
(2, 3).into(),
(3, 3).into(),
(3, 0).into(),
];
println!("Testing puzzle with an ambiguous solution: {:?}", puzzle);
let result = validate_puzzle(&puzzle.into(), &solution);
println!("Validation result: {:?}", result);
assert!(result.is_err());
}
}