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}