Extract constants and make monsters variable per level

This commit is contained in:
Joachim Lusiardi 2024-10-26 17:00:51 +02:00
parent e490011b4e
commit b833b43c7c
6 changed files with 94 additions and 40 deletions

58
src/constants.rs Normal file
View File

@ -0,0 +1,58 @@
use std::{collections::HashMap, ops::RangeInclusive};
use crate::monster::MonsterTypes;
/// the number of rooms in vertical direction
pub const ROOMS_VERTICAL: usize = 7;
/// the number of rooms in horizontal direction
pub const ROOMS_HORIZONTAL: usize = 7;
/// the width of a room in the grid of rooms (number of characters)
pub const ROOM_WIDTH: usize = 9;
/// the height of a room in the grid of rooms (number of characters)
pub const ROOM_HEIGHT: usize = 6;
/// How many levels does the dungeon have?
pub const LEVELS: usize = 3;
/// length of a game frame in ms
pub const FRAME_LENGTH: u64 = 100;
/// define the minimum width of a terminal to run the game, must be at least the level width plus some space for stats and messages
pub const MIN_WIDTH: u16 = 120;
/// define the minimum height of a terminal to run the game, this must be at least the level height!
pub const MIN_HEIGHT: u16 = LEVEL_HEIGHT as u16;
/// the calculated width of a level
pub const LEVEL_WIDTH: usize = 1 + ROOMS_VERTICAL * ROOM_WIDTH;
/// the calculated height of a level
pub const LEVEL_HEIGHT: usize = 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT;
pub fn get_monsters_per_level() -> Vec<HashMap<MonsterTypes, std::ops::RangeInclusive<u8>>> {
let tmp = vec![
// level 1
vec![(MonsterTypes::Rat, 50), (MonsterTypes::Spider, 50)],
// level 2
vec![(MonsterTypes::Rat, 50), (MonsterTypes::Snake, 50)],
// level 3
vec![(MonsterTypes::Orc, 33), (MonsterTypes::Skeleton, 33), (MonsterTypes::Snake, 33)],
];
if tmp.len() != LEVELS {
panic!("Only {} monster sets defined for {} levels!", tmp.len(), LEVELS);
}
let mut result: Vec<HashMap<MonsterTypes, std::ops::RangeInclusive<u8>>> = vec![];
for (idx, level) in tmp.iter().enumerate() {
let mut sum = 0;
let mut map: HashMap<MonsterTypes, RangeInclusive<u8>> = HashMap::new();
for monster in level {
map.insert(monster.0, RangeInclusive::new(sum+1, sum+monster.1));
sum += monster.1;
}
if sum != 100 {
panic!("all percentages must add to 100 (was {}) per level, error in level {}!", sum, idx+1);
}
result.push(map);
}
result
}

View File

