diff --git a/README.md b/README.md index 015198d..6d38d66 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,12 @@ Here is a demonstration of mazes being generated with the various algorithms. [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_division_method) [Code](./src/map.rs#L287) +### Wilson's algorithm `--wilson` + +![Animated demo of the algorithm](./animations/wilson.webp) +[Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Wilson's_algorithm) +[Code](./src/map.rs#L360) + ## Usage ``` @@ -48,6 +54,7 @@ FLAGS: --prim Use Prim's algorithm for maze generation --ab Use the Aldous-Broder algorithm for maze generation --div Use the recursive division method for maze generation + --wilson Use Wilson's algorithm (loop-erased random walk) for maze generation -h, --help Prints help information OPTIONS: diff --git a/animations/wilson.webp b/animations/wilson.webp new file mode 100644 index 0000000..e11612b Binary files /dev/null and b/animations/wilson.webp differ diff --git a/src/main.rs b/src/main.rs index 683fd3f..95f00c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,12 +83,19 @@ fn main() { .help("Use the recursive division method for maze generation") .display_order(9), ) + .arg( + Arg::with_name("WILSON") + .long("wilson") + .help("Use Wilson's algorithm (loop-erased random walk) for maze generation") + .display_order(9), + ) .group(ArgGroup::with_name("ALGORITHM").args(&[ "DFS", "TREE", "PRIM", "AB", "DIV", + "WILSON", ])) .get_matches(); @@ -158,6 +165,8 @@ fn main() { Map::generate_ab(rows, columns, start_pos, initial_peek_fn, peek_fn) } else if matches.is_present("DIV") { Map::generate_div(rows, columns, initial_peek_fn, peek_fn) + } else if matches.is_present("WILSON") { + Map::generate_wilson(rows, columns, start_pos, initial_peek_fn, peek_fn) } else { Map::generate_dfs(rows, columns, start_pos, initial_peek_fn, peek_fn) }; diff --git a/src/map.rs b/src/map.rs index 0384a16..617ea1b 100644 --- a/src/map.rs +++ b/src/map.rs @@ -356,6 +356,57 @@ impl Map { map } + pub fn generate_wilson(rows: usize, columns: usize, start: Position, mut initial_peek: F, mut peek: G) -> Map + where + F: FnMut(&Map), + G: FnMut(&Map, &Position, &Direction), + { + let mut map = Map::new(rows, columns); + initial_peek(&map); + let mut rng = thread_rng(); + + let mut in_map = HashSet::new(); + in_map.insert(start); + let mut unvisited: Vec<_> = (0..rows) + .flat_map(|r| (0..columns).filter_map(move |c| Some(Position(r, c)).filter(|p| p != &start))) + .collect(); + + let mut path: Vec<(Position, Direction)> = Vec::new(); + let mut moved_positions = Vec::with_capacity(4); + while !unvisited.is_empty() { + let mut current = unvisited[rng.gen_range(0, unvisited.len())]; + + while !in_map.contains(¤t) { + moved_positions.clear(); + moved_positions.extend( + DIRECTIONS + .iter() + .filter_map(|d| map.move_in_direction(¤t, d).map(|m| (m, d))), + ); + if let Some((next, direction)) = moved_positions.choose(&mut rng) { + if let Some(index) = path.iter().position(|p| &p.0 == next) { + for (p, d) in path.drain(index..).rev() { + map.set(&p, &d, true); + peek(&map, &p, &d); + } + } else { + map.set(¤t, direction, false); + peek(&map, ¤t, direction); + path.push((current, **direction)); + } + current = *next; + } + } + + for (p, _) in path.drain(..) { + in_map.insert(p); + unvisited.swap_remove(unvisited.iter().position(|u| u == &p).unwrap()); + } + } + + map + } + pub fn set_above(&mut self, pos: &Position, closed: bool) { self.set_below(&Position(pos.0 - 1, pos.1), closed); }