Browse Source

Chapter 31 is complete - symmetry and brush size.

Herbert Wolverson 4 years ago
parent
commit
9b09a3b9fb

+ 9 - 0
Cargo.lock

@@ -513,6 +513,15 @@ dependencies = [
 [[package]]
 name = "chapter-31-symmetry"
 version = "0.1.0"
+dependencies = [
+ "rltk 0.4.1 (git+https://github.com/thebracket/rltk_rs)",
+ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "specs-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "web-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "cloudabi"

BIN
book/src/c31-s1.gif


BIN
book/src/c31-s2.gif


+ 268 - 0
book/src/chapter_31.md

@@ -14,8 +14,276 @@ In the previous chapter on Diffusion-Limited Aggregation, we introduced two new
 
 ## Building the library versions
 
+We'll start by moving the `DLASymmetry` enumeration out of `dla.rs` and into `common.rs`. We'll also change its name, since we are no longer binding it to a specific algorithm:
 
+```rust
+#[derive(PartialEq, Copy, Clone)]
+pub enum Symmetry { None, Horizontal, Vertical, Both }
+```
 
+At the end of `common.rs`, we can add the following:
+
+```rust
+pub fn paint(map: &mut Map, mode: Symmetry, brush_size: i32, x: i32, y:i32) {
+    match mode {
+        Symmetry::None => apply_paint(map, brush_size, x, y),
+        Symmetry::Horizontal => {
+            let center_x = map.width / 2;
+            if x == center_x {
+                apply_paint(map, brush_size, x, y);                    
+            } else {
+                let dist_x = i32::abs(center_x - x);
+                apply_paint(map, brush_size, center_x + dist_x, y);
+                apply_paint(map, brush_size, center_x - dist_x, y);
+            }
+        }
+        Symmetry::Vertical => {
+            let center_y = map.height / 2;
+            if y == center_y {
+                apply_paint(map, brush_size, x, y);
+            } else {
+                let dist_y = i32::abs(center_y - y);
+                apply_paint(map, brush_size, x, center_y + dist_y);
+                apply_paint(map, brush_size, x, center_y - dist_y);
+            }
+        }
+        Symmetry::Both => {
+            let center_x = map.width / 2;
+            let center_y = map.height / 2;
+            if x == center_x && y == center_y {
+                apply_paint(map, brush_size, x, y);
+            } else {
+                let dist_x = i32::abs(center_x - x);
+                apply_paint(map, brush_size, center_x + dist_x, y);
+                apply_paint(map, brush_size, center_x - dist_x, y);
+                let dist_y = i32::abs(center_y - y);
+                apply_paint(map, brush_size, x, center_y + dist_y);
+                apply_paint(map, brush_size, x, center_y - dist_y);
+            }
+        }
+    }
+}
+
+fn apply_paint(map: &mut Map, brush_size: i32, x: i32, y: i32) {
+    match brush_size {
+        1 => {
+            let digger_idx = map.xy_idx(x, y);
+            map.tiles[digger_idx] = TileType::Floor;
+        }
+
+        _ => {
+            let half_brush_size = brush_size / 2;
+            for brush_y in y-half_brush_size .. y+half_brush_size {
+                for brush_x in x-half_brush_size .. x+half_brush_size {
+                    if brush_x > 1 && brush_x < map.width-1 && brush_y > 1 && brush_y < map.height-1 {
+                        let idx = map.xy_idx(brush_x, brush_y);
+                        map.tiles[idx] = TileType::Floor;
+                    }
+                }
+            }
+        }
+    }
+}
+```
+
+This shouldn't be a surprise: it's the *exact* same code we had in `dla.rs` - but with the `&mut self` removed and instead taking parameters.
+
+## Modifying dla.rs to use it
+
+It's relatively simple to modify `dla.rs` to use it. Replace all `DLASymmetry` references with `Symmetry`. Replace all calls to `self.paint(x, y)` with `paint(&mut self.map, self.symmetry, self.brush_size, x, y);`. You can check the source code to see the changes - no need to repeat them all here. Make sure to include `paint` and `Symmetry` in the list of included functions at the top, too.
+
+Like a lot of refactoring, the proof of the pudding is that if you `cargo run` your code - nothing has changed! We won't bother with a screenshot to show that it's the same as last time!
+
+## Modifying Drunkard's Walk to use it
+
+We'll start by modifying the `DrunkardSettings` struct to accept the two new features:
+
+```rust
+pub struct DrunkardSettings {
+    pub spawn_mode : DrunkSpawnMode,
+    pub drunken_lifetime : i32,
+    pub floor_percent: f32,
+    pub brush_size: i32,
+    pub symmetry: Symmetry
+}
+```
+
+The compiler will complain that we aren't setting these in our constructors, so we'll add some default values:
+
+```rust
+pub fn open_area(new_depth : i32) -> DrunkardsWalkBuilder {
+    DrunkardsWalkBuilder{
+        map : Map::new(new_depth),
+        starting_position : Position{ x: 0, y : 0 },
+        depth : new_depth,
+        history: Vec::new(),
+        noise_areas : HashMap::new(),
+        settings : DrunkardSettings{
+            spawn_mode: DrunkSpawnMode::StartingPoint,
+            drunken_lifetime: 400,
+            floor_percent: 0.5,
+            brush_size: 1,
+            symmetry: Symmetry::None
+        }
+    }
+}
+```
+
+We need to make similar changes to the other constructors - just adding `brush_size` and `symmetry` to each of the `DrunkardSettings` builders.
+
+We also need to replace the line:
+
+```rust
+self.map.tiles[drunk_idx] = TileType::DownStairs;
+```
+
+With:
+
+```rust
+paint(&mut self.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y);
+self.map.tiles[drunk_idx] = TileType::DownStairs;
+```
+
+The double-draw retains the function of adding `>` symbols to show you the walker's path, while retaining the overdraw of the paint function.
+
+## Making a wider-carving drunk
+
+To test this out, we'll add a new constructor to `drunkard.rs`:
+
+```rust
+pub fn fat_passages(new_depth : i32) -> DrunkardsWalkBuilder {
+    DrunkardsWalkBuilder{
+        map : Map::new(new_depth),
+        starting_position : Position{ x: 0, y : 0 },
+        depth : new_depth,
+        history: Vec::new(),
+        noise_areas : HashMap::new(),
+        settings : DrunkardSettings{
+            spawn_mode: DrunkSpawnMode::Random,
+            drunken_lifetime: 100,
+            floor_percent: 0.4,
+            brush_size: 2,
+            symmetry: Symmetry::None
+        }
+    }
+}
+```
+
+We'll also quickly modify `random_builder` in `map_builders/mod.rs` to showcase this one:
+
+```rust
+pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
+    /*let mut rng = rltk::RandomNumberGenerator::new();
+    let builder = rng.roll_dice(1, 12);
+    match builder {
+        1 => Box::new(BspDungeonBuilder::new(new_depth)),
+        2 => Box::new(BspInteriorBuilder::new(new_depth)),
+        3 => Box::new(CellularAutomotaBuilder::new(new_depth)),
+        4 => Box::new(DrunkardsWalkBuilder::open_area(new_depth)),
+        5 => Box::new(DrunkardsWalkBuilder::open_halls(new_depth)),
+        6 => Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)),
+        7 => Box::new(MazeBuilder::new(new_depth)),
+        8 => Box::new(DLABuilder::walk_inwards(new_depth)),
+        9 => Box::new(DLABuilder::walk_outwards(new_depth)),
+        10 => Box::new(DLABuilder::central_attractor(new_depth)),
+        11 => Box::new(DLABuilder::insectoid(new_depth)),
+        _ => Box::new(SimpleMapBuilder::new(new_depth))
+    }*/
+    Box::new(DrunkardsWalkBuilder::fat_passages(new_depth))
+}
+```
+
+This shows an immediate change in the map generation:
+
+![Screenshot](./c31-s1.gif).
+
+Notice how the "fatter" digging area gives more open halls. It also runs in half the time, since we exhaust the desired floor count *much* more quickly.
+
+## Adding Symmetry
+
+Like DLA, symmetrical drunkards can make interesting looking maps. We'll add one more constructor:
+
+```rust
+pub fn fearful_symmetry(new_depth : i32) -> DrunkardsWalkBuilder {
+    DrunkardsWalkBuilder{
+        map : Map::new(new_depth),
+        starting_position : Position{ x: 0, y : 0 },
+        depth : new_depth,
+        history: Vec::new(),
+        noise_areas : HashMap::new(),
+        settings : DrunkardSettings{
+            spawn_mode: DrunkSpawnMode::Random,
+            drunken_lifetime: 100,
+            floor_percent: 0.4,
+            brush_size: 1,
+            symmetry: Symmetry::Both
+        }
+    }
+}
+```
+
+We also modify our `random_builder` function to use it:
+
+```rust
+pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
+    /*let mut rng = rltk::RandomNumberGenerator::new();
+    let builder = rng.roll_dice(1, 12);
+    match builder {
+        1 => Box::new(BspDungeonBuilder::new(new_depth)),
+        2 => Box::new(BspInteriorBuilder::new(new_depth)),
+        3 => Box::new(CellularAutomotaBuilder::new(new_depth)),
+        4 => Box::new(DrunkardsWalkBuilder::open_area(new_depth)),
+        5 => Box::new(DrunkardsWalkBuilder::open_halls(new_depth)),
+        6 => Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)),
+        7 => Box::new(MazeBuilder::new(new_depth)),
+        8 => Box::new(DLABuilder::walk_inwards(new_depth)),
+        9 => Box::new(DLABuilder::walk_outwards(new_depth)),
+        10 => Box::new(DLABuilder::central_attractor(new_depth)),
+        11 => Box::new(DLABuilder::insectoid(new_depth)),
+        _ => Box::new(SimpleMapBuilder::new(new_depth))
+    }*/
+    Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth))
+}
+```
+
+`cargo run` will render results something like these:
+
+![Screenshot](./c31-s2.gif).
+
+Notice how the symmetry is applied (really fast - we're blasting out the floor tiles, now!) - and then unreachable areas are culled, getting rid of part of the map. This is quite a nice map!
+
+## Restoring Randomness Once More
+
+Once again, we add our new algorithms to the `random_builder` function in `map_builders/mod.rs`:
+
+```rust
+pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
+    let mut rng = rltk::RandomNumberGenerator::new();
+    let builder = rng.roll_dice(1, 14);
+    match builder {
+        1 => Box::new(BspDungeonBuilder::new(new_depth)),
+        2 => Box::new(BspInteriorBuilder::new(new_depth)),
+        3 => Box::new(CellularAutomotaBuilder::new(new_depth)),
+        4 => Box::new(DrunkardsWalkBuilder::open_area(new_depth)),
+        5 => Box::new(DrunkardsWalkBuilder::open_halls(new_depth)),
+        6 => Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)),
+        7 => Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)),
+        8 => Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)),
+        9 => Box::new(MazeBuilder::new(new_depth)),
+        10 => Box::new(DLABuilder::walk_inwards(new_depth)),
+        11 => Box::new(DLABuilder::walk_outwards(new_depth)),
+        12 => Box::new(DLABuilder::central_attractor(new_depth)),
+        13 => Box::new(DLABuilder::insectoid(new_depth)),
+        _ => Box::new(SimpleMapBuilder::new(new_depth))
+    }
+}
+```
+
+We're up to 14 algorithms, now! We have an increasingly varied game!
+
+## Wrap-Up
+
+This chapter has demonstrated a very useful tool for the game programmer: finding a handy algorithm, making it generic, and using it in other parts of your code. It's rare to guess exactly what you need up-front (and there's a *lot* to be said for "you won't need it" - implementing things when you *do* need them), so it's a valuable weapon in our arsenal to be able to quickly refactor our code for reuse.
 
 **The source code for this chapter may be found [here](https://github.com/thebracket/rustrogueliketutorial/tree/master/chapter-31-symmetry)**
 

+ 1 - 1
chapter-31-symmetry/src/main.rs

@@ -137,7 +137,7 @@ impl GameState for State {
                 draw_map(&self.mapgen_history[self.mapgen_index], ctx);
 
                 self.mapgen_timer += ctx.frame_time_ms;
-                if self.mapgen_timer > 20.0 {
+                if self.mapgen_timer > 200.0 {
                     self.mapgen_timer = 0.0;
                     self.mapgen_index += 1;
                     if self.mapgen_index == self.mapgen_history.len() {

+ 79 - 15
chapter-31-symmetry/src/map_builders/common.rs

@@ -2,6 +2,9 @@ use super::{Map, Rect, TileType};
 use std::cmp::{max, min};
 use std::collections::HashMap;
 
+#[derive(PartialEq, Copy, Clone)]
+pub enum Symmetry { None, Horizontal, Vertical, Both }
+
 pub fn apply_room_to_map(map : &mut Map, room : &Rect) {
     for y in room.y1 +1 ..= room.y2 {
         for x in room.x1 + 1 ..= room.x2 {
@@ -84,21 +87,82 @@ pub fn generate_voronoi_spawn_regions(map: &Map, rng : &mut rltk::RandomNumberGe
 }
 
 pub fn draw_corridor(map: &mut Map, x1:i32, y1:i32, x2:i32, y2:i32) {
-        let mut x = x1;
-        let mut y = y1;
-
-        while x != x2 || y != y2 {
-            if x < x2 {
-                x += 1;
-            } else if x > x2 {
-                x -= 1;
-            } else if y < y2 {
-                y += 1;
-            } else if y > y2 {
-                y -= 1;
+    let mut x = x1;
+    let mut y = y1;
+
+    while x != x2 || y != y2 {
+        if x < x2 {
+            x += 1;
+        } else if x > x2 {
+            x -= 1;
+        } else if y < y2 {
+            y += 1;
+        } else if y > y2 {
+            y -= 1;
+        }
+
+        let idx = map.xy_idx(x, y);
+        map.tiles[idx] = TileType::Floor;
+    }
+}
+
+pub fn paint(map: &mut Map, mode: Symmetry, brush_size: i32, x: i32, y:i32) {
+    match mode {
+        Symmetry::None => apply_paint(map, brush_size, x, y),
+        Symmetry::Horizontal => {
+            let center_x = map.width / 2;
+            if x == center_x {
+                apply_paint(map, brush_size, x, y);                    
+            } else {
+                let dist_x = i32::abs(center_x - x);
+                apply_paint(map, brush_size, center_x + dist_x, y);
+                apply_paint(map, brush_size, center_x - dist_x, y);
+            }
+        }
+        Symmetry::Vertical => {
+            let center_y = map.height / 2;
+            if y == center_y {
+                apply_paint(map, brush_size, x, y);
+            } else {
+                let dist_y = i32::abs(center_y - y);
+                apply_paint(map, brush_size, x, center_y + dist_y);
+                apply_paint(map, brush_size, x, center_y - dist_y);
             }
+        }
+        Symmetry::Both => {
+            let center_x = map.width / 2;
+            let center_y = map.height / 2;
+            if x == center_x && y == center_y {
+                apply_paint(map, brush_size, x, y);
+            } else {
+                let dist_x = i32::abs(center_x - x);
+                apply_paint(map, brush_size, center_x + dist_x, y);
+                apply_paint(map, brush_size, center_x - dist_x, y);
+                let dist_y = i32::abs(center_y - y);
+                apply_paint(map, brush_size, x, center_y + dist_y);
+                apply_paint(map, brush_size, x, center_y - dist_y);
+            }
+        }
+    }
+}
 
-            let idx = map.xy_idx(x, y);
-            map.tiles[idx] = TileType::Floor;
+fn apply_paint(map: &mut Map, brush_size: i32, x: i32, y: i32) {
+    match brush_size {
+        1 => {
+            let digger_idx = map.xy_idx(x, y);
+            map.tiles[digger_idx] = TileType::Floor;
+        }
+
+        _ => {
+            let half_brush_size = brush_size / 2;
+            for brush_y in y-half_brush_size .. y+half_brush_size {
+                for brush_x in x-half_brush_size .. x+half_brush_size {
+                    if brush_x > 1 && brush_x < map.width-1 && brush_y > 1 && brush_y < map.height-1 {
+                        let idx = map.xy_idx(brush_x, brush_y);
+                        map.tiles[idx] = TileType::Floor;
+                    }
+                }
+            }
         }
-    }
+    }
+}

+ 12 - 75
chapter-31-symmetry/src/map_builders/dla.rs

@@ -1,6 +1,7 @@
 use super::{MapBuilder, Map,  
     TileType, Position, spawner, SHOW_MAPGEN_VISUALIZER,
-    remove_unreachable_areas_returning_most_distant, generate_voronoi_spawn_regions};
+    remove_unreachable_areas_returning_most_distant, generate_voronoi_spawn_regions,
+    Symmetry, paint};
 use rltk::RandomNumberGenerator;
 use specs::prelude::*;
 use std::collections::HashMap;
@@ -8,9 +9,6 @@ use std::collections::HashMap;
 #[derive(PartialEq, Copy, Clone)]
 pub enum DLAAlgorithm { WalkInwards, WalkOutwards, CentralAttractor }
 
-#[derive(PartialEq, Copy, Clone)]
-pub enum DLASymmetry { None, Horizontal, Vertical, Both }
-
 pub struct DLABuilder {
     map : Map,
     starting_position : Position,
@@ -19,7 +17,7 @@ pub struct DLABuilder {
     noise_areas : HashMap<i32, Vec<usize>>,
     algorithm : DLAAlgorithm,
     brush_size: i32,
-    symmetry: DLASymmetry,
+    symmetry: Symmetry,
     floor_percent: f32
 }
 
@@ -67,7 +65,7 @@ impl DLABuilder {
             noise_areas : HashMap::new(),
             algorithm: DLAAlgorithm::WalkInwards,
             brush_size: 2,
-            symmetry: DLASymmetry::None,
+            symmetry: Symmetry::None,
             floor_percent: 0.25
         }
     }
@@ -81,7 +79,7 @@ impl DLABuilder {
             noise_areas : HashMap::new(),
             algorithm: DLAAlgorithm::WalkInwards,
             brush_size: 1,
-            symmetry: DLASymmetry::None,
+            symmetry: Symmetry::None,
             floor_percent: 0.25
         }
     }
@@ -95,7 +93,7 @@ impl DLABuilder {
             noise_areas : HashMap::new(),
             algorithm: DLAAlgorithm::WalkOutwards,
             brush_size: 2,
-            symmetry: DLASymmetry::None,
+            symmetry: Symmetry::None,
             floor_percent: 0.25
         }
     }
@@ -109,7 +107,7 @@ impl DLABuilder {
             noise_areas : HashMap::new(),
             algorithm: DLAAlgorithm::CentralAttractor,
             brush_size: 2,
-            symmetry: DLASymmetry::None,
+            symmetry: Symmetry::None,
             floor_percent: 0.25
         }
     }
@@ -123,7 +121,7 @@ impl DLABuilder {
             noise_areas : HashMap::new(),
             algorithm: DLAAlgorithm::CentralAttractor,
             brush_size: 2,
-            symmetry: DLASymmetry::Horizontal,
+            symmetry: Symmetry::Horizontal,
             floor_percent: 0.25
         }
     }
@@ -167,7 +165,7 @@ impl DLABuilder {
                         }
                         digger_idx = self.map.xy_idx(digger_x, digger_y);
                     }
-                    self.paint(prev_x, prev_y);
+                    paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y);
                 }
 
                 DLAAlgorithm::WalkOutwards => {
@@ -184,7 +182,7 @@ impl DLABuilder {
                         }
                         digger_idx = self.map.xy_idx(digger_x, digger_y);
                     }
-                    self.paint(digger_x, digger_y);
+                    paint(&mut self.map, self.symmetry, self.brush_size, digger_x, digger_y);
                 }
 
                 DLAAlgorithm::CentralAttractor => {
@@ -208,7 +206,7 @@ impl DLABuilder {
                         path.remove(0);
                         digger_idx = self.map.xy_idx(digger_x, digger_y);
                     }
-                    self.paint(prev_x, prev_y);
+                    paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y);
                 }
             }
 
