diff --git a/rusty-roguelike/resources/dungeonfont.png b/rusty-roguelike/resources/dungeonfont.png new file mode 100644 index 0000000..705c6a3 Binary files /dev/null and b/rusty-roguelike/resources/dungeonfont.png differ diff --git a/rusty-roguelike/src/camera.rs b/rusty-roguelike/src/camera.rs new file mode 100644 index 0000000..abf54f6 --- /dev/null +++ b/rusty-roguelike/src/camera.rs @@ -0,0 +1,26 @@ +use crate::prelude::*; + +pub struct Camera { + pub left_x: i32, + pub right_x: i32, + pub top_y: i32, + pub bottom_y: i32, +} + +impl Camera { + pub fn new(player_position: Point) -> Self { + return Self { + left_x: player_position.x - DISPLAY_WIDTH / 2, + right_x: player_position.x + DISPLAY_WIDTH / 2, + top_y: player_position.y - DISPLAY_HEIGHT / 2, + bottom_y: player_position.y + DISPLAY_HEIGHT / 2, + }; + } + + pub fn on_player_move(&mut self, player_position: Point) { + self.left_x = player_position.x - DISPLAY_WIDTH / 2; + self.right_x = player_position.x + DISPLAY_WIDTH / 2; + self.top_y = player_position.y - DISPLAY_HEIGHT / 2; + self.bottom_y = player_position.y + DISPLAY_HEIGHT / 2; + } +} diff --git a/rusty-roguelike/src/main.rs b/rusty-roguelike/src/main.rs index 7e0c3af..887ccef 100644 --- a/rusty-roguelike/src/main.rs +++ b/rusty-roguelike/src/main.rs @@ -1,42 +1,69 @@ mod map; mod player; +mod map_builder; +mod camera; mod prelude { pub use bracket_lib::prelude::*; pub use crate::map::*; pub use crate::player::*; + pub use crate::map_builder::*; + pub use crate::camera::*; pub const SCREEN_WIDTH: i32 = 80; pub const SCREEN_HEIGHT: i32 = 50; + + pub const DISPLAY_WIDTH: i32 = SCREEN_WIDTH / 2; + pub const DISPLAY_HEIGHT: i32 = SCREEN_HEIGHT / 2; } +const FONT_PATH: &str = "dungeonfont.png"; + use prelude::*; struct State { map: Map, player: Player, + camera: Camera, } impl State { fn new() -> Self { + let mut rng = RandomNumberGenerator::new(); + let mb = MapBuilder::new(&mut rng); + return Self { - map: Map::new(), - player: Player::new(Point { x: SCREEN_WIDTH / 2, y: SCREEN_HEIGHT / 2 }), + map: mb.map, + player: Player::new(mb.player_start), + camera: Camera::new(mb.player_start), }; } } impl GameState for State { fn tick(&mut self, ctx: &mut BTerm) { + ctx.set_active_console(0); ctx.cls(); - self.player.update(ctx, &self.map); - self.map.render(ctx); - self.player.render(ctx); + ctx.set_active_console(1); + ctx.cls(); + + self.player.update(ctx, &self.map, &mut self.camera); + self.map.render(ctx, &self.camera); + self.player.render(ctx, &self.camera); } } fn main() -> BError { - let context = BTermBuilder::simple80x50().with_title("Dungeon Crawler").with_fps_cap(30.0).build()?; + let context = BTermBuilder::new() + .with_title("Dungeon Crawler") + .with_fps_cap(30.0) + .with_dimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT) + .with_tile_dimensions(32, 32) + .with_resource_path("resources/") + .with_font(FONT_PATH, 32, 32) + .with_simple_console(DISPLAY_WIDTH, DISPLAY_HEIGHT, FONT_PATH) + .with_simple_console_no_bg(DISPLAY_WIDTH, DISPLAY_HEIGHT, FONT_PATH) + .build()?; return main_loop(context, State::new()); } diff --git a/rusty-roguelike/src/map.rs b/rusty-roguelike/src/map.rs index aaeaac4..3efc7cc 100644 --- a/rusty-roguelike/src/map.rs +++ b/rusty-roguelike/src/map.rs @@ -2,7 +2,7 @@ pub use crate::prelude::*; const NUM_TILES: usize = (SCREEN_WIDTH * SCREEN_HEIGHT) as usize; -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Debug)] pub enum TileType { Floor, Wall, @@ -39,16 +39,33 @@ impl Map { } } - pub fn render(&self, ctx: &mut BTerm) { - for y in 0..SCREEN_HEIGHT { - for x in 0..SCREEN_WIDTH { - let idx = map_idx(x, y); - match self.tiles[idx] { - TileType::Floor => { - ctx.set(x, y, YELLOW, BLACK, to_cp437('.')); - } - TileType::Wall => { - ctx.set(x, y, GREEN, BLACK, to_cp437('#')); + pub fn render(&self, ctx: &mut BTerm, camera: &Camera) { + ctx.set_active_console(0); + + for y in camera.top_y..camera.bottom_y { + for x in camera.left_x..camera.right_x { + if self.in_bounds(Point::new(x, y)) { + let idx = map_idx(x, y); + + match self.tiles[idx] { + TileType::Wall => { + ctx.set( + x - camera.left_x, + y - camera.top_y, + WHITE, + BLACK, + to_cp437('#'), + ); + } + TileType::Floor => { + ctx.set( + x - camera.left_x, + y - camera.top_y, + WHITE, + BLACK, + to_cp437('.'), + ); + } } } } diff --git a/rusty-roguelike/src/map_builder.rs b/rusty-roguelike/src/map_builder.rs new file mode 100644 index 0000000..5a709a9 --- /dev/null +++ b/rusty-roguelike/src/map_builder.rs @@ -0,0 +1,97 @@ +use crate::prelude::*; + +const NUM_ROOMS: usize = 20; + +pub struct MapBuilder { + pub map: Map, + pub rooms: Vec, + pub player_start: Point, +} + + +impl MapBuilder { + pub fn new(rng: &mut RandomNumberGenerator) -> Self { + let mut mb = MapBuilder { + map: Map::new(), + rooms: Vec::new(), + player_start: Point::zero(), + }; + + mb.fill(TileType::Wall); + mb.build_random_rooms(rng); + mb.build_corridors(rng); + mb.player_start = mb.rooms[0].center(); + + return mb; + } + + fn fill(&mut self, tile: TileType) { + self.map.tiles.iter_mut().for_each(|t| *t = tile); + } + + fn build_random_rooms(&mut self, rng: &mut RandomNumberGenerator) { + while self.rooms.len() < NUM_ROOMS { + let room = Rect::with_size( + rng.range(1, SCREEN_WIDTH - 10), + rng.range(1, SCREEN_HEIGHT - 10), + rng.range(2, 10), + rng.range(2, 10), + ); + + let mut overlap = false; + for r in self.rooms.iter() { + if r.intersect(&room) { + overlap = true; + break; + } + } + + if !overlap { + room.for_each(|p| { + if p.x > 0 && p.x < SCREEN_WIDTH && p.y > 0 && p.y < SCREEN_HEIGHT { + let p_idx = map_idx(p.x, p.y); + self.map.tiles[p_idx] = TileType::Floor; + } + }); + + self.rooms.push(room); + } + } + } + + fn build_corridors(&mut self, rng: &mut RandomNumberGenerator) { + let mut rooms = self.rooms.clone(); + rooms.sort_by(|a, b| a.center().x.cmp(&b.center().x)); + + for (i, room) in rooms.iter().enumerate().skip(1) { + let prev = rooms[i-1].center(); + let next = room.center(); + + if rng.range(0, 2) == 1 { + self.apply_vertical_tunnel(prev.y, next.y, prev.x); + self.apply_horizontal_tunnel(prev.x, next.x, next.y); + } else { + self.apply_horizontal_tunnel(prev.x, next.x, prev.y); + self.apply_vertical_tunnel(prev.y, next.y, next.x); + } + } + } + + fn apply_vertical_tunnel(&mut self, y1: i32, y2: i32, x: i32) { + use std::cmp::{min, max}; + for y in min(y1, y2) ..= max(y1, y2) { + if let Some(idx) = self.map.try_idx(Point::new(x, y)) { + self.map.tiles[idx] = TileType::Floor; + } + } + } + + fn apply_horizontal_tunnel(&mut self, x1: i32, x2: i32, y: i32) { + use std::cmp::{min, max}; + for x in min(x1, x2) ..= max(x1, x2) { + if let Some(idx) = self.map.try_idx(Point::new(x, y)) { + self.map.tiles[idx] = TileType::Floor; + } + } + } +} diff --git a/rusty-roguelike/src/player.rs b/rusty-roguelike/src/player.rs index 1b93c8d..a32eace 100644 --- a/rusty-roguelike/src/player.rs +++ b/rusty-roguelike/src/player.rs @@ -13,7 +13,7 @@ impl Player { }; } - pub fn update(&mut self, ctx: &mut BTerm, map: &Map) { + pub fn update(&mut self, ctx: &mut BTerm, map: &Map, camera: &mut Camera) { if let Some(key) = ctx.key { let delta = match key { VirtualKeyCode::Up => Point::new(0, -1), @@ -26,14 +26,17 @@ impl Player { let new_pos = self.position + delta; if map.can_enter_tile(new_pos) { self.position = new_pos; + camera.on_player_move(new_pos); } } } - pub fn render(&self, ctx: &mut BTerm) { + pub fn render(&self, ctx: &mut BTerm, camera: &Camera) { + ctx.set_active_console(1); + return ctx.set( - self.position.x, - self.position.y, + self.position.x - camera.left_x, + self.position.y - camera.top_y, WHITE, BLACK, to_cp437('@')