Browse Source

Chapter 23 - beta quality but mostly working.

Herbert Wolverson 5 years ago
parent
commit
f7787b2225

+ 1 - 0
.vscode/spellright.dict

@@ -25,3 +25,4 @@ Kyzrati
 gotchas
 Rustacean
 Nethack
+Bresenham

BIN
book/src/c23-s1.jpg


File diff suppressed because it is too large
+ 449 - 1
book/src/chapter_23.md


+ 16 - 15
chapter-23-bsproom-dungeons/src/main.rs

@@ -35,6 +35,7 @@ pub mod particle_system;
 pub mod hunger_system;
 pub mod rex_assets;
 pub mod trigger_system;
+pub mod map_builders;
 
 rltk::add_wasm_support!();
 
@@ -307,20 +308,21 @@ impl State {
         // Build a new map and place the player
         let worldmap;
         let current_depth;
+        let player_start;
         {
             let mut worldmap_resource = self.ecs.write_resource::<Map>();
             current_depth = worldmap_resource.depth;
-            *worldmap_resource = Map::new_map_rooms_and_corridors(current_depth + 1);
+            let (newmap, start) = map_builders::build_random_map(current_depth + 1);
+            *worldmap_resource = newmap;
+            player_start = start;
             worldmap = worldmap_resource.clone();
         }
 
         // Spawn bad guys
-        for room in worldmap.rooms.iter().skip(1) {
-            spawner::spawn_room(&mut self.ecs, room, current_depth+1);
-        }
+        map_builders::spawn(&worldmap, &mut self.ecs, current_depth+1);
 
         // Place the player and update resources
-        let (player_x, player_y) = worldmap.rooms[0].center();
+        let (player_x, player_y) = (player_start.x, player_start.y);
         let mut player_position = self.ecs.write_resource::<Point>();
         *player_position = Point::new(player_x, player_y);
         let mut position_components = self.ecs.write_storage::<Position>();
@@ -360,19 +362,20 @@ impl State {
 
         // Build a new map and place the player
         let worldmap;
+        let player_start;
         {
             let mut worldmap_resource = self.ecs.write_resource::<Map>();
-            *worldmap_resource = Map::new_map_rooms_and_corridors(1);
+            let (newmap, start) = map_builders::build_random_map(1);
+            player_start = start;
+            *worldmap_resource = newmap;
             worldmap = worldmap_resource.clone();
         }
 
         // Spawn bad guys
-        for room in worldmap.rooms.iter().skip(1) {
-            spawner::spawn_room(&mut self.ecs, room, 1);
-        }
+        map_builders::spawn(&worldmap, &mut self.ecs, 1);
 
         // Place the player and update resources
-        let (player_x, player_y) = worldmap.rooms[0].center();
+        let (player_x, player_y) = (player_start.x, player_start.y);
         let player_entity = spawner::player(&mut self.ecs, player_x, player_y);
         let mut player_position = self.ecs.write_resource::<Point>();
         *player_position = Point::new(player_x, player_y);
@@ -439,15 +442,13 @@ fn main() {
 
     gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
 
-    let map : Map = Map::new_map_rooms_and_corridors(1);
-    let (player_x, player_y) = map.rooms[0].center();
+    let (map, player_start) = map_builders::build_random_map(1);
+    let (player_x, player_y) = (player_start.x, player_start.y);
 
     let player_entity = spawner::player(&mut gs.ecs, player_x, player_y);
 
     gs.ecs.insert(rltk::RandomNumberGenerator::new());
-    for room in map.rooms.iter().skip(1) {
-        spawner::spawn_room(&mut gs.ecs, room, 1);
-    }
+    map_builders::spawn(&map, &mut gs.ecs, 1);
 
     gs.ecs.insert(map);
     gs.ecs.insert(Point::new(player_x, player_y));

+ 4 - 74
chapter-23-bsproom-dungeons/src/map.rs

@@ -1,7 +1,6 @@
 extern crate rltk;
-use rltk::{ RGB, Rltk, Console, RandomNumberGenerator, BaseMap, Algorithm2D, Point };
+use rltk::{ RGB, Rltk, Console, BaseMap, Algorithm2D, Point };
 use super::{Rect};
-use std::cmp::{max, min};
 extern crate specs;
 use specs::prelude::*;
 use serde::{Serialize, Deserialize};
@@ -38,33 +37,6 @@ impl Map {
         (y as usize * self.width as usize) + x as usize
     }
 
-    fn apply_room_to_map(&mut self, room : &Rect) {
-        for y in room.y1 +1 ..= room.y2 {
-            for x in room.x1 + 1 ..= room.x2 {
-                let idx = self.xy_idx(x, y);
-                self.tiles[idx] = TileType::Floor;
-            }
-        }
-    }
-
-    fn apply_horizontal_tunnel(&mut self, x1:i32, x2:i32, y:i32) {
-        for x in min(x1,x2) ..= max(x1,x2) {
-            let idx = self.xy_idx(x, y);
-            if idx > 0 && idx < self.width as usize * self.height as usize {
-                self.tiles[idx as usize] = TileType::Floor;
-            }
-        }
-    }
-
-    fn apply_vertical_tunnel(&mut self, y1:i32, y2:i32, x:i32) {
-        for y in min(y1,y2) ..= max(y1,y2) {
-            let idx = self.xy_idx(x, y);
-            if idx > 0 && idx < self.width as usize * self.height as usize {
-                self.tiles[idx as usize] = TileType::Floor;
-            }
-        }
-    }
-
     fn is_exit_valid(&self, x:i32, y:i32) -> bool {
         if x < 1 || x > self.width-1 || y < 1 || y > self.height-1 { return false; }
         let idx = (y * self.width) + x;
@@ -83,10 +55,9 @@ impl Map {
         }
     }
 
-    /// Makes a new map using the algorithm from http://rogueliketutorials.com/tutorials/tcod/part-3/
-    /// This gives a handful of random rooms and corridors joining them together.
-    pub fn new_map_rooms_and_corridors(new_depth : i32) -> Map {
-        let mut map = Map{
+    /// Generates an empty map, consisting entirely of solid walls
+    pub fn new(new_depth : i32) -> Map {
+        Map{
             tiles : vec![TileType::Wall; MAPCOUNT],
             rooms : Vec::new(),
             width : MAPWIDTH as i32,
@@ -97,48 +68,7 @@ impl Map {
             tile_content : vec![Vec::new(); MAPCOUNT],
             depth: new_depth,
             bloodstains: HashSet::new()
-        };
-
-        const MAX_ROOMS : i32 = 30;
-        const MIN_SIZE : i32 = 6;
-        const MAX_SIZE : i32 = 10;
-
-        let mut rng = RandomNumberGenerator::new();
-
-        for _i in 0..MAX_ROOMS {
-            let w = rng.range(MIN_SIZE, MAX_SIZE);
-            let h = rng.range(MIN_SIZE, MAX_SIZE);
-            let x = rng.roll_dice(1, map.width - w - 1) - 1;
-            let y = rng.roll_dice(1, map.height - h - 1) - 1;
-            let new_room = Rect::new(x, y, w, h);
-            let mut ok = true;
-            for other_room in map.rooms.iter() {
-                if new_room.intersect(other_room) { ok = false }
-            }
-            if ok {
-                map.apply_room_to_map(&new_room);
-
-                if !map.rooms.is_empty() {
-                    let (new_x, new_y) = new_room.center();
-                    let (prev_x, prev_y) = map.rooms[map.rooms.len()-1].center();
-                    if rng.range(0,1) == 1 {
-                        map.apply_horizontal_tunnel(prev_x, new_x, prev_y);
-                        map.apply_vertical_tunnel(prev_y, new_y, new_x);
-                    } else {
-                        map.apply_vertical_tunnel(prev_y, new_y, prev_x);
-                        map.apply_horizontal_tunnel(prev_x, new_x, new_y);
-                    }
-                }
-
-                map.rooms.push(new_room);
-            }
         }
-
-        let stairs_position = map.rooms[map.rooms.len()-1].center();
-        let stairs_idx = map.xy_idx(stairs_position.0, stairs_position.1);
-        map.tiles[stairs_idx] = TileType::DownStairs;
-
-        map
     }
 }
 

+ 138 - 0
chapter-23-bsproom-dungeons/src/map_builders/bsp_dungeon.rs

@@ -0,0 +1,138 @@
+use super::{MapBuilder, Map, Rect, apply_room_to_map, 
+    TileType, Position, spawner};
+use rltk::RandomNumberGenerator;
+use specs::prelude::*;
+
+pub struct BspDungeonBuilder {}
+
+fn add_subrects(rects : &mut Vec<Rect>, rect : Rect) {
+    let width = i32::abs(rect.x1 - rect.x2);
+    let height = i32::abs(rect.y1 - rect.y2);
+    let half_width = i32::max(width / 2, 1);
+    let half_height = i32::max(height / 2, 1);
+
+    rects.push(Rect::new( rect.x1, rect.y1, half_width, half_height ));
+	rects.push(Rect::new( rect.x1, rect.y1 + half_height, half_width, half_height ));
+	rects.push(Rect::new( rect.x1 + half_width, rect.y1, half_width, half_height ));
+	rects.push(Rect::new( rect.x1 + half_width, rect.y1 + half_height, half_width, half_height ));
+}
+
+fn get_random_rect(rects : &mut Vec<Rect>, rng : &mut RandomNumberGenerator) -> Rect {
+    if rects.len() == 1 { return rects[0]; }
+    let idx = (rng.roll_dice(1, rects.len() as i32)-1) as usize;
+    rects[idx]
+}
+
+fn get_random_sub_rect(rect : Rect, rng : &mut RandomNumberGenerator) -> Rect {
+    let mut result = rect;
+    let rect_width = i32::abs(rect.x1 - rect.x2);
+    let rect_height = i32::abs(rect.y1 - rect.y2);
+
+    let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 10))-1) + 1;
+    let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 10))-1) + 1;
+
+    result.x1 += rng.roll_dice(1, 6)-1;
+    result.y1 += rng.roll_dice(1, 6)-1;
+    result.x2 = result.x1 + w;
+    result.y2 = result.y1 + h;
+
+    result
+}
+
+fn is_possible(map : &mut Map, mut rect : Rect) -> bool {
+    if rect.x1 > 0 {
+        rect.x1 -= 1;
+        rect.x2 += 1;
+    }
+    if rect.y1 > 0 {
+        rect.y1 -= 1;
+        rect.y1 += 1;
+    }
+
+    for y in rect.y1 ..= rect.y2 {
+        for x in rect.x1 ..= rect.x2 {
+            if x > map.width-1 { return false; }
+            if y > map.height-1 { return false; }
+            if x < 0 { return false; }
+            if y < 0 { return false; }
+            let idx = map.xy_idx(x, y);
+            if map.tiles[idx] != TileType::Wall { return false; }
+        }
+    }
+
+    true
+}
+
+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 idx = map.xy_idx(x, y);
+        map.tiles[idx] = TileType::Floor;
+    }
+}
+
+impl MapBuilder for BspDungeonBuilder {
+    fn build(new_depth: i32) -> (Map, Position) {
+        let mut map = Map::new(new_depth);
+        let mut rng = RandomNumberGenerator::new();
+
+        let mut rects : Vec<Rect> = Vec::new(); // Vector to hold our rectangles as we divide
+        rects.push( Rect::new(2, 2, map.width-5, map.height-5) ); // Start with a single map-sized rectangle
+        let first_room = rects[0];
+        add_subrects(&mut rects, first_room); // Divide the first room
+
+        // Up to 240 times, we get a random rectangle and divide it. If its possible to squeeze a
+        // room in there, we place it and add it to the rooms list.
+        let mut n_rooms = 0;
+        while n_rooms < 240 {
+            let rect = get_random_rect(&mut rects, &mut rng);
+            let candidate = get_random_sub_rect(rect, &mut rng);
+
+            if is_possible(&mut map, candidate) {
+                apply_room_to_map(&mut map, &candidate);
+                map.rooms.push(candidate);
+                add_subrects(&mut rects, rect);
+            }
+
+            n_rooms += 1;
+        }
+
+        // Now we sort the rooms
+        map.rooms.sort_by(|a,b| a.x1.cmp(&b.x1) );
+
+        // Now we want corridors
+        for i in 0..map.rooms.len()-1 {
+            let room = map.rooms[i];
+            let next_room = map.rooms[i+1];
+            let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2))-1);
+            let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2))-1);
+            let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2))-1);
+            let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2))-1);
+            draw_corridor(&mut map, start_x, start_y, end_x, end_y);
+        }
+
+        let player_start = map.rooms[0].center();
+        let stairs = map.rooms[map.rooms.len()-1].center();
+        let stairs_idx = map.xy_idx(stairs.0, stairs.1);
+        map.tiles[stairs_idx] = TileType::DownStairs;
+        (map, Position{ x : player_start.0, y : player_start.1 })
+    }
+
+    fn spawn(map : &Map, ecs : &mut World, new_depth: i32) {
+        for room in map.rooms.iter().skip(1) {
+            spawner::spawn_room(ecs, room, new_depth);
+        }
+    }
+}