@@ -227,66 +225,5 @@ impl DLABuilder {
 
         // Now we build a noise map for use in spawning entities later
         self.noise_areas = generate_voronoi_spawn_regions(&self.map, &mut rng);
-    }
-
-    fn paint(&mut self, x: i32, y:i32) {
-        match self.symmetry {
-            DLASymmetry::None => self.apply_paint(x, y),
-            DLASymmetry::Horizontal => {
-                let center_x = self.map.width / 2;
-                if x == center_x {
-                    self.apply_paint(x, y);                    
-                } else {
-                    let dist_x = i32::abs(center_x - x);
-                    self.apply_paint(center_x + dist_x, y);
-                    self.apply_paint(center_x - dist_x, y);
-                }
-            }
-            DLASymmetry::Vertical => {
-                let center_y = self.map.height / 2;
-                if y == center_y {
-                    self.apply_paint(x, y);
-                } else {
-                    let dist_y = i32::abs(center_y - y);
-                    self.apply_paint(x, center_y + dist_y);
-                    self.apply_paint(x, center_y - dist_y);
-                }
-            }
-            DLASymmetry::Both => {
-                let center_x = self.map.width / 2;
-                let center_y = self.map.height / 2;
-                if x == center_x && y == center_y {
-                    self.apply_paint(x, y);
-                } else {
-                    let dist_x = i32::abs(center_x - x);
-                    self.apply_paint(center_x + dist_x, y);
-                    self.apply_paint(center_x - dist_x, y);
-                    let dist_y = i32::abs(center_y - y);
-                    self.apply_paint(x, center_y + dist_y);
-                    self.apply_paint(x, center_y - dist_y);
-                }
-            }
-        }
-    }
-
-    fn apply_paint(&mut self, x: i32, y: i32) {
-        match self.brush_size {
-            1 => {
-                let digger_idx = self.map.xy_idx(x, y);
-                self.map.tiles[digger_idx] = TileType::Floor;
-            }
-
-            _ => {
-                let half_brush_size = self.brush_size / 2;
-                for brush_y in y-half_brush_size .. y+half_brush_size {
-                    for brush_x in x-half_brush_size .. x+half_brush_size {
-                        if brush_x > 1 && brush_x < self.map.width-1 && brush_y > 1 && brush_y < self.map.height-1 {
-                            let idx = self.map.xy_idx(brush_x, brush_y);
-                            self.map.tiles[idx] = TileType::Floor;
-                        }
-                    }
-                }
-            }
-        }
-    }
+    }    
 }

