From e490011b4e994bb39bda20ed5bbdd0035f470c06 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Fri, 25 Oct 2024 07:21:34 +0200 Subject: [PATCH] use macros to create monsters --- Cargo.lock | 76 +++++++++++++++++++-- Cargo.toml | 6 +- macros/Cargo.toml | 17 +++++ macros/src/lib.rs | 58 ++++++++++++++++ src/level_generator.rs | 11 +-- src/monster.rs | 152 ++++++++++++++++++----------------------- 6 files changed, 221 insertions(+), 99 deletions(-) create mode 100644 macros/Cargo.toml create mode 100644 macros/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 70c50de..b01db38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 075e7eb..eefa0bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 " @@ -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"], ] \ No newline at end of file diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..d33e607 --- /dev/null +++ b/macros/Cargo.toml @@ -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" \ No newline at end of file diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..cf1c841 --- /dev/null +++ b/macros/src/lib.rs @@ -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, + } + 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() +} diff --git a/src/level_generator.rs b/src/level_generator.rs index cae1a5b..0ff81c6 100644 --- a/src/level_generator.rs +++ b/src/level_generator.rs @@ -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] { diff --git a/src/monster.rs b/src/monster.rs index cf1f4b2..82fbb97 100644 --- a/src/monster.rs +++ b/src/monster.rs @@ -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, - } - 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 { - 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 { + 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)), } }