use macros to create monsters

This commit is contained in:
Joachim Lusiardi 2024-10-25 07:21:34 +02:00
parent 8267ad083f
commit e490011b4e
6 changed files with 221 additions and 99 deletions

76
Cargo.lock generated
View File

@ -104,6 +104,41 @@ dependencies = [
"winapi",
]
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.9.0"
@ -115,6 +150,7 @@ name = "el_diabolo"
version = "0.1.0"
dependencies = [
"crossterm",
"macros",
"petgraph",
"rand",
"ratatui",
@ -143,6 +179,12 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.11"
@ -176,6 +218,12 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.1.0"
@ -257,6 +305,16 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "macros"
version = "0.1.0"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "mio"
version = "1.0.2"
@ -323,18 +381,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.69"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
@ -481,6 +539,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.3"
@ -505,9 +569,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.38"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",

View File

@ -1,3 +1,6 @@
[workspace]
members = ["macros"]
[package]
name = "el_diabolo"
version = "0.1.0"
@ -11,6 +14,7 @@ crossterm = "0.28.1"
rand = "0.8.5"
petgraph = "0.6.5"
whoami = "1.5.2"
macros = { path = "./macros" }
[package.metadata.deb]
maintainer = "Joachim Lusiardi <joachim@lusiardi.de>"
@ -20,5 +24,5 @@ depends = "$auto"
section = "game"
priority = "optional"
assets = [
["target/release/el_diabolo", "/usr/bin/", "755"],
["target/release/el_diabolo", "/usr/bin/", "755"],
]

17
macros/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "macros"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "macros"
path = "src/lib.rs"
proc-macro = true
[dependencies]
syn = "2.0.85"
quote = "1.0.37"
proc-macro2 = "1.0.89"
darling= "0.20.10"

58
macros/src/lib.rs Normal file
View File

@ -0,0 +1,58 @@
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);
let (enum_item, _) = match &input.items[0] {
syn::Item::Enum(enum_item) => (enum_item, &enum_item.attrs),
_ => panic!("must be an enum"),
};
let structs = enum_item.variants.iter().map(|variant| {
let variant_name = &variant.ident;
let t = quote! {
pub struct #variant_name {
name: String,
life: usize,
position: Position,
symbol: String,
color: Color,
experience_gain: usize,
ticks_between_steps: u128,
damage_range: RangeInclusive<usize>,
}
impl Monster for #variant_name {
fn get_name(&self) -> &str { &self.name }
fn is_dead(&self) -> bool { self.life <= 0 }
fn get_experience_gain(&self) -> usize { self.experience_gain }
fn get_representation(&self) -> (&str, Color) { (&self.symbol, self.color) }
fn decrease_life(&mut self, by: usize) {
self.life = self.life.saturating_sub(by);
}
fn get_ticks_between_steps(&self) -> u128 { self.ticks_between_steps }
fn damage(&self) -> usize { rand::thread_rng().gen_range(self.damage_range.clone()) }
#[cfg(test)]
fn get_life(&self) -> usize { self.life }
}
impl HasPosition for #variant_name {
fn get_position(&mut self) -> &mut Position {
&mut self.position
}
fn get_immutable_position(&self) -> &Position {
&self.position
}
}
};
t
});
let output = quote::quote! {
#(#structs)*
};
output.into()
}

View File