+ 29 - 0
chapter-23-bsproom-dungeons/src/map_builders/common.rs

@@ -0,0 +1,29 @@
+use super::{Map, Rect, TileType};
+use std::cmp::{max, min};
+
+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 {
+            let idx = map.xy_idx(x, y);
+            map.tiles[idx] = TileType::Floor;
+        }
+    }
+}
+
+pub fn apply_horizontal_tunnel(map : &mut Map, x1:i32, x2:i32, y:i32) {
+    for x in min(x1,x2) ..= max(x1,x2) {
+        let idx = map.xy_idx(x, y);
+        if idx > 0 && idx < map.width as usize * map.height as usize {
+            map.tiles[idx as usize] = TileType::Floor;
+        }
+    }
+}
+
+pub fn apply_vertical_tunnel(map : &mut Map, y1:i32, y2:i32, x:i32) {
+    for y in min(y1,y2) ..= max(y1,y2) {
+        let idx = map.xy_idx(x, y);
+        if idx > 0 && idx < map.width as usize * map.height as usize {
+            map.tiles[idx as usize] = TileType::Floor;
+        }
+    }
+}

+ 27 - 0
chapter-23-bsproom-dungeons/src/map_builders/mod.rs

@@ -0,0 +1,27 @@
+use super::{Map, Rect, TileType, Position, spawner};
+mod simple_map;
+use simple_map::SimpleMapBuilder;
+mod bsp_dungeon;
+use bsp_dungeon::BspDungeonBuilder;
+mod common;
+use common::*;
+use specs::prelude::*;
+
+trait MapBuilder {
+    fn build(new_depth: i32) -> (Map, Position);
+    fn spawn(map : &Map, ecs : &mut World, new_depth: i32);
+}
+
+pub fn build_random_map(new_depth: i32) -> (Map, Position) {
+    let mut rng = rltk::RandomNumberGenerator::new();
+    let builder = rng.roll_dice(1, 2);
+    println!("Builder roll: {}", builder);
+    match builder {
+        2 => BspDungeonBuilder::build(new_depth),
+        _ => SimpleMapBuilder::build(new_depth)
+    }    
+}
+
+pub fn spawn(map : &Map, ecs : &mut World, new_depth: i32) {
+    BspDungeonBuilder::spawn(map, ecs, new_depth);
+}

