Compare commits

..

No commits in common. "3249529f94704d5bf59bac2a58e03bf52fdc63b8" and "74831857cf76c7fe4f6e8842bcb27e18f1e274f3" have entirely different histories.

12 changed files with 382 additions and 1326 deletions

View File

@ -1,16 +0,0 @@
name: publish package
on:
push:
branches-ignore:
- main
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: run tests
run: |
cd ${{ gitea.workspace }}
cargo test

1
.gitignore vendored
View File

@ -1,2 +1 @@
/target
*.profraw

View File

@ -1,10 +0,0 @@
```
cargo install grcov
rustup component add llvm-tools-preview
```
```
RUSTFLAGS="-Cinstrument-coverage" cargo clean
RUSTFLAGS="-Cinstrument-coverage" cargo test
grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
```

View File

@ -3,6 +3,7 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_derive(CreateMonsters)]
pub fn create_monsters(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::File);

View File

@ -39,7 +39,7 @@ impl Chest {
impl Artifact for Chest {
fn get_representation(&self) -> (&str, Color) {
("C", Color::Cyan)
("C", Color::Blue)
}
fn get_immutable_position(&self) -> &Position {

View File

@ -1,25 +1,19 @@
use std::{collections::HashMap, ops::RangeInclusive};
use crate::{monster::MonsterTypes, room::RoomType};
use crate::monster::MonsterTypes;
/// the number of rooms in vertical direction
pub const ROOMS_HORIZONTAL: usize = 8;
/// the number of rooms in horizontal 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;
// the minmal width of a room
pub const ROOM_MIN_WIDTH: usize = 4;
// the minmal height of a room
pub const ROOM_MIN_HEIGHT: usize = 4;
/// How many levels does the dungeon have?
pub const LEVELS: usize = 4;
pub const LEVELS: usize = 3;
/// length of a game frame in ms
pub const FRAME_LENGTH: u64 = 100;
@ -30,9 +24,9 @@ pub const MIN_WIDTH: u16 = 120;
pub const MIN_HEIGHT: u16 = LEVEL_HEIGHT as u16;
/// the calculated width of a level
pub const LEVEL_WIDTH: usize = 1 + ROOMS_HORIZONTAL * ROOM_WIDTH;
pub const LEVEL_WIDTH: usize = 1 + ROOMS_VERTICAL * ROOM_WIDTH;
/// the calculated height of a level
pub const LEVEL_HEIGHT: usize = 1 + ROOMS_VERTICAL * ROOM_HEIGHT;
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 =[
@ -41,24 +35,10 @@ pub fn get_monsters_per_level() -> Vec<HashMap<MonsterTypes, std::ops::RangeIncl
// level 2
vec![(MonsterTypes::Rat, 50), (MonsterTypes::Snake, 50)],
// level 3
vec![
(MonsterTypes::Orc, 34),
(MonsterTypes::Skeleton, 33),
(MonsterTypes::Snake, 33),
],
// level 4
vec![
(MonsterTypes::Orc, 34),
(MonsterTypes::Skeleton, 33),
(MonsterTypes::Snake, 33),
],
vec![(MonsterTypes::Orc, 34), (MonsterTypes::Skeleton, 33), (MonsterTypes::Snake, 33)],
];
if tmp.len() < LEVELS {
panic!(
"Only {} monster sets defined for {} levels!",
tmp.len(),
LEVELS
);
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() {
@ -67,69 +47,10 @@ pub fn get_monsters_per_level() -> Vec<HashMap<MonsterTypes, std::ops::RangeIncl
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
}
pub fn get_room_type_per_level() -> Vec<HashMap<RoomType, std::ops::RangeInclusive<u8>>> {
let tmp = [
// level 1
vec![
(RoomType::EmptyRoom, 50),
(RoomType::ArtifactRoom, 10),
(RoomType::MonsterRoom, 20),
(RoomType::BasicRoom, 20),
],
// level 2
vec![
(RoomType::EmptyRoom, 50),
(RoomType::BasicRoom, 25),
(RoomType::MonsterRoom, 12),
(RoomType::ArtifactRoom, 13),
],
// level 3
vec![
(RoomType::EmptyRoom, 50),
(RoomType::BasicRoom, 25),
(RoomType::MonsterRoom, 25),
],
// level 4
vec![
(RoomType::BasicRoom, 33),
(RoomType::MonsterRoom, 33),
(RoomType::ArtifactRoom, 34),
],
];
if tmp.len() < LEVELS {
panic!(
"Only {} room sets defined for {} levels!",
tmp.len(),
LEVELS
);
}
let mut result: Vec<HashMap<RoomType, std::ops::RangeInclusive<u8>>> = vec![];
for (idx, level) in tmp.iter().enumerate() {
let mut sum = 0;
let mut map: HashMap<RoomType, RangeInclusive<u8>> = HashMap::new();
for room in level {
map.insert(room.0, RangeInclusive::new(sum + 1, sum + room.1));
sum += room.1;
}
if sum != 100 {
panic!(
"all percentages must add to 100 (was {}) per level, error in level {}!",
sum,
idx + 1
);
panic!("all percentages must add to 100 (was {}) per level, error in level {}!", sum, idx+1);
}
result.push(map);
}

View File

@ -57,7 +57,6 @@ impl Level {
return (None, None, None);
}
if !self.discovered[x][y] {
// #[cfg(test)]
return (Some(StructureElement::Unknown), None, None);
}
let search_pos = &Position::new(self.level, x, y);

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,13 @@
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::style::Color;
use ratatui::widgets::{StatefulWidget, Widget};
use crate::game::Game;
use crate::level::StructureElement;
use crate::position::HasPosition;
const FG_BROWN: Color = Color::Rgb(140, 34, 0);
const BACKGROUND: Color = Color::Black;
const FG_BROWN: Color = Color::Rgb(186, 74, 0);
pub struct LevelWidget {}
@ -16,13 +15,6 @@ impl LevelWidget {
fn set_cell(&self, buf: &mut Buffer, x: u16, y: u16, symbol: &str, fg: Color, bg: Color) {
buf[(x, y)].set_symbol(symbol).set_bg(bg).set_fg(fg);
}
fn set_bold_cell(&self, buf: &mut Buffer, x: u16, y: u16, symbol: &str, fg: Color, bg: Color) {
buf[(x, y)]
.set_symbol(symbol)
.set_bg(bg)
.set_fg(fg)
.set_style(Style::new().add_modifier(Modifier::BOLD));
}
}
impl StatefulWidget for LevelWidget {
@ -47,36 +39,36 @@ impl StatefulWidget for LevelWidget {
(Some(structure_element), None, None) => {
match structure_element {
StructureElement::Start => {
self.set_cell(buf, x, y, "α", Color::White, BACKGROUND);
self.set_cell(buf, x, y, "α", Color::Black, Color::Gray);
}
StructureElement::End => {
self.set_cell(buf, x, y, "Ω", Color::White, BACKGROUND);
self.set_cell(buf, x, y, "Ω", Color::Black, Color::Gray);
}
StructureElement::Wall => {
// TODO add fancy walls with https://en.wikipedia.org/wiki/Box-drawing_characters
self.set_cell(buf, x, y, "#", FG_BROWN, FG_BROWN);
self.set_cell(buf, x, y, "#", FG_BROWN, Color::Gray);
}
StructureElement::Floor => {
self.set_cell(buf, x, y, " ", FG_BROWN, BACKGROUND);
self.set_cell(buf, x, y, " ", FG_BROWN, Color::Gray);
}
StructureElement::StairDown => {
self.set_cell(buf, x, y, ">", Color::White, BACKGROUND);
self.set_cell(buf, x, y, ">", Color::Black, Color::Gray);
}
StructureElement::StairUp => {
self.set_cell(buf, x, y, "<", Color::White, BACKGROUND);
self.set_cell(buf, x, y, "<", Color::Black, Color::Gray);
}
StructureElement::Unknown => {
self.set_cell(buf, x, y, "", Color::Gray, BACKGROUND);
self.set_cell(buf, x, y, "", Color::DarkGray, Color::Gray);
}
}
}
(_, Some(m), _) => {
let (s, c) = m.get_representation();
self.set_cell(buf, x, y, s, c, BACKGROUND);
self.set_cell(buf, x, y, s, c, Color::Gray);
}
(_, _, Some(t)) => {
let (s, c) = t.get_representation();
self.set_bold_cell(buf, x, y, s, c, BACKGROUND);
self.set_cell(buf, x, y, s, c, Color::Gray);
}
(None, None, None) => {}
};
@ -88,7 +80,7 @@ impl StatefulWidget for LevelWidget {
let player_pos = player.get_immutable_position();
let player_x = al + player_pos.get_x() as u16;
let player_y = at + player_pos.get_y() as u16;
self.set_cell(buf, player_x, player_y, "8", Color::LightRed, BACKGROUND);
self.set_cell(buf, player_x, player_y, "8", Color::Red, Color::Gray);
}
}
}

View File

@ -36,7 +36,6 @@ mod level_widget;
mod monster;
mod player;
mod position;
mod room;
//
fn main() -> Result<()> {

View File

@ -45,7 +45,7 @@ create_monster!(
name:"rat".to_string(),
life: 2,
symbol: String::from("r"),
color: Color::White,
color: Color::Black,
experience_gain: 5,
ticks_between_steps: 5,
damage_range: 1..=2,
@ -56,7 +56,7 @@ create_monster!(
name:"spider".to_string(),
life: 3,
symbol: String::from("s"),
color: Color::Yellow,
color: Color::Blue,
experience_gain: 7,
ticks_between_steps: 7,
damage_range: 2..=3,
@ -67,7 +67,7 @@ create_monster!(
name: "orc".to_string(),
life: 4,
symbol: String::from("O"),
color: Color::Gray,
color: Color::DarkGray,
experience_gain: 10,
ticks_between_steps: 10,
damage_range: 2..=3,
@ -78,7 +78,7 @@ create_monster!(
name: "snake".to_string(),
life: 3,
symbol: String::from("s"),
color: Color::White,
color: Color::Black,
experience_gain: 10,
ticks_between_steps: 20,
damage_range: 1..=4,
@ -89,7 +89,7 @@ create_monster!(
name: "skeleton".to_string(),
life: 3,
symbol: String::from("S"),
color: Color::Gray,
color: Color::DarkGray,
experience_gain: 20,
ticks_between_steps: 10,
damage_range: 1..=5,

View File

@ -1,280 +0,0 @@
use std::{
cmp::{max, min},
ops::RangeInclusive,
};
use crate::{
constants::{
LEVEL_HEIGHT, LEVEL_WIDTH, ROOM_HEIGHT, ROOM_MIN_HEIGHT, ROOM_MIN_WIDTH, ROOM_WIDTH,
},
level::StructureElement,
};
use rand::rngs::ThreadRng;
use rand::Rng;
#[derive(PartialEq, Copy, Clone, Eq, Hash, Debug)]
pub enum RoomType {
Start,
End,
StairUp,
StairDown,
BasicRoom,
ArtifactRoom,
MonsterRoom,
EmptyRoom,
}
#[derive(Copy, Clone, Debug)]
pub struct Connection {
pub start_pos: (usize, usize),
pub end_pos: (usize, usize),
}
impl Connection {
pub fn render(
&self,
rng: &mut ThreadRng,
tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH],
) {
fn calc_range(a: usize, b: usize) -> RangeInclusive<usize> {
if a > b {
b..=a
} else {
a..=b
}
}
// the tuples are (col, row)
let abs_d_col =
max(self.start_pos.0, self.end_pos.0) - min(self.start_pos.0, self.end_pos.0);
let abs_d_row =
max(self.start_pos.1, self.end_pos.1) - min(self.start_pos.1, self.end_pos.1);
if abs_d_col == 0 {
for row in calc_range(self.end_pos.1, self.start_pos.1) {
tgt[self.end_pos.0][row] = StructureElement::Floor;
}
} else if abs_d_row == 0 {
for col in calc_range(self.end_pos.0, self.start_pos.0) {
tgt[col][self.end_pos.1] = StructureElement::Floor;
}
} else {
let d_row = self.end_pos.1 as i32 - self.start_pos.1 as i32;
if d_row > 0 {
// more up/down
let d_row = self.end_pos.1 - self.start_pos.1;
let vert_offset = if d_row > 3 {
rng.gen_range(1..=d_row - 1)
} else {
d_row / 2
};
for r in self.start_pos.1..=(self.start_pos.1 + vert_offset) {
tgt[self.start_pos.0][r] = StructureElement::Floor;
}
for c in calc_range(self.end_pos.0, self.start_pos.0) {
tgt[c][self.start_pos.1 + vert_offset] = StructureElement::Floor;
}
for r in (self.start_pos.1 + vert_offset)..=self.end_pos.1 {
tgt[self.end_pos.0][r] = StructureElement::Floor;
}
} else {
// more left/right
let d_col = self.end_pos.0 - self.start_pos.0;
let horizont_offset = if d_col > 3 {
rng.gen_range(1..=d_col - 1)
} else {
d_col / 2
};
for tgt_col in tgt
.iter_mut()
.skip(self.start_pos.0)
.take(horizont_offset + 1)
{
tgt_col[self.start_pos.1] = StructureElement::Floor;
}
for r in calc_range(self.end_pos.1, self.start_pos.1) {
tgt[self.start_pos.0 + horizont_offset][r] = StructureElement::Floor;
}
for tgt_col in tgt
.iter_mut()
.take(self.end_pos.0 + 1)
.skip(self.start_pos.0 + horizont_offset)
{
tgt_col[self.end_pos.1] = StructureElement::Floor;
}
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Room {
pub kind: RoomType,
pub offset_x: usize,
pub offset_y: usize,
pub width: usize,
pub height: usize,
pub special: (usize, usize),
pub connection_down: Option<Connection>,
pub connection_right: Option<Connection>,
}
impl Room {
pub fn new(rng: &mut ThreadRng) -> Self {
let width = rng.gen_range(ROOM_MIN_WIDTH..ROOM_WIDTH);
let height = rng.gen_range(ROOM_MIN_HEIGHT..ROOM_HEIGHT);
let offset_x = rng.gen_range(0..(ROOM_WIDTH - width));
let offset_y = rng.gen_range(0..(ROOM_HEIGHT - height));
let sx = offset_x + rng.gen_range(1..width - 1);
let sy = offset_y + rng.gen_range(1..height - 1);
Self {
kind: RoomType::EmptyRoom,
offset_x,
offset_y,
width,
height,
special: (sx, sy),
connection_down: None,
connection_right: None,
}
}
// pub fn get_x_range(&self) -> Range<usize> {
// self.offset_x..self.offset_x + self.width
// }
// pub fn get_y_range(&self) -> Range<usize> {
// self.offset_y..self.offset_y + self.height
// }
pub fn render(
&self,
tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH],
col: usize,
row: usize,
) -> (usize, usize) {
let top = 1 + row * ROOM_HEIGHT;
let left = 1 + col * ROOM_WIDTH;
for col in tgt.iter_mut().skip(left).take(ROOM_WIDTH) {
//left..left + ROOM_WIDTH {
for row_element in col.iter_mut().skip(top).take(ROOM_HEIGHT) {
// top..top + ROOM_HEIGHT {
*row_element = StructureElement::Wall;
}
}
if self.kind == RoomType::EmptyRoom {
return (0, 0);
}
// render floor elements
for x in 0..self.width {
for y in 0..self.height {
tgt[left + self.offset_x + x][top + self.offset_y + y] = StructureElement::Floor;
}
}
match self.kind {
RoomType::Start => {
tgt[left + self.special.0][top + self.special.1] = StructureElement::Start;
}
RoomType::End => {
tgt[left + self.special.0][top + self.special.1] = StructureElement::End;
}
RoomType::StairUp => {
tgt[left + self.special.0][top + self.special.1] = StructureElement::StairUp;
}
RoomType::StairDown => {
tgt[left + self.special.0][top + self.special.1] = StructureElement::StairDown;
}
_ => {}
};
(left + self.special.0, top + self.special.1)
}
}
#[test]
fn test_room_creation() {
let mut rng = rand::thread_rng();
let room = Room::new(&mut rng);
assert_eq!(room.kind, RoomType::EmptyRoom);
for _ in 0..1000 {
let room = Room::new(&mut rng);
assert!((3..=ROOM_WIDTH).contains(&room.width));
}
}
#[cfg(test)]
fn all_wall(
tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH],
col: usize,
row: usize,
) -> bool {
let top = 1 + row * ROOM_HEIGHT;
let left = 1 + col * ROOM_WIDTH;
for x in left..left + ROOM_WIDTH {
for y in top..top + ROOM_HEIGHT {
if tgt[x][y] != StructureElement::Wall {
return false;
}
}
}
true
}
#[cfg(test)]
fn has_structure_element(
tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH],
col: usize,
row: usize,
element: StructureElement,
) -> bool {
let top = 1 + row * ROOM_HEIGHT;
let left = 1 + col * ROOM_WIDTH;
for x in left..left + ROOM_WIDTH {
for y in top..top + ROOM_HEIGHT {
if tgt[x][y] == element {
return true;
}
}
}
false
}
#[test]
fn test_room_render_empty() {
let mut rng = rand::thread_rng();
let room = Room::new(&mut rng);
assert_eq!(room.kind, RoomType::EmptyRoom);
let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH];
room.render(&mut structure, 0, 0);
assert!(all_wall(&mut structure, 0, 0));
}
#[test]
fn test_room_render_basic() {
let mut rng = rand::thread_rng();
let mut room = Room::new(&mut rng);
room.kind = RoomType::BasicRoom;
let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH];
room.render(&mut structure, 1, 3);
assert!(!all_wall(&mut structure, 1, 3));
}
#[test]
fn test_room_render_start() {
let mut rng = rand::thread_rng();
let mut room = Room::new(&mut rng);
room.kind = RoomType::Start;
let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH];
room.render(&mut structure, 1, 3);
assert!(!all_wall(&mut structure, 1, 3));
assert!(has_structure_element(
&mut structure,
1,
3,
StructureElement::Start
));
}