+ 49 - 5
chapter-31-symmetry/src/map_builders/drunkard.rs

@@ -1,6 +1,7 @@
 use super::{MapBuilder, Map,  
     TileType, Position, spawner, SHOW_MAPGEN_VISUALIZER, 
-    remove_unreachable_areas_returning_most_distant, generate_voronoi_spawn_regions};
+    remove_unreachable_areas_returning_most_distant, generate_voronoi_spawn_regions,
+    paint, Symmetry};
 use rltk::RandomNumberGenerator;
 use specs::prelude::*;
 use std::collections::HashMap;
@@ -11,7 +12,9 @@ pub enum DrunkSpawnMode { StartingPoint, Random }
 pub struct DrunkardSettings {
     pub spawn_mode : DrunkSpawnMode,
     pub drunken_lifetime : i32,
-    pub floor_percent: f32
+    pub floor_percent: f32,
+    pub brush_size: i32,
+    pub symmetry: Symmetry
 }
 
 pub struct DrunkardsWalkBuilder {
@@ -79,7 +82,9 @@ impl DrunkardsWalkBuilder {
             settings : DrunkardSettings{
                 spawn_mode: DrunkSpawnMode::StartingPoint,
                 drunken_lifetime: 400,
-                floor_percent: 0.5
+                floor_percent: 0.5,
+                brush_size: 1,
+                symmetry: Symmetry::None
             }
         }
     }
@@ -94,7 +99,9 @@ impl DrunkardsWalkBuilder {
             settings : DrunkardSettings{
                 spawn_mode: DrunkSpawnMode::Random,
                 drunken_lifetime: 400,
-                floor_percent: 0.5
+                floor_percent: 0.5,
+                brush_size: 1,
+                symmetry: Symmetry::None
             }
         }
     }
@@ -109,7 +116,43 @@ impl DrunkardsWalkBuilder {
             settings : DrunkardSettings{
                 spawn_mode: DrunkSpawnMode::Random,
                 drunken_lifetime: 100,
-                floor_percent: 0.4
+                floor_percent: 0.4,
+                brush_size: 1,
+                symmetry: Symmetry::None
+            }
+        }
+    }
+
+    pub fn fat_passages(new_depth : i32) -> DrunkardsWalkBuilder {
+        DrunkardsWalkBuilder{
+            map : Map::new(new_depth),
+            starting_position : Position{ x: 0, y : 0 },
+            depth : new_depth,
+            history: Vec::new(),
+            noise_areas : HashMap::new(),
+            settings : DrunkardSettings{
+                spawn_mode: DrunkSpawnMode::Random,
+                drunken_lifetime: 100,
+                floor_percent: 0.4,
+                brush_size: 2,
+                symmetry: Symmetry::None
+            }
+        }
+    }
+
+    pub fn fearful_symmetry(new_depth : i32) -> DrunkardsWalkBuilder {
+        DrunkardsWalkBuilder{
+            map : Map::new(new_depth),
+            starting_position : Position{ x: 0, y : 0 },
+            depth : new_depth,
+            history: Vec::new(),
+            noise_areas : HashMap::new(),
+            settings : DrunkardSettings{
+                spawn_mode: DrunkSpawnMode::Random,
+                drunken_lifetime: 100,
+                floor_percent: 0.4,
+                brush_size: 1,
+                symmetry: Symmetry::Both
             }
         }
     }
