From 1e09bbf7e814d20ddbc446206d0855121ad556f2 Mon Sep 17 00:00:00 2001 From: David Blajda Date: Sat, 24 Aug 2019 15:47:36 +0000 Subject: :WIP: Web UI with stdweb --- src/bin/main.rs | 39 ++++ src/bin/web.rs | 73 +++++++ src/canvas.rs | 61 ++++++ src/lib.rs | 590 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 613 -------------------------------------------------------- 5 files changed, 763 insertions(+), 613 deletions(-) create mode 100644 src/bin/main.rs create mode 100644 src/bin/web.rs create mode 100644 src/canvas.rs create mode 100644 src/lib.rs delete mode 100644 src/main.rs (limited to 'src') diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..0806dfd --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,39 @@ +use darkcloud2_levelgen::{ Tile, Level, TileTemplate, Region, VecRegion, place_template, +connect_rooms, create_deadend, tunnel_branches, pretty_print, random_dungeon, create_templates}; + +use rand::{Rng, SeedableRng}; +use rand::rngs::SmallRng; +use rand::thread_rng; + +fn main() { + let mut thread_rng = thread_rng(); + let mut rng = SmallRng::from_seed([14;16]); + + let templates = create_templates(); + let template = &templates[0]; + + let mut level: VecRegion> = Region::new_with(15, 11); + let mut level = Level::new(Box::new(level)); + let res = place_template(&mut level, template, 0, 0); + let res = place_template(&mut level, template, 3, 7); + let res = place_template(&mut level, template, 5, 2); + let res = place_template(&mut level, template, 8, 7); + let res = connect_rooms(&mut level, 0, 1, &mut rng); + let res = connect_rooms(&mut level, 2, 1, &mut rng); + let res = connect_rooms(&mut level, 3, 2, &mut rng); + create_deadend(&mut level, &mut rng); + tunnel_branches(&mut level, &mut rng); + tunnel_branches(&mut level, &mut rng); + tunnel_branches(&mut level, &mut rng); + println!("{:?}", res); + pretty_print(&level); + + + let templates = create_templates(); + for _ in 0..10 { + let l = random_dungeon(&templates, &mut rng); + if l.is_ok() { + pretty_print(&l.unwrap()); + } + } +} diff --git a/src/bin/web.rs b/src/bin/web.rs new file mode 100644 index 0000000..166e3b2 --- /dev/null +++ b/src/bin/web.rs @@ -0,0 +1,73 @@ +#[macro_use] +extern crate stdweb; +extern crate darkcloud2_levelgen; + +use rand::{Rng, SeedableRng}; +use rand::rngs::SmallRng; + +use darkcloud2_levelgen::canvas::render_level; +use darkcloud2_levelgen::{random_dungeon, create_templates}; + +use stdweb::traits::*; +use stdweb::unstable::TryInto; +use stdweb::web::{ + document, + window, + CanvasRenderingContext2d +}; + +use stdweb::web::event::{ + MouseMoveEvent, + ResizeEvent, + ClickEvent, +}; + +use stdweb::web::html_element::CanvasElement; + +macro_rules! enclose { + ( ($( $x:ident ),*) $y:expr ) => { + { + $(let $x = $x.clone();)* + $y + } + }; +} + +static mut CANVAS: Option = None; +static mut RNG: Option = None; + +fn draw_new_level() { + unsafe { + let mut canvas = CANVAS.as_mut().unwrap(); + let mut rng = RNG.as_mut().unwrap(); + canvas.clear_rect(0.0, 0.0, 1000.0, 1000.0); + let templates = create_templates(); + let mut l = random_dungeon(&templates, &mut rng); + while l.is_err() { + l = random_dungeon(&templates, &mut rng); + + } + let level = l.unwrap(); + render_level(&mut canvas, &level); + } +} + +fn main() { + stdweb::initialize(); + + let mut rng = SmallRng::from_seed([14;16]); + unsafe { RNG = Some(rng); } + + let canvas: CanvasElement = document().query_selector( "#canvas" ).unwrap().unwrap().try_into().unwrap(); + let mut context: CanvasRenderingContext2d = canvas.get_context().unwrap(); + unsafe { CANVAS = Some(context); } + draw_new_level(); + + let new = document().query_selector("#new").unwrap().unwrap(); + + new.add_event_listener(|_: ClickEvent| { + draw_new_level() + }); + + stdweb::event_loop(); +} diff --git a/src/canvas.rs b/src/canvas.rs new file mode 100644 index 0000000..ead8636 --- /dev/null +++ b/src/canvas.rs @@ -0,0 +1,61 @@ +use stdweb::web::CanvasRenderingContext2d; +use crate::{Level, Tile}; + +use crate::Direction; + +const SCALE_X: f64 = 8.0; +const SCALE_Y: f64 = 4.0; + +pub fn render_level(canvas: &mut CanvasRenderingContext2d, level: &Level) { + let region = &level.region; + + let x_offset = 20.0; + let y_offset = 20.0; + + + for y in 0..region.height() { + for x in 0..region.width() { + let old_width = canvas.get_line_width(); + canvas.set_line_width(2.0); + canvas.set_fill_style_color("lightgrey"); + let x_f = x as f64; + let y_f = y as f64; + + let x_l = x_offset + (x_f * SCALE_X); + let x_r = x_offset + (x_f + 1.0) * SCALE_X; + + let y_t = y_offset + (y_f * SCALE_Y); + let y_b = y_offset + (y_f + 1.0) * SCALE_Y; + + + let tile = region.get(x, y).unwrap(); + if let Some(tile) = tile { + canvas.fill_rect(x_l, y_t, x_r - x_l, y_b - y_t); + if tile.connections & Direction::UP == 0 { + canvas.begin_path(); + canvas.move_to(x_l, y_t); + canvas.line_to(x_r, y_t); + canvas.stroke(); + } + if tile.connections & Direction::RIGHT == 0 { + canvas.begin_path(); + canvas.move_to(x_r, y_t); + canvas.line_to(x_r, y_b); + canvas.stroke(); + } + if tile.connections & Direction::DOWN == 0 { + canvas.begin_path(); + canvas.move_to(x_l, y_b); + canvas.line_to(x_r, y_b); + canvas.stroke(); + } + if tile.connections & Direction::LEFT == 0 { + canvas.begin_path(); + canvas.move_to(x_l, y_t); + canvas.line_to(x_l, y_b); + canvas.stroke(); + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..54d5fa3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,590 @@ +#[macro_use] +extern crate stdweb; + +use rand::{Rng, SeedableRng}; + +pub mod canvas; + +type RoomId = u32; + +#[derive(Debug)] +pub enum Error { + OutOfBounds, + MaximumIterations, + NonEmpty, + InvalidRoomId, +} + + +pub mod Direction { + pub const UP: u8 = 1 << 0; + pub const RIGHT: u8 = 1 << 1; + pub const DOWN: u8 = 1 << 2; + pub const LEFT: u8 = 1 << 3; + + #[derive(Debug)] + pub enum Error { + Underflow, + Overflow, + } + + pub fn opposite(dir: u8) -> u8 { + match dir { + UP => DOWN, + RIGHT => LEFT, + DOWN => UP, + LEFT => RIGHT, + _ => unreachable!() + } + } + + pub fn step_towards(dir: u8, point: (u32, u32)) -> Result<(u32, u32), Error> { + let p = match dir { + UP => if point.1 > 0 { (point.0, point.1 - 1) } else { return Err(Error::Underflow) }, + RIGHT => (point.0 + 1, point.1), + DOWN => (point.0, point.1 + 1), + LEFT => if point.0 > 0 { (point.0 - 1, point.1) } else { return Err(Error::Underflow) }, + _ => unreachable!() + + }; + Ok(p) + } + +} + +pub struct RoomDescription { + width: u32, + height: u32, + data: Vec, +} + +pub trait Region { + fn width(&self) -> u32; + fn height(&self) -> u32; + fn set(&mut self, x: u32, y: u32, value: T) -> Result<(), Error>; + fn get(&self, x: u32, y: u32) -> Result<&T, Error>; + fn get_mut(&mut self, x: u32, y: u32) -> Result<&mut T, Error>; +} + +pub struct VecRegion { + inner: Vec, + width: u32, + height: u32, +} + +impl Region for VecRegion { + fn width(&self) -> u32 { + self.width + } + + fn height(&self) -> u32 { + self.height + } + + fn set(&mut self, x: u32, y: u32, value: T) -> Result<(), Error> { + if x >= self.width || y >= self.height { + return Err(Error::OutOfBounds); + } + + self.inner[(y * self.width + x) as usize] = value; + Ok(()) + } + + fn get(&self, x: u32, y:u32) -> Result<&T, Error> { + if x >= self.width || y >= self.height { + return Err(Error::OutOfBounds); + } + + Ok(&self.inner[(y * self.width + x) as usize]) + } + + fn get_mut(&mut self, x: u32, y: u32) -> Result<&mut T, Error> { + if x >= self.width || y >= self.height { + return Err(Error::OutOfBounds); + } + + Ok(&mut self.inner[(y * self.width + x) as usize]) + } +} + +impl Region { + pub fn new_with(width: u32, height: u32) -> VecRegion { + let size = (width * height) as usize; + let mut inner = Vec::with_capacity(size); + inner.resize_with(size, T::default); + VecRegion { + inner, + width, + height, + } + } +} + + +const TILE_IS_PATH: u8 = 0x4; + +#[derive(Clone, Debug)] +pub struct Tile { + owner: Owner, + flags: u8, + connections: u8, +} + +#[derive(Clone)] +pub struct TileTemplate { + pub connections: u8 +} + +#[derive(Eq, PartialEq, Clone, Debug, Copy)] +enum Owner { + Room(RoomId), + Tunnel(u32), +} + +impl TileTemplate { + fn into_tile(&self, room: RoomId) -> Tile { + Tile { + connections: self.connections, + owner: Owner::Room(room), + flags: 0, + } + } +} + +//type Level = Region>; +type RoomTemplate = Region>; + +struct Room { + x: u32, + y: u32, + width: u32, + height: u32, +} + +pub struct Level { + region: Box>>, + rooms: Vec, + tunnels: u32, +} + +impl Level { + pub fn new(region: Box>>) -> Level { + Level { + region: region, + rooms: Vec::new(), + tunnels: 0 + } + } +} + +pub fn place_template(level: &mut Level, template: &RoomTemplate, x: u32, y: u32) -> Result { + let region = &mut level.region; + let width = region.width(); + let height = region.height(); + + if x >= width || x + template.width() > width || y >= height || y + template.height() > height { + return Err(Error::OutOfBounds); + } + + for row in 0..template.height() { + for column in 0..template.width() { + if region.get(column + x, row + y)?.is_some() { + return Err(Error::NonEmpty); + } + } + } + + let room = Room { + x, + y, + width: template.width(), + height: template.height(), + }; + let room_id = level.rooms.len() as u32; + level.rooms.push(room); + + for row in 0..template.height() { + for column in 0..template.width() { + region.set(column + x, row + y, + template.get(column, row)?.clone().map(|t| t.into_tile(room_id)))?; + } + } + + Ok(room_id) +} + +fn tunnel(level: &mut Level, from: (u32, u32), to: (u32, u32), owner: Owner, rng: &mut R) -> Result<(), Error> { + + let mut from = from; + let mut connected = false; + + while !connected { + + let diff: (i64, i64) = (from.0 as i64 - to.0 as i64, from.1 as i64 - to.1 as i64); + let abs_diff = (i64::abs(diff.0), i64::abs(diff.1)); + let mut direction = if abs_diff.1 < abs_diff.0 { + if diff.0 < 0 { Direction::RIGHT } else { Direction::LEFT } + } else { + if diff.1 < 0 { Direction::DOWN } else { Direction::UP } + }; + + let mut steps = match direction { + Direction::RIGHT | Direction::LEFT => rng.gen_range(0, abs_diff.0) + 1, + Direction::UP | Direction::DOWN => rng.gen_range(0, abs_diff.1) + 1, + _ => 1 + }; + + while steps > 0 { + let connection = direction; + let opposite = Direction::opposite(connection); + let tile = level.region.get_mut(from.0, from.1)?; + if let Some(tile) = tile { + tile.connections = tile.connections | connection; + } else { + level.region.set(from.0, from.1, Some(Tile { owner: owner, flags: TILE_IS_PATH, connections: direction})); + } + + from = Direction::step_towards(direction, from).unwrap(); + + let tile = level.region.get_mut(from.0, from.1)?; + match tile { + Some(tile) => { + if tile.owner != owner { + tile.connections = tile.connections | opposite; + connected = true; + break; + } + }, + None => { + level.region.set(from.0, from.1, Some(Tile { owner: owner, flags: TILE_IS_PATH, connections: opposite})); + } + } + steps = steps - 1; + } + } + Ok(()) +} + +pub fn connect_rooms(level: &mut Level, from_id: RoomId, to_id: RoomId, rng: &mut R) -> Result<(), Error> { + + let mut candidates = Vec::new(); + let from = level.rooms.get(from_id as usize).ok_or(Error::InvalidRoomId)?; + let from_center = (from.x + (from.width / 2), from.y + (from.height / 2)); + + let to = level.rooms.get(to_id as usize).ok_or(Error::InvalidRoomId)?; + let to_center = (to.x + (to.width / 2), to.y + (to.height / 2)); + + let diff: (i32, i32) = (from_center.0 as i32 - to_center.0 as i32, from_center.1 as i32 - to_center.1 as i32); + let abs_diff = (i32::abs(diff.0), i32::abs(diff.1)); + + let direction = if abs_diff.1 < abs_diff.0 { + if -1 < diff.0 { Direction::LEFT } else { Direction::RIGHT } + } else { + if diff.1 < 0 { Direction::DOWN } else { Direction::UP } + }; + + /*Find a suitable starting location*/ + match direction { + Direction::UP => { + while candidates.len() < 1 { + let mut y = from.y; + for x in from.x..(from.x + from.width) { + if let Some(tile) = level.region.get(x, y)? { + candidates.push((x, y)); + } + } + y = y + 1; + } + }, + Direction::RIGHT => { + while candidates.len() < 1 { + let mut x = from.x + from.width - 1; + for y in from.y..(from.y + from.height) { + if let Some(tile) = level.region.get(x, y)? { + candidates.push((x, y)); + } + } + x = x - 1; + } + }, + Direction::DOWN => { + while candidates.len() < 1 { + let mut y = from.y + from.height - 1; + for x in from.x..(from.x + from.width) { + if let Some(tile) = level.region.get(x, y)? { + candidates.push((x, y)); + } + } + y = y - 1; + } + }, + Direction::LEFT => { + while candidates.len() < 1 { + let mut x = from.x; + for y in from.y..(from.y + from.height) { + if let Some(tile) = level.region.get(x, y)? { + candidates.push((x, y)); + } + } + x = x + 1; + } + }, + _ => unreachable!() + } + + let pos = candidates[rng.gen_range(0, candidates.len())]; + tunnel(level, pos, to_center, Owner::Room(from_id), rng) +} + +pub fn create_deadend(level: &mut Level, rng: &mut R) -> Result<(), Error> { + let mut attempts = 0; + loop { + let x = rng.gen_range(0, level.region.width() - 3); + let y = rng.gen_range(0, level.region.height() - 3); + let mut empty = true; + + for i in 0..3 { + for j in 0..3 { + let tile = level.region.get(x + j, y + i)?; + if tile.is_some() { + empty = false; + break; + } + } + } + attempts = attempts + 1; + if attempts > 128 {break;} + + if empty { + let owner = Owner::Tunnel(level.tunnels); + let rnd_index = rng.gen_range(0, level.rooms.len()); + let room = &mut level.rooms[rnd_index]; + let room_center: (u32, u32) = ((room.x + (room.width/2)), (room.y + (room.height/2))); + let res = tunnel(level, (x,y), room_center, owner , rng); + level.tunnels += 1; + return res; + } + + } + Ok(()) +} + +fn empty_neighbours(level: &Level, x: u32, y:u32, options: &mut [u8; 4], neighbours: &mut usize) { + let region = &level.region; + if y > 0 && region.get(x, y - 1).unwrap().is_none() { + options[*neighbours] = Direction::UP; + *neighbours += 1; + } + + if x + 1 < region.width() && region.get(x + 1, y).unwrap().is_none() { + options[*neighbours] = Direction::RIGHT; + *neighbours += 1; + } + + if y + 1 < region.height() && region.get(x, y + 1).unwrap().is_none() { + options[*neighbours] = Direction::DOWN; + *neighbours += 1; + } + + if x > 0 && region.get(x - 1, y).unwrap().is_none() { + options[*neighbours] = Direction::LEFT; + *neighbours += 1; + } +} + +pub fn tunnel_branches(level: &mut Level, rng: &mut R) -> Result<(), Error> { + let mut iterations = 0; + const max_iterations: u32 = 128; + + let mut neighbours = 0; + let mut options = [0; 4]; + let mut x = 0; + let mut y = 0; + + let owner = Owner::Tunnel(level.tunnels); + level.tunnels += 1; + + while neighbours <= 0 && iterations < max_iterations { + neighbours = 0; + x = rng.gen_range(0, level.region.width() - 4) + 2; + y = rng.gen_range(0, level.region.height() - 4) + 2; + + if let Some(tile) = level.region.get(x, y)? { + if tile.flags & TILE_IS_PATH != 0 { + empty_neighbours(level, x, y, &mut options, &mut neighbours); + } + } + iterations = iterations + 1; + } + + if neighbours <= 0 { + return Err(Error::NonEmpty); + } + + let direction = options[rng.gen_range(0, neighbours)]; + if let Some(tile) = level.region.get_mut(x, y)? { + tile.connections |= direction; + }; + + let mut pos = (x, y); + pos = Direction::step_towards(direction, pos).unwrap(); + level.region.set(pos.0, pos.1, Some(Tile { owner: owner, flags: TILE_IS_PATH, connections: Direction::opposite(direction)})); + let mut iterations = rng.gen_range(0, 2); + + while iterations >= 0 { + neighbours = 0; + empty_neighbours(level, pos.0, pos.1, &mut options, &mut neighbours); + + if neighbours <= 0 { + return Ok(()); + } + + let direction = options[rng.gen_range(0, neighbours)]; + let mut length = rng.gen_range(0, 2) + 1; + while length > 0 { + let old_pos = pos.clone(); + let m_pos = Direction::step_towards(direction, pos); + if let Err(_) = m_pos { + break; + } else { + pos = m_pos.unwrap(); + } + + match level.region.get(pos.0, pos.1) { + Ok(tile) => { + if tile.is_none() { + level.region.set(pos.0, pos.1, Some(Tile { owner: owner, flags: TILE_IS_PATH, connections: Direction::opposite(direction)})); + if let Some(tile) = level.region.get_mut(old_pos.0, old_pos.1)? { + tile.connections |= direction; + } + } + }, + Err(_) => { + pos = old_pos; + length = 0; + } + } + length += -1; + } + iterations += -1; + } + + Ok(()) +} + +pub fn pretty_print(level: &Level) { + + let region = &level.region; + + for x in 0..region.width() { print!(" _"); } + println!(""); + + for y in 0..region.height() { + for x in 0..region.width() { + if x == 0 { print!("|");} + let tile = region.get(x, y).unwrap(); + match tile { + Some(tile) => { + if tile.connections & Direction::DOWN == 0 { print!("_") } else { print!(" ") } + if tile.connections & Direction::RIGHT == 0 { print!("|") } else { print!(" ") } + }, + None => print!("_|"), + } + } + println!(""); + } +} + +pub fn create_templates() -> Vec>> { + + let mut template:VecRegion> = Region::new_with(3, 3); + use Direction::{UP, DOWN, LEFT, RIGHT}; + template.set(0, 0, Some(TileTemplate { connections: RIGHT | DOWN})); + template.set(1, 0, Some(TileTemplate { connections: RIGHT | DOWN | LEFT})); + template.set(2, 0, Some(TileTemplate { connections: DOWN | LEFT})); + + template.set(0, 1, Some(TileTemplate { connections: UP | RIGHT | DOWN})); + template.set(1, 1, Some(TileTemplate { connections: UP | RIGHT | DOWN | LEFT})); + template.set(2, 1, Some(TileTemplate { connections: UP | DOWN | LEFT})); + + template.set(0, 2, Some(TileTemplate { connections: UP | RIGHT})); + template.set(1, 2, Some(TileTemplate { connections: UP | RIGHT | LEFT})); + template.set(2, 2, Some(TileTemplate { connections: UP | LEFT})); + + vec!(template) +} + +pub fn random_dungeon(templates: &Vec>>, mut rng: &mut R) -> Result { + let mut region: VecRegion> = Region::new_with(15, 15); + let mut level = Level { + rooms: Vec::new(), + region: Box::new(region), + tunnels: 0, + }; + + let mut attempts = 20; + let first = loop { + let template = &templates[0]; + let x = rng.gen_range(0, level.region.width() - 2) + 1; + let y = rng.gen_range(0, level.region.height() - 2) + 1; + let res = place_template(&mut level, template, x, y); + if attempts <= 0 && res.is_err() { panic!() } + if res.is_ok() { break res.unwrap() } + attempts += -1; + }; + + let mut attempts = 20; + let second = loop { + let template = &templates[0]; + let x = rng.gen_range(0, level.region.width() - 2) + 1; + let y = rng.gen_range(0, level.region.height() - 2) + 1; + let res = place_template(&mut level, template, x, y); + if attempts <= 0 && res.is_err() { return Err(Error::MaximumIterations); } + if res.is_ok() { break res.unwrap() } + attempts += -1; + }; + + connect_rooms(&mut level, first, second, &mut rng)?; + let mut additional_rooms = rng.gen_range(0, 3) + 4; + + while additional_rooms >= 0 { + let mut attempts = 20; + let room_id = loop { + let template = &templates[0]; + let x = rng.gen_range(0, level.region.width() - 2) + 1; + let y = rng.gen_range(0, level.region.height() - 2) + 1; + let res = place_template(&mut level, template, x, y); + if attempts <= 0 && res.is_err() { return Err(Error::MaximumIterations); } + if res.is_ok() { break res.unwrap() } + attempts += -1; + }; + let to_room = (level.rooms.len() - 1) as u32; + connect_rooms(&mut level, room_id, rng.gen_range(0, to_room), &mut rng)?; + additional_rooms += -1; + } + + let mut connections = rng.gen_range(0, 3) + 1; + while connections > 0 { + let upper = level.rooms.len(); + let mut from = 0; + let mut to = 0; + while to == from { + from = rng.gen_range(0, upper); + to = rng.gen_range(0, upper); + } + connect_rooms(&mut level, from as u32, to as u32, &mut rng)?; + connections += -1; + } + + let mut deadends = rng.gen_range(0, 3) + 1; + while deadends > 0 { + create_deadend(&mut level, &mut rng)?; + deadends += -1; + } + + tunnel_branches(&mut level, &mut rng)?; + tunnel_branches(&mut level, &mut rng)?; + tunnel_branches(&mut level, &mut rng)?; + + Ok(level) +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index fe32d14..0000000 --- a/src/main.rs +++ /dev/null @@ -1,613 +0,0 @@ -use rand::{Rng, SeedableRng}; -use rand::thread_rng; -use rand::rngs::SmallRng; - - -type RoomId = u32; - -enum LevelBuilderError { - OutOfBounds, - InvalidRoomId, -} - -#[derive(Debug)] -enum Error { - OutOfBounds, - MaximumIterations, - NonEmpty, - InvalidRoomId, -} - - -mod Direction { - pub const UP: u8 = 1 << 0; - pub const RIGHT: u8 = 1 << 1; - pub const DOWN: u8 = 1 << 2; - pub const LEFT: u8 = 1 << 3; - - #[derive(Debug)] - pub enum Error { - Underflow, - Overflow, - } - - pub fn opposite(dir: u8) -> u8 { - match dir { - UP => DOWN, - RIGHT => LEFT, - DOWN => UP, - LEFT => RIGHT, - _ => unreachable!() - } - } - - pub fn step_towards(dir: u8, point: (u32, u32)) -> Result<(u32, u32), Error> { - let p = match dir { - UP => if point.1 > 0 { (point.0, point.1 - 1) } else { return Err(Error::Underflow) }, - RIGHT => (point.0 + 1, point.1), - DOWN => (point.0, point.1 + 1), - LEFT => if point.0 > 0 { (point.0 - 1, point.1) } else { return Err(Error::Underflow) }, - _ => unreachable!() - - }; - Ok(p) - } - -} - -struct RoomDescription { - width: u32, - height: u32, - data: Vec, -} - -struct LevelBuilder { } - -trait Region { - fn width(&self) -> u32; - fn height(&self) -> u32; - fn set(&mut self, x: u32, y: u32, value: T) -> Result<(), Error>; - fn get(&self, x: u32, y: u32) -> Result<&T, Error>; - fn get_mut(&mut self, x: u32, y: u32) -> Result<&mut T, Error>; -} - -struct VecRegion { - inner: Vec, - width: u32, - height: u32, -} - -impl Region for VecRegion { - fn width(&self) -> u32 { - self.width - } - - fn height(&self) -> u32 { - self.height - } - - fn set(&mut self, x: u32, y: u32, value: T) -> Result<(), Error> { - if x >= self.width || y >= self.height { - return Err(Error::OutOfBounds); - } - - self.inner[(y * self.width + x) as usize] = value; - Ok(()) - } - - fn get(&self, x: u32, y:u32) -> Result<&T, Error> { - if x >= self.width || y >= self.height { - return Err(Error::OutOfBounds); - } - - Ok(&self.inner[(y * self.width + x) as usize]) - } - - fn get_mut(&mut self, x: u32, y: u32) -> Result<&mut T, Error> { - if x >= self.width || y >= self.height { - return Err(Error::OutOfBounds); - } - - Ok(&mut self.inner[(y * self.width + x) as usize]) - } -} - -impl Region { - fn new_with(width: u32, height: u32) -> VecRegion { - let size = (width * height) as usize; - let mut inner = Vec::with_capacity(size); - inner.resize_with(size, T::default); - VecRegion { - inner, - width, - height, - } - } -} - - -const TILE_IS_PATH: u8 = 0x4; - -#[derive(Clone, Debug)] -struct Tile { - owner: Owner, - flags: u8, - connections: u8, -} - -#[derive(Clone)] -struct TileTemplate { - connections: u8 -} - -#[derive(Eq, PartialEq, Clone, Debug, Copy)] -enum Owner { - Room(RoomId), - Tunnel(u32), -} - -impl TileTemplate { - fn into_tile(&self, room: RoomId) -> Tile { - Tile { - connections: self.connections, - owner: Owner::Room(room), - flags: 0, - } - } -} - -//type Level = Region>; -type RoomTemplate = Region>; - -struct Room { - x: u32, - y: u32, - width: u32, - height: u32, -} - -struct Level { - region: Box>>, - rooms: Vec, - tunnels: u32, -} - -fn place_template(level: &mut Level, template: &RoomTemplate, x: u32, y: u32) -> Result { - let region = &mut level.region; - let width = region.width(); - let height = region.height(); - - if x >= width || x + template.width() > width || y >= height || y + template.height() > height { - return Err(Error::OutOfBounds); - } - - for row in 0..template.height() { - for column in 0..template.width() { - if region.get(column + x, row + y)?.is_some() { - return Err(Error::NonEmpty); - } - } - } - - let room = Room { - x, - y, - width: template.width(), - height: template.height(), - }; - let room_id = level.rooms.len() as u32; - level.rooms.push(room); - - for row in 0..template.height() { - for column in 0..template.width() { - region.set(column + x, row + y, - template.get(column, row)?.clone().map(|t| t.into_tile(room_id)))?; - } - } - - Ok(room_id) -} - -fn tunnel(level: &mut Level, from: (u32, u32), to: (u32, u32), owner: Owner, rng: &mut R) -> Result<(), Error> { - - let mut from = from; - let mut connected = false; - - while !connected { - - let diff: (i64, i64) = (from.0 as i64 - to.0 as i64, from.1 as i64 - to.1 as i64); - let abs_diff = (i64::abs(diff.0), i64::abs(diff.1)); - let mut direction = if abs_diff.1 < abs_diff.0 { - if diff.0 < 0 { Direction::RIGHT } else { Direction::LEFT } - } else { - if diff.1 < 0 { Direction::DOWN } else { Direction::UP } - }; - - let mut steps = match direction { - Direction::RIGHT | Direction::LEFT => rng.gen_range(0, abs_diff.0) + 1, - Direction::UP | Direction::DOWN => rng.gen_range(0, abs_diff.1) + 1, - _ => 1 - }; - - while steps > 0 { - let connection = direction; - let opposite = Direction::opposite(connection); - let tile = level.region.get_mut(from.0, from.1)?; - if let Some(tile) = tile { - tile.connections = tile.connections | connection; - } - - from = Direction::step_towards(direction, from).unwrap(); - - let tile = level.region.get_mut(from.0, from.1)?; - match tile { - Some(tile) => { - if tile.owner != owner { - tile.connections = tile.connections | opposite; - connected = true; - break; - } - }, - None => { - level.region.set(from.0, from.1, Some(Tile { owner: owner, flags: TILE_IS_PATH, connections: opposite})); - } - } - steps = steps - 1; - } - } - Ok(()) -} - -fn connect_rooms(level: &mut Level, from_id: RoomId, to_id: RoomId, rng: &mut R) -> Result<(), Error> { - - let mut candidates = Vec::new(); - let from = level.rooms.get(from_id as usize).ok_or(Error::InvalidRoomId)?; - let from_center = (from.x + (from.width / 2), from.y + (from.height / 2)); - - let to = level.rooms.get(to_id as usize).ok_or(Error::InvalidRoomId)?; - let to_center = (to.x + (to.width / 2), to.y + (to.height / 2)); - - let diff: (i32, i32) = (from_center.0 as i32 - to_center.0 as i32, from_center.1 as i32 - to_center.1 as i32); - let abs_diff = (i32::abs(diff.0), i32::abs(diff.1)); - - let direction = if abs_diff.1 < abs_diff.0 { - if -1 < diff.0 { Direction::LEFT } else { Direction::RIGHT } - } else { - if diff.1 < 0 { Direction::DOWN } else { Direction::UP } - }; - - /*Find a suitable starting location*/ - match direction { - Direction::UP => { - while candidates.len() < 1 { - let mut y = from.y; - for x in from.x..(from.x + from.width) { - if let Some(tile) = level.region.get(x, y)? { - candidates.push((x, y)); - } - } - y = y + 1; - } - }, - Direction::RIGHT => { - while candidates.len() < 1 { - let mut x = from.x + from.width - 1; - for y in from.y..(from.y + from.height) { - if let Some(tile) = level.region.get(x, y)? { - candidates.push((x, y)); - } - } - x = x - 1; - } - }, - Direction::DOWN => { - while candidates.len() < 1 { - let mut y = from.y + from.height - 1; - for x in from.x..(from.x + from.width) { - if let Some(tile) = level.region.get(x, y)? { - candidates.push((x, y)); - } - } - y = y - 1; - } - }, - Direction::LEFT => { - while candidates.len() < 1 { - let mut x = from.x; - for y in from.y..(from.y + from.height) { - if let Some(tile) = level.region.get(x, y)? { - candidates.push((x, y)); - } - } - x = x + 1; - } - }, - _ => unreachable!() - } - - let pos = candidates[rng.gen_range(0, candidates.len())]; - tunnel(level, pos, to_center, Owner::Room(from_id), rng) -} - -fn create_deadend(level: &mut Level, rng: &mut R) -> Result<(), Error> { - let mut attempts = 0; - loop { - let x = rng.gen_range(0, level.region.width() - 3); - let y = rng.gen_range(0, level.region.height() - 3); - let mut empty = true; - - for i in 0..3 { - for j in 0..3 { - let tile = level.region.get(x + j, y + i)?; - if tile.is_some() { - empty = false; - break; - } - } - } - attempts = attempts + 1; - if attempts > 128 {break;} - - if empty { - let rnd_index = rng.gen_range(0, level.rooms.len()); - let room = &mut level.rooms[rnd_index]; - let room_center: (u32, u32) = ((room.x + (room.width/2)), (room.y + (room.height/2))); - let res = tunnel(level, (x,y), room_center, Owner::Tunnel(level.tunnels), rng); - level.tunnels += 1; - return res; - } - - } - Ok(()) -} - -fn empty_neighbours(level: &Level, x: u32, y:u32, options: &mut [u8; 4], neighbours: &mut usize) { - let region = &level.region; - if y > 0 && region.get(x, y - 1).unwrap().is_none() { - options[*neighbours] = Direction::UP; - *neighbours += 1; - } - - if x + 1 < region.width() && region.get(x + 1, y).unwrap().is_none() { - options[*neighbours] = Direction::RIGHT; - *neighbours += 1; - } - - if y + 1 < region.height() && region.get(x, y + 1).unwrap().is_none() { - options[*neighbours] = Direction::DOWN; - *neighbours += 1; - } - - if x > 0 && region.get(x - 1, y).unwrap().is_none() { - options[*neighbours] = Direction::LEFT; - *neighbours += 1; - } -} - -fn random_walk(level: &mut Level, rng: &mut R) -> Result<(), Error> { - let mut iterations = 0; - const max_iterations: u32 = 128; - - let mut neighbours = 0; - let mut options = [0; 4]; - let mut x = 0; - let mut y = 0; - - let owner = Owner::Tunnel(level.tunnels); - level.tunnels += 1; - - while neighbours <= 0 && iterations < max_iterations { - neighbours = 0; - x = rng.gen_range(0, level.region.width() - 4) + 2; - y = rng.gen_range(0, level.region.height() - 4) + 2; - - if let Some(tile) = level.region.get(x, y)? { - if tile.flags & TILE_IS_PATH != 0 { - empty_neighbours(level, x, y, &mut options, &mut neighbours); - } - } - iterations = iterations + 1; - } - - if neighbours <= 0 { - return Err(Error::NonEmpty); - } - - let direction = options[rng.gen_range(0, neighbours)]; - if let Some(tile) = level.region.get_mut(x, y)? { - tile.connections |= direction; - }; - - let mut pos = (x, y); - pos = Direction::step_towards(direction, pos).unwrap(); - level.region.set(pos.0, pos.1, Some(Tile { owner: owner, flags: TILE_IS_PATH, connections: Direction::opposite(direction)})); - let mut iterations = rng.gen_range(0, 2); - - while iterations >= 0 { - neighbours = 0; - empty_neighbours(level, pos.0, pos.1, &mut options, &mut neighbours); - - if neighbours <= 0 { - return Ok(()); - } - - let direction = options[rng.gen_range(0, neighbours)]; - let mut length = rng.gen_range(0, 2) + 1; - while length > 0 { - let old_pos = pos.clone(); - let m_pos = Direction::step_towards(direction, pos); - if let Err(_) = m_pos { - break; - } else { - pos = m_pos.unwrap(); - } - - match level.region.get(pos.0, pos.1) { - Ok(tile) => { - if tile.is_none() { - level.region.set(pos.0, pos.1, Some(Tile { owner: owner, flags: TILE_IS_PATH, connections: Direction::opposite(direction)})); - if let Some(tile) = level.region.get_mut(old_pos.0, old_pos.1)? { - tile.connections |= direction; - } - } - }, - Err(_) => { - pos = old_pos; - length = 0; - } - } - length += -1; - } - iterations += -1; - } - - Ok(()) -} - -fn pretty_print(level: &Level) { - - let region = &level.region; - - for x in 0..region.width() { print!(" _"); } - println!(""); - - for y in 0..region.height() { - for x in 0..region.width() { - if x == 0 { print!("|");} - let tile = region.get(x, y).unwrap(); - match tile { - Some(tile) => { - if tile.connections & Direction::DOWN == 0 { print!("_") } else { print!(" ") } - if tile.connections & Direction::RIGHT == 0 { print!("|") } else { print!(" ") } - }, - None => print!("_|"), - } - } - println!(""); - } -} - -fn random_dungeon(templates: Vec>>, mut rng: &mut R) -> Result { - let mut region: VecRegion> = Region::new_with(15, 15); - let mut level = Level { - rooms: Vec::new(), - region: Box::new(region), - tunnels: 0, - }; - - let mut attempts = 20; - let first = loop { - let template = &templates[0]; - let x = rng.gen_range(0, level.region.width() - 2) + 1; - let y = rng.gen_range(0, level.region.height() - 2) + 1; - let res = place_template(&mut level, template, x, y); - if attempts <= 0 && res.is_err() { panic!() } - if res.is_ok() { break res.unwrap() } - attempts += -1; - }; - - let mut attempts = 20; - let second = loop { - let template = &templates[0]; - let x = rng.gen_range(0, level.region.width() - 2) + 1; - let y = rng.gen_range(0, level.region.height() - 2) + 1; - let res = place_template(&mut level, template, x, y); - if attempts <= 0 && res.is_err() { panic!() } - if res.is_ok() { break res.unwrap() } - attempts += -1; - }; - - connect_rooms(&mut level, first, second, &mut rng)?; - let mut additional_rooms = rng.gen_range(0, 3) + 4; - - while additional_rooms >= 0 { - let mut attempts = 20; - let room_id = loop { - let template = &templates[0]; - let x = rng.gen_range(0, level.region.width() - 2) + 1; - let y = rng.gen_range(0, level.region.height() - 2) + 1; - let res = place_template(&mut level, template, x, y); - if attempts <= 0 && res.is_err() { panic!() } - if res.is_ok() { break res.unwrap() } - attempts += -1; - }; - let to_room = (level.rooms.len() - 1) as u32; - connect_rooms(&mut level, room_id, rng.gen_range(0, to_room), &mut rng)?; - additional_rooms += -1; - } - - let mut connections = rng.gen_range(0, 3) + 1; - while connections > 0 { - let upper = level.rooms.len(); - let mut from = 0; - let mut to = 0; - while to == from { - from = rng.gen_range(0, upper); - to = rng.gen_range(0, upper); - } - connect_rooms(&mut level, from as u32, to as u32, &mut rng)?; - connections += -1; - } - - let mut deadends = rng.gen_range(0, 3) + 1; - while deadends > 0 { - create_deadend(&mut level, &mut rng)?; - deadends += -1; - } - - random_walk(&mut level, &mut rng)?; - random_walk(&mut level, &mut rng)?; - random_walk(&mut level, &mut rng)?; - - Ok(level) -} - - -fn main() { - let mut thread_rng = thread_rng(); - //18 - let mut rng = SmallRng::from_seed([14;16]); - - - let mut template:VecRegion> = Region::new_with(3, 3); - use Direction::{UP, DOWN, LEFT, RIGHT}; - template.set(0, 0, Some(TileTemplate { connections: RIGHT | DOWN})); - template.set(1, 0, Some(TileTemplate { connections: RIGHT | DOWN | LEFT})); - template.set(2, 0, Some(TileTemplate { connections: DOWN | LEFT})); - - template.set(0, 1, Some(TileTemplate { connections: UP | RIGHT | DOWN})); - template.set(1, 1, Some(TileTemplate { connections: UP | RIGHT | DOWN | LEFT})); - template.set(2, 1, Some(TileTemplate { connections: UP | DOWN | LEFT})); - - template.set(0, 2, Some(TileTemplate { connections: UP | RIGHT})); - template.set(1, 2, Some(TileTemplate { connections: UP | RIGHT | LEFT})); - template.set(2, 2, Some(TileTemplate { connections: UP | LEFT})); - - - let mut level: VecRegion> = Region::new_with(15, 11); - let mut level = Level { - rooms: Vec::new(), - region: Box::new(level), - tunnels: 0, - }; - - let res = place_template(&mut level, &mut template, 0, 0); - let res = place_template(&mut level, &mut template, 3, 7); - let res = place_template(&mut level, &mut template, 5, 2); - let res = place_template(&mut level, &mut template, 8, 7); - let res = connect_rooms(&mut level, 0, 1, &mut rng); - let res = connect_rooms(&mut level, 2, 1, &mut rng); - let res = connect_rooms(&mut level, 3, 2, &mut rng); - //let res = connect_rooms(&mut level, 1, 3, &mut rng); - create_deadend(&mut level, &mut rng); - random_walk(&mut level, &mut rng); - random_walk(&mut level, &mut rng); - random_walk(&mut level, &mut rng); - //create_deadend(&mut level, &mut rng); - println!("{:?}", res); - pretty_print(&level); - - let l = random_dungeon(vec!(template), &mut rng).unwrap(); - pretty_print(&l); - //println!("{:?}", level); -} -- cgit v1.2.3