@ -12,7 +12,7 @@ use rand::Rng;
use crate::artifacts::{Artifact, Chest, Potion};
use crate::level::{Level, StructureElement};
use crate::monster::{create_monster_by_type, Monster, Orc, Rat, Snake};
use crate::monster::{create_monster_by_type, Monster, MonsterTypes, Orc, Rat, Snake};
use crate::position::Position;
pub const ROOMS_VERTICAL: usize = 7;
@ -268,11 +268,12 @@ impl LevelGenerator {
let t = [
// level 0
HashMap::from([("Rat", 1..=100)]),
HashMap::from([(MonsterTypes::Rat, 1..=100)]),
// level 1
// HashMap::from([("Rat", 1..=50), ("Snake", 51..=100)]),
// // level 2
// HashMap::from([("Rat", 1..=40)]),
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() {
for (mtype, range) in &t[level] {

View File

@ -1,10 +1,10 @@
use std::ops::RangeInclusive;
use crate::position::{HasPosition, Position};
use macros::CreateMonsters;
use rand::Rng;
use ratatui::prelude::Color;
use crate::position::{HasPosition, Position};
pub trait Monster: HasPosition {
fn get_name(&self) -> &str;
fn is_dead(&self) -> bool;
@ -18,99 +18,77 @@ pub trait Monster: HasPosition {
fn damage(&self) -> usize;
}
#[derive(CreateMonsters, PartialEq, Eq, Hash)]
pub enum MonsterTypes {
Rat,
Orc,
Snake,
Skeleton,
}
macro_rules! create_monster {
($($t:ident),+ $(,)?) => ($(
pub struct $t {
name: String,
life: usize,
position: Position,
symbol: String,
color: Color,
experience_gain: usize,
ticks_between_steps: u128,
damage_range: RangeInclusive<usize>,
}
impl Monster for $t {
fn get_name(&self) -> &str { &self.name }
fn is_dead(&self) -> bool { self.life <= 0 }
fn get_experience_gain(&self) -> usize { self.experience_gain }
fn get_representation(&self) -> (&str, Color) { (&self.symbol, self.color) }
fn decrease_life(&mut self, by: usize) {
self.life = self.life.saturating_sub(by);
}
fn get_ticks_between_steps(&self) -> u128 { self.ticks_between_steps }
fn damage(&self) -> usize { rand::thread_rng().gen_range(self.damage_range.clone()) }
#[cfg(test)]
fn get_life(&self) -> usize { self.life }
}
impl HasPosition for $t {
fn get_position(&mut self) -> &mut Position {
&mut self.position
}
fn get_immutable_position(&self) -> &Position {
&self.position
($t:ident $(, $k:ident : $v:expr)*$(,)?) => (
impl $t {
pub fn new_with_position(position: Position) -> Self {
Self {
position,
$($k: $v,)*
}
}
}
)+)
)
}
impl Rat {
pub fn new_with_position(position: Position) -> Self {
Self {
name: "rat".to_string(),
life: 2,
position,
symbol: String::from("R"),
color: Color::Black,
experience_gain: 5,
ticks_between_steps: 5,
damage_range: 1..=2,
}
}
}
create_monster!(Rat);
create_monster!(
Rat,
name:"rat".to_string(),
life: 2,
symbol: String::from("R"),
color: Color::Black,
experience_gain: 5,
ticks_between_steps: 5,
damage_range: 1..=2,
);
impl Orc {
pub fn new_with_position(position: Position) -> Self {
Self {
name: "orc".to_string(),
life: 4,
position,
symbol: String::from("O"),
color: Color::DarkGray,
experience_gain: 10,
ticks_between_steps: 10,
damage_range: 2..=3,
}
}
}
create_monster!(Orc);
create_monster!(
Orc,
name: "orc".to_string(),
life: 4,
symbol: String::from("O"),
color: Color::DarkGray,
experience_gain: 10,
ticks_between_steps: 10,
damage_range: 2..=3,
);
impl Snake {
pub fn new_with_position(position: Position) -> Self {
Self {
name: "snake".to_string(),
life: 3,
position,
symbol: String::from("S"),
color: Color::DarkGray,
experience_gain: 10,
ticks_between_steps: 20,
damage_range: 1..=4,
}
}
}
create_monster!(Snake);
create_monster!(
Snake,
name: "snake".to_string(),
life: 3,
symbol: String::from("s"),
color: Color::Black,
experience_gain: 10,
ticks_between_steps: 20,
damage_range: 1..=4,
);
pub fn create_monster_by_type(mtype: &str, position: Position) -> Box<dyn Monster> {
match mtype {
"Rat" => Box::new(Rat::new_with_position(position)),
"Orc" => Box::new(Orc::new_with_position(position)),
"Snake" => Box::new(Snake::new_with_position(position)),
&_ => {
panic!("Unknown species: {}", mtype)
}
create_monster!(
Skeleton,
name: "skeleton".to_string(),
life: 3,
symbol: String::from("S"),
color: Color::DarkGray,
experience_gain: 20,
ticks_between_steps: 10,
damage_range: 1..=5,
);
pub fn create_monster_by_type(monster_type: &MonsterTypes, position: Position) -> Box<dyn Monster> {
match monster_type {
MonsterTypes::Rat => Box::new(Rat::new_with_position(position)),
MonsterTypes::Orc => Box::new(Orc::new_with_position(position)),
MonsterTypes::Snake => Box::new(Snake::new_with_position(position)),
MonsterTypes::Skeleton => Box::new(Skeleton::new_with_position(position)),
}
}