domino_lib/generate/
mod.rs

1//! This module provides functionality for generating valid puzzle instances.
2//!
3//! It includes a function to generate a puzzle with a valid Eulerian cycle and remove a specified number of tiles.
4
5use rand::Rng;
6use crate::{utils::find_eulerian_cycle, Graph, Puzzle, Solution, Tile};
7
8/// Generates a puzzle with a valid Eulerian cycle and removes a specified number of tiles.
9///
10/// This function constructs a `Graph` representation of the puzzle, finds an Eulerian cycle,
11/// and converts the cycle into a `Solution`. Then, it removes a specified number of tiles
12/// either sequentially or randomly, based on the `random` flag.
13///
14/// # Arguments
15///
16/// * `n` - The size of the puzzle.
17/// * `minimum_removals` - The number of tiles to remove from the solution.
18/// * `random` - If `true`, removes tiles at random; otherwise, removes them sequentially.
19///
20/// # Returns
21///
22/// A `Puzzle` instance with `Some(Tile)` values for placed tiles and `None` for removed tiles.
23pub fn generate_puzzle(n: usize, minimum_removals: usize, random: bool) -> Puzzle {
24    let graph = Graph::regular(n);
25    let eulerian_cycle = find_eulerian_cycle(&graph)(random);
26
27    // Convert Eulerian cycle to a solution of tiles
28    let solution: Solution = eulerian_cycle
29        .windows(2)
30        .map(|arc| {
31            Tile(
32                arc[0].clone().try_into().unwrap(),
33                arc[1].clone().try_into().unwrap(),
34            )
35        })
36        .collect();
37
38    // Convert solution into a puzzle with all tiles initially placed
39    let mut puzzle: Puzzle = solution.into_iter().map(Some).collect();
40
41    // Remove the specified number of tiles
42    if puzzle.len() > minimum_removals {
43        if random {
44            let mut rng = rand::thread_rng();
45            for _ in 0..minimum_removals {
46                let mut index = rng.gen_range(0..puzzle.len());
47                while puzzle[index].is_none() {
48                    index = rng.gen_range(0..puzzle.len());
49                }
50                puzzle[index] = None;
51            }
52        } else {
53            for index in 0..minimum_removals {
54                puzzle[index] = None;
55            }
56        }
57    }
58
59    puzzle
60}