@ -1,11 +1,9 @@
use crate::constants::LEVELS;
use crate::level::{Level, StructureElement}; use crate::level::{Level, StructureElement};
use crate::level_generator::LevelGenerator; use crate::level_generator::LevelGenerator;
use crate::player::Player; use crate::player::Player;
use crate::position::{HasPosition, Position}; use crate::position::{HasPosition, Position};
/// How many levels does the dungeon have?
pub const LEVELS: usize = 2;
#[derive(PartialEq)] #[derive(PartialEq)]
/// represents a state of a game /// represents a state of a game
pub enum GameState { pub enum GameState {

View File

@ -6,10 +6,8 @@ use rand::Rng;
use crate::artifacts::Artifact; use crate::artifacts::Artifact;
#[cfg(test)] #[cfg(test)]
use crate::artifacts::{Chest, Potion}; use crate::artifacts::{Chest, Potion};
use crate::level_generator::ROOMS_HORIZONTAL; use crate::constants::LEVEL_HEIGHT;
use crate::level_generator::ROOMS_VERTICAL; use crate::constants::LEVEL_WIDTH;
use crate::level_generator::ROOM_HEIGHT;
use crate::level_generator::ROOM_WIDTH;
use crate::monster::Monster; use crate::monster::Monster;
#[cfg(test)] #[cfg(test)]
use crate::monster::{Orc, Rat}; use crate::monster::{Orc, Rat};
@ -17,9 +15,6 @@ use crate::player::Player;
use crate::position::HasPosition; use crate::position::HasPosition;
use crate::position::Position; use crate::position::Position;
pub const LEVEL_WIDTH: usize = 1 + ROOMS_VERTICAL * ROOM_WIDTH;
pub const LEVEL_HEIGHT: usize = 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT;
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum StructureElement { pub enum StructureElement {
Start, Start,

View File

@ -1,5 +1,4 @@
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::collections::HashMap;
use std::ops::Range; use std::ops::Range;
use petgraph::algo::min_spanning_tree; use petgraph::algo::min_spanning_tree;
@ -11,16 +10,13 @@ use rand::rngs::ThreadRng;
use rand::Rng; use rand::Rng;
use crate::artifacts::{Artifact, Chest, Potion}; use crate::artifacts::{Artifact, Chest, Potion};
use crate::constants::{
get_monsters_per_level, ROOMS_HORIZONTAL, ROOMS_VERTICAL, ROOM_HEIGHT, ROOM_WIDTH,
};
use crate::level::{Level, StructureElement}; use crate::level::{Level, StructureElement};
use crate::monster::{create_monster_by_type, Monster, MonsterTypes, Orc, Rat, Snake}; use crate::monster::{create_monster_by_type, Monster, Orc, Rat, Snake};
use crate::position::Position; use crate::position::Position;
pub const ROOMS_VERTICAL: usize = 7;
pub const ROOMS_HORIZONTAL: usize = 7;
pub const ROOM_WIDTH: usize = 9;
pub const ROOM_HEIGHT: usize = 6;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
enum RoomType { enum RoomType {
Start, Start,
@ -266,15 +262,7 @@ impl LevelGenerator {
let level = position.get_level(); let level = position.get_level();
let value = rng.gen_range(1..=100); let value = rng.gen_range(1..=100);
let t = [ let t = get_monsters_per_level();
// level 0
HashMap::from([(MonsterTypes::Rat, 1..=100)]),
// level 1
HashMap::from([(MonsterTypes::Rat, 1..=50), (MonsterTypes::Snake, 51..=100)]),
// level 2
HashMap::from([(MonsterTypes::Orc, 1..=100)]),
HashMap::from([(MonsterTypes::Skeleton, 1..=100)]),
];
if level < t.len() { if level < t.len() {
for (mtype, range) in &t[level] { for (mtype, range) in &t[level] {
if range.contains(&value) { if range.contains(&value) {

View File

@ -2,6 +2,12 @@ use std::io::stdout;
use std::io::Result; use std::io::Result;
use std::time::Instant; use std::time::Instant;
use constants::FRAME_LENGTH;
use constants::LEVELS;
use constants::LEVEL_HEIGHT;
use constants::LEVEL_WIDTH;
use constants::MIN_HEIGHT;
use constants::MIN_WIDTH;
use crossterm::{ use crossterm::{
event::{self, KeyCode, KeyEventKind}, event::{self, KeyCode, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
@ -22,6 +28,7 @@ use crate::player::Player;
use crate::position::HasPosition; use crate::position::HasPosition;
mod artifacts; mod artifacts;
mod constants;
mod game; mod game;
mod level; mod level;
mod level_generator; mod level_generator;
@ -30,12 +37,6 @@ mod monster;
mod player; mod player;
mod position; mod position;
/// length of a game frame in ms
pub const FRAME_LENGTH: u64 = 100;
pub const MIN_WIDTH: u16 = 120;
pub const MIN_HEIGHT: u16 = 30;
// //
fn main() -> Result<()> { fn main() -> Result<()> {
let mut game = Game::new(Player::new(realname().as_str(), 30)); let mut game = Game::new(Player::new(realname().as_str(), 30));
@ -79,15 +80,15 @@ fn main() -> Result<()> {
area.width = MIN_WIDTH; area.width = MIN_WIDTH;
} }
if area.height > MIN_HEIGHT { if area.height > MIN_HEIGHT {
area.y = (area.height - level::LEVEL_HEIGHT as u16) / 2; area.y = (area.height - LEVEL_HEIGHT as u16) / 2;
area.height = level::LEVEL_HEIGHT as u16; area.height = LEVEL_HEIGHT as u16;
} }
let map_area = Rect { let map_area = Rect {
x: area.x, x: area.x,
y: area.y, y: area.y,
width: level::LEVEL_WIDTH as u16, width: LEVEL_WIDTH as u16,
height: level::LEVEL_HEIGHT as u16, height: LEVEL_HEIGHT as u16,
}; };
frame.render_stateful_widget(LevelWidget {}, map_area, &mut game); frame.render_stateful_widget(LevelWidget {}, map_area, &mut game);
@ -109,12 +110,13 @@ fn main() -> Result<()> {
.style(Style::default().bg(Color::Blue)); .style(Style::default().bg(Color::Blue));
frame.render_widget( frame.render_widget(
Paragraph::new(format!( Paragraph::new(format!(
"Health: {}/{}\nExp: {}\nGold: {}\nLevel: {}\nInventory: {}/{}", "Health: {} of {}\nExp: {}\nGold: {}\nLevel: {} of {}\nInventory used: {} of {}",
game.get_player().get_life(), game.get_player().get_life(),
game.get_player().get_max_life(), game.get_player().get_max_life(),
game.get_player().get_experience(), game.get_player().get_experience(),
game.get_player().get_gold(), game.get_player().get_gold(),
game.get_player().get_immutable_position().get_level() + 1, game.get_player().get_immutable_position().get_level() + 1,
LEVELS,
game.get_player().inventory_size().0, game.get_player().inventory_size().0,
game.get_player().inventory_size().1, game.get_player().inventory_size().1,
)) ))

View File

@ -18,12 +18,13 @@ pub trait Monster: HasPosition {
fn damage(&self) -> usize; fn damage(&self) -> usize;
} }
#[derive(CreateMonsters, PartialEq, Eq, Hash)] #[derive(CreateMonsters, PartialEq, Eq, Hash, Clone, Copy)]
pub enum MonsterTypes { pub enum MonsterTypes {
Rat, Rat,
Orc, Orc,
Snake, Snake,
Skeleton, Skeleton,
Spider,
} }
macro_rules! create_monster { macro_rules! create_monster {
@ -43,13 +44,24 @@ create_monster!(
Rat, Rat,
name:"rat".to_string(), name:"rat".to_string(),
life: 2, life: 2,
symbol: String::from("R"), symbol: String::from("r"),
color: Color::Black, color: Color::Black,
experience_gain: 5, experience_gain: 5,
ticks_between_steps: 5, ticks_between_steps: 5,
damage_range: 1..=2, damage_range: 1..=2,
); );
create_monster!(
Spider,
name:"spider".to_string(),
life: 3,
symbol: String::from("s"),
color: Color::Blue,
experience_gain: 7,
ticks_between_steps: 7,
damage_range: 2..=3,
);
create_monster!( create_monster!(
Orc, Orc,
name: "orc".to_string(), name: "orc".to_string(),
@ -89,6 +101,7 @@ pub fn create_monster_by_type(monster_type: &MonsterTypes, position: Position) -
MonsterTypes::Orc => Box::new(Orc::new_with_position(position)), MonsterTypes::Orc => Box::new(Orc::new_with_position(position)),
MonsterTypes::Snake => Box::new(Snake::new_with_position(position)), MonsterTypes::Snake => Box::new(Snake::new_with_position(position)),
MonsterTypes::Skeleton => Box::new(Skeleton::new_with_position(position)), MonsterTypes::Skeleton => Box::new(Skeleton::new_with_position(position)),
MonsterTypes::Spider => Box::new(Spider::new_with_position(position)),
} }
} }