Skip to content

Instantly share code, notes, and snippets.

@saintech
Last active April 4, 2020 22:26
Show Gist options
  • Save saintech/59e250109f7844e980291a3b99e58310 to your computer and use it in GitHub Desktop.
Save saintech/59e250109f7844e980291a3b99e58310 to your computer and use it in GitHub Desktop.
A primitive procedural map generator for maps based on rooms, like in The Binding of Isaac - https://play.rust-lang.org/?gist=59e250109f7844e980291a3b99e58310&edition=2018
use rand::seq::SliceRandom as _;
use rand::Rng;
use std::ops::Range;
const NUMBER_OF_ROOMS: Range<usize> = 9..12;
const SCREEN_WIDTH: usize = 80;
const SCREEN_HEIGHT: usize = SCREEN_WIDTH;
struct Room {
top: bool,
right: bool,
bottom: bool,
left: bool,
}
impl Room {
pub fn new() -> Self {
Room {
top: false,
right: false,
bottom: false,
left: false,
}
}
}
type Map = std::collections::HashMap<(i32, i32), Room>;
fn generate_map() -> Map {
let mut random = rand::thread_rng();
let mut map: Map = Default::default();
map.insert((0, 0), Room::new());
let number_of_rooms = random.gen_range(NUMBER_OF_ROOMS.start, NUMBER_OF_ROOMS.end);
while map.len() < number_of_rooms {
let variants = get_all_variants(&map);
let &(x, y, direction) = variants.choose(&mut random).unwrap();
open_room(&mut map, (x, y), direction);
}
map
}
#[derive(Copy, Clone)]
enum Direction {
Top,
Right,
Bottom,
Left,
}
fn get_all_variants(map: &Map) -> Vec<(i32, i32, Direction)> {
let mut result = vec![];
for (&(x, y), room) in map {
if !room.top {
result.push((x, y, Direction::Top));
}
if !room.right {
result.push((x, y, Direction::Right));
}
if !room.bottom {
result.push((x, y, Direction::Bottom));
}
if !room.left {
result.push((x, y, Direction::Left));
}
}
result
}
fn open_room(map: &mut Map, from_pos: (i32, i32), direction: Direction) {
let (from_x, from_y) = from_pos;
let new_pos = match direction {
Direction::Top => (from_x, from_y - 1),
Direction::Right => (from_x + 1, from_y),
Direction::Bottom => (from_x, from_y + 1),
Direction::Left => (from_x - 1, from_y),
};
let mut room = map.remove(&new_pos).unwrap_or(Room::new());
let from_room = map.get_mut(&from_pos).unwrap();
match direction {
Direction::Top => {
from_room.top = true;
room.bottom = true;
}
Direction::Right => {
from_room.right = true;
room.left = true;
}
Direction::Bottom => {
from_room.bottom = true;
room.top = true;
}
Direction::Left => {
from_room.left = true;
room.right = true;
}
};
map.insert(new_pos, room);
}
fn render_room(room: &Room) -> String {
let mut sprite = String::from(
"\
\x20 \
\x20+-+ \
\x20| | \
\x20+-+ \
\x20 ",
);
if room.top {
sprite.replace_range(2..3, "|");
}
if room.right {
sprite.replace_range(14..15, "-");
}
if room.bottom {
sprite.replace_range(22..23, "|");
}
if room.left {
sprite.replace_range(10..11, "-");
}
sprite
}
fn render_map(map: Map) {
let mut screen = " ".repeat(SCREEN_WIDTH * SCREEN_HEIGHT);
for (&(x, y), room) in &map {
let room_sprite = render_room(room);
for i in 0..5 as usize {
let line = &room_sprite[(i * 5)..(i * 5 + 5)];
let pos =
((y * 5 + 35) as usize * SCREEN_WIDTH) + (i * SCREEN_WIDTH) + (x * 5 + 35) as usize;
screen.replace_range(pos..(pos + 5), line);
}
}
assert_eq!(screen.len(), SCREEN_WIDTH * SCREEN_HEIGHT);
println!();
for y in 0..SCREEN_HEIGHT {
let line = &screen[(y * SCREEN_WIDTH)..(y * SCREEN_WIDTH + SCREEN_WIDTH)];
if !line.trim().is_empty() {
println!("{}", line);
}
}
println!();
}
fn main() {
let map = generate_map();
render_map(map);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment