This commit is contained in:
unintendedfraud
2024-01-27 22:47:22 +01:00
parent 227c0021fe
commit 6e7ab861c9
5 changed files with 95 additions and 54 deletions
@@ -1,5 +1,5 @@
[package] [package]
name = "dungeoncrawler" name = "rusty-roguelike"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
+36
View File
@@ -0,0 +1,36 @@
# Rusty Roguelike
Roguelike following the book "Hands-on Rust".
## Short description
A dungeon crawler with procedurally generated levels, monsters of increasing difficulty and turn-based movement.
## Story
The hero's hometown is suffering from a plague of monsters. Welling up from the deep, they seem unstoppable. Legend
tells of the Amulet of Yala that can be used to stem the tide. After a long night at the tavern, the hero promises to
save the day - and set forth into the dungeon.
## Basic game loops
1. Enter dungeon level
2. Explore, revealing the map
3. Encounter ennemies from whom the players decide to fight or flee from
4. Find power-ups and use them to strenghten the player
5. Locate the exit to the level, back to 1
## MVP
1. Create a basic dungeon map
2. Place the player and let them walk around
3. Spawn monsters, draw them, and let the player kill them by walking into them
4. Add health and a combat system that uses it
5. Add healing potions
6. Display a "Game Over" screen when the player dies
7. Add the Amulet of Yala to the level and let the player wins by reaching it
## Stretch goals
1. Add field of view
2. Add more interesting dungeon designs
3. Add some dungeon themes
4. Add multiple layers to the dungeon, with the amulet on the last one
5. Add varied weapons to the game
6. Move to a data driven design for spawning ennemies
7. Consider some visuals effects to make combats more visceral
8. Consider keeping scores
@@ -3,10 +3,11 @@ mod player;
mod prelude { mod prelude {
pub use bracket_lib::prelude::*; pub use bracket_lib::prelude::*;
pub const SCREEN_WIDTH: i32 = 80;
pub const SCREEN_HEIGHT: i32 = 50;
pub use crate::map::*; pub use crate::map::*;
pub use crate::player::*; pub use crate::player::*;
pub const SCREEN_WIDTH: i32 = 80;
pub const SCREEN_HEIGHT: i32 = 50;
} }
use prelude::*; use prelude::*;
@@ -18,10 +19,10 @@ struct State {
impl State { impl State {
fn new() -> Self { fn new() -> Self {
Self { return Self {
map: Map::new(), map: Map::new(),
player: Player::new(Point { x: 0, y: 0 }), player: Player::new(Point { x: SCREEN_WIDTH / 2, y: SCREEN_HEIGHT / 2 }),
} };
} }
} }
@@ -35,10 +36,7 @@ impl GameState for State {
} }
fn main() -> BError { fn main() -> BError {
let ctx = BTermBuilder::simple80x50() let context = BTermBuilder::simple80x50().with_title("Dungeon Crawler").with_fps_cap(30.0).build()?;
.with_title("Dungeon Crawler")
.with_fps_cap(30.0)
.build()?;
main_loop(ctx, State::new()) return main_loop(context, State::new());
} }
@@ -1,53 +1,57 @@
use crate::prelude::*; pub use crate::prelude::*;
const NUM_TILES: usize = (SCREEN_WIDTH * SCREEN_HEIGHT) as usize; const NUM_TILES: usize = (SCREEN_WIDTH * SCREEN_HEIGHT) as usize;
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub enum TileType { pub enum TileType {
Wall,
Floor, Floor,
Wall,
} }
pub struct Map { pub struct Map {
pub tiles: Vec<TileType>, pub tiles: Vec<TileType>,
} }
pub fn map_idx(x: i32, y: i32) -> usize {
return ((y * SCREEN_WIDTH) + x) as usize;
}
impl Map { impl Map {
pub fn new() -> Self { pub fn new() -> Self {
Self { return Self {
tiles: vec![TileType::Floor; NUM_TILES], tiles: vec![TileType::Floor; NUM_TILES],
} }
} }
pub fn in_bounds(&self, point: Point) -> bool {
return point.x >= 0 && point.x < SCREEN_WIDTH && point.y >= 0 && point.y < SCREEN_HEIGHT;
}
pub fn can_enter_tile(&self, point: Point) -> bool {
return self.in_bounds(point) && self.tiles[map_idx(point.x, point.y)] == TileType::Floor;
}
pub fn try_idx(&self, point: Point) -> Option<usize> {
if self.in_bounds(point) {
return Some(map_idx(point.x, point.y));
} else {
return None;
}
}
pub fn render(&self, ctx: &mut BTerm) { pub fn render(&self, ctx: &mut BTerm) {
for y in 0..SCREEN_HEIGHT { for y in 0..SCREEN_HEIGHT {
for x in 0..SCREEN_WIDTH { for x in 0..SCREEN_WIDTH {
let idx = map_idx(x, y); let idx = map_idx(x, y);
match self.tiles[idx] { match self.tiles[idx] {
TileType::Floor => ctx.set(x, y, YELLOW, BLACK, to_cp437('.')), TileType::Floor => {
TileType::Wall => ctx.set(x, y, GREEN, BLACK, to_cp437('#')), ctx.set(x, y, YELLOW, BLACK, to_cp437('.'));
}
TileType::Wall => {
ctx.set(x, y, GREEN, BLACK, to_cp437('#'));
}
} }
} }
} }
} }
pub fn in_bounds(&self, point: Point) -> bool {
point.x >= 0 && point.x < SCREEN_WIDTH && point.y >= 0 && point.y < SCREEN_HEIGHT
}
pub fn can_enter_tile(&self, point: Point) -> bool {
self.in_bounds(point) && self.tiles[map_idx(point.x, point.y)] == TileType::Floor
}
pub fn try_idx(&self, point: Point) -> Option<usize> {
if !self.in_bounds(point) {
None
} else {
Some(map_idx(point.x, point.y))
}
}
}
pub fn map_idx(x: i32, y: i32) -> usize {
((y * SCREEN_WIDTH) + x) as usize
} }
@@ -1,39 +1,42 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Player { pub struct Player {
position: Point, pub hp: i32,
pub position: Point,
} }
impl Player { impl Player {
pub fn new(position: Point) -> Self { pub fn new(point: Point) -> Self {
Player { position } return Self {
} hp: 100,
position: point,
pub fn render(&self, ctx: &mut BTerm) { };
ctx.set(
self.position.x,
self.position.y,
WHITE,
BLACK,
to_cp437('@'),
)
} }
pub fn update(&mut self, ctx: &mut BTerm, map: &Map) { pub fn update(&mut self, ctx: &mut BTerm, map: &Map) {
if let Some(key) = ctx.key { if let Some(key) = ctx.key {
let delta = match key { let delta = match key {
VirtualKeyCode::Up => Point::new(0, -1), VirtualKeyCode::Up => Point::new(0, -1),
VirtualKeyCode::Right => Point::new(1, 0),
VirtualKeyCode::Down => Point::new(0, 1), VirtualKeyCode::Down => Point::new(0, 1),
VirtualKeyCode::Left => Point::new(-1, 0), VirtualKeyCode::Left => Point::new(-1, 0),
VirtualKeyCode::Right => Point::new(1, 0),
_ => Point::zero(), _ => Point::zero(),
}; };
let new_position = self.position + delta; let new_pos = self.position + delta;
if map.can_enter_tile(new_pos) {
if map.can_enter_tile(new_position) { self.position = new_pos;
self.position = new_position;
} }
} }
} }
pub fn render(&self, ctx: &mut BTerm) {
return ctx.set(
self.position.x,
self.position.y,
WHITE,
BLACK,
to_cp437('@')
);
}
} }