@@ -153,6 +196,7 @@ impl DrunkardsWalkBuilder {
                 if self.map.tiles[drunk_idx] == TileType::Wall {
                     did_something = true;
                 }
+                paint(&mut self.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y);
                 self.map.tiles[drunk_idx] = TileType::DownStairs;
 
                 let stagger_direction = rng.roll_dice(1, 4);

+ 8 - 6
chapter-31-symmetry/src/map_builders/mod.rs

@@ -28,7 +28,7 @@ pub trait MapBuilder {
 
 pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
     let mut rng = rltk::RandomNumberGenerator::new();
-    let builder = rng.roll_dice(1, 12);
+    let builder = rng.roll_dice(1, 14);
     match builder {
         1 => Box::new(BspDungeonBuilder::new(new_depth)),
         2 => Box::new(BspInteriorBuilder::new(new_depth)),
@@ -36,11 +36,13 @@ pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
         4 => Box::new(DrunkardsWalkBuilder::open_area(new_depth)),
         5 => Box::new(DrunkardsWalkBuilder::open_halls(new_depth)),
         6 => Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)),
-        7 => Box::new(MazeBuilder::new(new_depth)),
-        8 => Box::new(DLABuilder::walk_inwards(new_depth)),
-        9 => Box::new(DLABuilder::walk_outwards(new_depth)),
-        10 => Box::new(DLABuilder::central_attractor(new_depth)),
-        11 => Box::new(DLABuilder::insectoid(new_depth)),
+        7 => Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)),
+        8 => Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)),
+        9 => Box::new(MazeBuilder::new(new_depth)),
+        10 => Box::new(DLABuilder::walk_inwards(new_depth)),
+        11 => Box::new(DLABuilder::walk_outwards(new_depth)),
+        12 => Box::new(DLABuilder::central_attractor(new_depth)),
+        13 => Box::new(DLABuilder::insectoid(new_depth)),
         _ => Box::new(SimpleMapBuilder::new(new_depth))
     }
 }