Part 3: The Dungeon
Adding stuff to build the dungeon, rooms, and hallways between them.
This commit is contained in:
128
src/main.rs
128
src/main.rs
@@ -1,3 +1,5 @@
|
||||
use rand::Rng;
|
||||
use std::cmp;
|
||||
use tcod::colors::*;
|
||||
use tcod::console::*;
|
||||
|
||||
@@ -15,6 +17,11 @@ const COLOR_DARK_GROUND: Color = Color {
|
||||
b: 150,
|
||||
};
|
||||
|
||||
// parameters for dungeon generator
|
||||
const ROOM_MAX_SIZE: i32 = 10;
|
||||
const ROOM_MIN_SIZE: i32 = 6;
|
||||
const MAX_ROOMS: i32 = 30;
|
||||
|
||||
// 20 FPS max
|
||||
const LIMIT_FPS: i32 = 20;
|
||||
|
||||
@@ -141,17 +148,120 @@ struct Game {
|
||||
map: Map,
|
||||
}
|
||||
|
||||
fn make_map() -> Map {
|
||||
// fill map with "unblocked" tiles
|
||||
let mut map = vec![vec![Tile::empty(); MAP_HEIGHT as usize]; MAP_WIDTH as usize];
|
||||
fn make_map(player: &mut Object) -> Map {
|
||||
// fill map with "blocked" tiles
|
||||
let mut map = vec![vec![Tile::wall(); MAP_HEIGHT as usize]; MAP_WIDTH as usize];
|
||||
|
||||
// place two pillars to test the map
|
||||
map[30][22] = Tile::wall();
|
||||
map[50][22] = Tile::wall();
|
||||
let mut rooms = vec![];
|
||||
|
||||
for _ in 0..MAX_ROOMS {
|
||||
// random width and height
|
||||
let w = rand::thread_rng().gen_range(ROOM_MIN_SIZE, ROOM_MAX_SIZE + 1);
|
||||
let h = rand::thread_rng().gen_range(ROOM_MIN_SIZE, ROOM_MAX_SIZE + 1);
|
||||
// random position without going out of the boundaries of the map
|
||||
let x = rand::thread_rng().gen_range(0, MAP_WIDTH - w);
|
||||
let y = rand::thread_rng().gen_range(0, MAP_HEIGHT - h);
|
||||
|
||||
let new_room = Rect::new(x, y, w, h);
|
||||
|
||||
// run through the other rooms and see if they intersect with this one
|
||||
let failed = rooms
|
||||
.iter()
|
||||
.any(|other_room| new_room.intersects_with(other_room));
|
||||
|
||||
if !failed {
|
||||
// room is valid, no intersections
|
||||
create_room(new_room, &mut map);
|
||||
|
||||
// center coordinates of the new room (useful later)
|
||||
let (new_x, new_y) = new_room.center();
|
||||
|
||||
if rooms.is_empty() {
|
||||
// this is the first room, have the user start here
|
||||
player.x = new_x;
|
||||
player.y = new_y;
|
||||
} else {
|
||||
// all other rooms after the first - connect them to the prev with a tunnel
|
||||
|
||||
// center coordinates of the previous room
|
||||
let (prev_x, prev_y) = rooms[rooms.len() - 1].center();
|
||||
|
||||
// get a random bool value
|
||||
if rand::random() {
|
||||
// first move horizontally then vertically
|
||||
create_h_tunnel(prev_x, new_x, prev_y, &mut map);
|
||||
create_v_tunnel(prev_y, new_y, new_x, &mut map);
|
||||
} else {
|
||||
// vertically then horizontally
|
||||
create_v_tunnel(prev_y, new_y, prev_x, &mut map);
|
||||
create_h_tunnel(prev_x, new_x, new_y, &mut map);
|
||||
}
|
||||
}
|
||||
|
||||
// append room to the list
|
||||
rooms.push(new_room);
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
// A rectangle on the map, used to characterize a room
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Rect {
|
||||
x1: i32,
|
||||
y1: i32,
|
||||
x2: i32,
|
||||
y2: i32,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
|
||||
Rect {
|
||||
x1: x,
|
||||
y1: y,
|
||||
x2: x + w,
|
||||
y2: y + h,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center(&self) -> (i32, i32) {
|
||||
let center_x = (self.x1 + self.x2) / 2;
|
||||
let center_y = (self.y1 + self.y2) / 2;
|
||||
(center_x, center_y)
|
||||
}
|
||||
|
||||
pub fn intersects_with(&self, other: &Rect) -> bool {
|
||||
// returns true if this rectangle intersects with another one
|
||||
(self.x1 <= other.x2)
|
||||
&& (self.y1 <= other.y2)
|
||||
&& (self.y2 >= other.y1)
|
||||
&& (self.y2 >= other.y1)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_room(room: Rect, map: &mut Map) {
|
||||
// go through the tles in the rectangle and make them passable
|
||||
for x in (room.x1 + 1)..room.x2 {
|
||||
for y in (room.y1 + 1)..room.y2 {
|
||||
map[x as usize][y as usize] = Tile::empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_h_tunnel(x1: i32, x2: i32, y: i32, map: &mut Map) {
|
||||
// horizontal tunnel `min()` and `max()` are used in case `x1 > x2`
|
||||
for x in cmp::min(x1, x2)..(cmp::max(x1, x2) + 1) {
|
||||
map[x as usize][y as usize] = Tile::empty();
|
||||
}
|
||||
}
|
||||
|
||||
fn create_v_tunnel(y1: i32, y2: i32, x: i32, map: &mut Map) {
|
||||
// vertical tunnel
|
||||
for y in cmp::min(y1, y2)..(cmp::max(y1, y2) + 1) {
|
||||
map[x as usize][y as usize] = Tile::empty();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let root = Root::initializer()
|
||||
.font("arial10x10.png", FontLayout::Tcod)
|
||||
@@ -169,14 +279,16 @@ fn main() {
|
||||
let mut player_y = SCREEN_HEIGHT / 2;
|
||||
|
||||
// create object representing the player
|
||||
let player = Object::new(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, '@', WHITE);
|
||||
let player = Object::new(0, 0, '@', WHITE);
|
||||
// create an NPC
|
||||
let npc = Object::new(SCREEN_WIDTH / 2 - 5, SCREEN_HEIGHT / 2, '@', YELLOW);
|
||||
// the list of objects with the two above
|
||||
let mut objects = [player, npc];
|
||||
|
||||
// Generate map
|
||||
let game = Game { map: make_map() };
|
||||
let game = Game {
|
||||
map: make_map(&mut objects[0]),
|
||||
};
|
||||
|
||||
// Main game loop
|
||||
while !tcod.root.window_closed() {
|
||||
|
||||
Reference in New Issue
Block a user