From 4eded9446b95c93a8a4feecc69e234d0fe9696d4 Mon Sep 17 00:00:00 2001 From: Alex Bethel Date: Sat, 18 Dec 2021 21:03:00 -0600 Subject: [PATCH] Room generation I'm too tired to write good code today, but this sorta works I guess --- Cargo.lock | 80 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/game.rs | 9 +++- src/main.rs | 1 + src/rooms.rs | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 src/rooms.rs diff --git a/Cargo.lock b/Cargo.lock index c565913..66546cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,29 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "dungeon_game" version = "0.1.0" dependencies = [ + "grid", "pancurses", + "rand", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "grid" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bed7999ee197fb0a5736c294af0289caf9681fafabd57ea7fa6af61bf3a19eec" +dependencies = [ + "no-std-compat", ] [[package]] @@ -47,6 +69,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "pancurses" version = "0.17.0" @@ -76,6 +104,58 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 10655d5..3776aa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] pancurses = "0.17.0" +rand = "0.8.4" +grid = "0.6.0" diff --git a/src/game.rs b/src/game.rs index 3bd691b..6e1f3ac 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,5 +1,7 @@ use pancurses::Window; +use crate::rooms; + /// A dungeon root. pub struct Dungeon { main_branch: DungeonBranch, @@ -27,7 +29,7 @@ pub struct DungeonLevel { tiles: [[DungeonTile; LEVEL_SIZE.0]; LEVEL_SIZE.1], } -/// The smallest possible independent location in the dungeon, +/// The smallest measurable independent location in the dungeon, /// corresponding to a single character on the screen. #[derive(Debug, Clone, Copy)] pub enum DungeonTile { @@ -41,7 +43,8 @@ impl DungeonLevel { /// configuration. pub fn new(cfg: &BranchConfig) -> Self { Self { - tiles: [[DungeonTile::Floor; LEVEL_SIZE.0]; LEVEL_SIZE.1], + // tiles: [[DungeonTile::Floor; LEVEL_SIZE.0]; LEVEL_SIZE.1], + tiles: rooms::generate_level(30, &mut rand::thread_rng()), } } @@ -57,6 +60,8 @@ impl DungeonLevel { }); } } + + // Leave the cursor at the lower-left. win.mv(0, 0); } } diff --git a/src/main.rs b/src/main.rs index 6dda9ea..7aaf042 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use game::{BranchConfig, DungeonLevel}; use pancurses::{endwin, initscr}; mod game; +mod rooms; fn main() { let window = initscr(); diff --git a/src/rooms.rs b/src/rooms.rs new file mode 100644 index 0000000..5f08bd2 --- /dev/null +++ b/src/rooms.rs @@ -0,0 +1,114 @@ +//! Generator for levels that consist of a number of rooms connected +//! by hallways. + +use std::ops::Range; + +use grid::Grid; +use rand::Rng; + +use crate::game::{DungeonTile, LEVEL_SIZE}; + +/// Generates a grid of the given size containing rooms connected by +/// passages. +pub fn generate(n_rooms: usize, rng: &mut impl Rng, size: (usize, usize)) -> Grid { + let mut grid = Grid::init(size.1, size.0, DungeonTile::Wall); + let rooms = gen_room_bounds(n_rooms, size, rng); // TODO: arg order + + for room in rooms { + for (x, y) in room.tiles() { + grid[y][x] = DungeonTile::Floor; + } + } + + grid +} + +/// Generates a grid of the statically-known level size. +pub fn generate_level( + n_rooms: usize, + rng: &mut impl Rng, +) -> [[DungeonTile; LEVEL_SIZE.0]; LEVEL_SIZE.1] { + // FIXME: This function is atrocious. We do an allocation here + // when we theoretically doesn't need to (we get a heap-allocated + // Grid back, when we know statically that it's LEVEL_SIZE so we + // could allocate it on the stack)... + let grid = generate(n_rooms, rng, LEVEL_SIZE); + + // ...and then we use a pointless default of DungeonTile::Floor + // here then copy in the real data from `grid`. + let mut data = [[DungeonTile::Floor; LEVEL_SIZE.0]; LEVEL_SIZE.1]; + for (value, slot) in Iterator::zip( + grid.into_vec().into_iter(), + data.iter_mut().flat_map(|elem| elem.iter_mut()), + ) { + *slot = value; + } + + data +} + +/// The bounding box of a room. +struct RoomBounds { + ul_corner: (usize, usize), + size: (usize, usize), +} + +impl RoomBounds { + /// Iterates over the tiles contained within the room. + pub fn tiles(&self) -> impl Iterator { + let (x_min, y_min) = self.ul_corner; + let (x_max, y_max) = (x_min + self.size.0, y_min + self.size.1); + + (y_min..y_max).flat_map(move |y| (x_min..x_max).map(move |x| (x, y))) + } + + /// Returns whether the two rooms are overlapping. + pub fn overlapping(&self, other: &Self) -> bool { + fn range_overlapping(a: Range, b: Range) -> bool { + if a.start > b.start { + range_overlapping(b, a) + } else { + a.end > b.start + } + } + + !range_overlapping( + self.ul_corner.0..self.ul_corner.0 + self.size.0, + other.ul_corner.0..other.ul_corner.0 + other.size.0, + ) && !range_overlapping( + self.ul_corner.1..self.ul_corner.1 + self.size.1, + other.ul_corner.1..other.ul_corner.1 + other.size.1, + ) + } +} + +/// The possible sizes of a room, on both the x and y axes. +const ROOM_SIZE_LIMITS: Range = 4..8; + +/// Generates bounds for a set of at most `n_rooms` nonoverlapping +/// rooms within a region of size `region_size`. +fn gen_room_bounds( + n_rooms: usize, + region_size: (usize, usize), + rng: &mut impl Rng, +) -> Vec { + let mut v: Vec = Vec::new(); + + for _ in 0..n_rooms { + let size = ( + rng.gen_range(ROOM_SIZE_LIMITS), + rng.gen_range(ROOM_SIZE_LIMITS), + ); + let ul_corner = ( + rng.gen_range(0..region_size.0 - size.0), + rng.gen_range(0..region_size.1 - size.1), + ); + + let new_room = RoomBounds { ul_corner, size }; + if v.iter().all(|room| !room.overlapping(&new_room)) { + v.push(new_room) + } + } + + v +}