+ 67 - 0
chapter-23-bsproom-dungeons/src/map_builders/simple_map.rs

@@ -0,0 +1,67 @@
+use super::{MapBuilder, Map, Rect, apply_room_to_map, 
+    apply_horizontal_tunnel, apply_vertical_tunnel, TileType,
+    Position, spawner};
+use rltk::RandomNumberGenerator;
+use specs::prelude::*;
+
+pub struct SimpleMapBuilder {}
+
+impl MapBuilder for SimpleMapBuilder {
+    fn build(new_depth: i32) -> (Map, Position) {
+        let mut map = Map::new(new_depth);
+        let playerpos = SimpleMapBuilder::rooms_and_corridors(&mut map);
+        (map, playerpos)
+    }
+
+    fn spawn(map : &Map, ecs : &mut World, new_depth: i32) {
+        for room in map.rooms.iter().skip(1) {
+            spawner::spawn_room(ecs, room, new_depth);
+        }
+    }
+}
+
+impl SimpleMapBuilder {
+    fn rooms_and_corridors(map : &mut Map) -> Position {
+        const MAX_ROOMS : i32 = 30;
+        const MIN_SIZE : i32 = 6;
+        const MAX_SIZE : i32 = 10;
+
+        let mut rng = RandomNumberGenerator::new();
+
+        for _i in 0..MAX_ROOMS {
+            let w = rng.range(MIN_SIZE, MAX_SIZE);
+            let h = rng.range(MIN_SIZE, MAX_SIZE);
+            let x = rng.roll_dice(1, map.width - w - 1) - 1;
+            let y = rng.roll_dice(1, map.height - h - 1) - 1;
+            let new_room = Rect::new(x, y, w, h);
+            let mut ok = true;
+            for other_room in map.rooms.iter() {
+                if new_room.intersect(other_room) { ok = false }
+            }
+            if ok {
+                apply_room_to_map(map, &new_room);
+
+                if !map.rooms.is_empty() {
+                    let (new_x, new_y) = new_room.center();
+                    let (prev_x, prev_y) = map.rooms[map.rooms.len()-1].center();
+                    if rng.range(0,1) == 1 {
+                        apply_horizontal_tunnel(map, prev_x, new_x, prev_y);
+                        apply_vertical_tunnel(map, prev_y, new_y, new_x);
+                    } else {
+                        apply_vertical_tunnel(map, prev_y, new_y, prev_x);
+                        apply_horizontal_tunnel(map, prev_x, new_x, new_y);
+                    }
+                }
+
+                map.rooms.push(new_room);
+            }
+        }
+
+        let stairs_position = map.rooms[map.rooms.len()-1].center();
+        let stairs_idx = map.xy_idx(stairs_position.0, stairs_position.1);
+        map.tiles[stairs_idx] = TileType::DownStairs;
+
+        let start_pos = map.rooms[0].center();
+        Position{ x: start_pos.0, y: start_pos.1 }
+    }
+}