diff --git a/defs/data.def b/defs/data.def new file mode 100644 index 0000000..3474cb7 --- /dev/null +++ b/defs/data.def @@ -0,0 +1,20 @@ +[SITE:CAVE] + [SIZE:1:3] + [UNDERGROUND] + +[CREATURE:BANDIT] + [NAME:bandit:bandits] + [HEALTH:8] + [AC:12] + [ATTACK:2:1:8] + [HUMANOID] + [PATROL] + +[CREATURE:SPIDER] + [NAME:spider:spiders] + [HEALTH:12] + [AC:13] + [ATTACK:2:3:10] + [MULTIATTACK:2] + [ANIMAL] + [PREFER_DARK] diff --git a/src/definitions.rs b/src/definitions.rs new file mode 100644 index 0000000..7972ab4 --- /dev/null +++ b/src/definitions.rs @@ -0,0 +1,265 @@ +use std::{borrow::BorrowMut, io::Read, str::FromStr}; + +#[derive(Debug)] +pub enum TopLevelTag { + Site, + Creature, +} + +#[derive(Default, Debug, Clone)] +pub struct Tag { + pub name: String, + pub values: Vec, +} + +impl Tag { + pub fn parse_value(&self) -> Result { + T::parse(&self.values).map_err(|_| ParseError::Parser(format!("Invalid value {:?} in tag {}", self.values, self.name))) + } +} + +pub trait ParseQuery: Sized { + fn parse(values: &Vec) -> Result; +} + +trait Parseable: Sized + FromStr { + fn parse(string: &str) -> Result { + string.parse().map_err(|_| ()) + } +} + +impl Parseable for String {} +impl Parseable for i32 {} +impl Parseable for f32 {} +impl Parseable for bool {} + +impl ParseQuery for A { + fn parse(values: &Vec) -> Result { + Ok(A::parse(values.get(0).ok_or(())?)?) + } +} + +impl ParseQuery for (A, B) { + fn parse(values: &Vec) -> Result { + Ok(( + A::parse(values.get(0).ok_or(())?)?, + B::parse(values.get(1).ok_or(())?)?, + )) + } +} + +impl ParseQuery for (A, B, C) { + fn parse(values: &Vec) -> Result { + Ok(( + A::parse(values.get(0).ok_or(())?)?, + B::parse(values.get(1).ok_or(())?)?, + C::parse(values.get(2).ok_or(())?)?, + )) + } +} + +#[derive(Default, Debug)] +pub struct Definitions { + pub sites: Vec, + pub creatures: Vec, +} + +#[derive(Default, Debug)] +pub struct SiteDef { + pub id: String, + pub min_size: i32, + pub max_size: i32, + pub is_underground: bool, +} + +impl SiteDef { + fn parse(header: Tag, tags: &[Tag]) -> Result { + let mut result = Self { + id: header.parse_value::().unwrap(), + ..Default::default() + }; + for tag in tags { + match tag.name.as_str() { + "SIZE" => (result.min_size, result.max_size) = tag.parse_value().unwrap(), + "UNDERGROUND" => result.is_underground = true, + _ => { + eprintln!("Unknown tag '{}' in SITE defs", tag.name); + } + } + } + Ok(result) + } +} + +#[derive(Default, Debug)] +pub struct CreatureDef { + pub id: String, + pub name_singular: String, + pub name_plural: String, + pub health: i32, + pub armor_class: i32, + pub to_hit_modifier: i32, + pub min_damage: i32, + pub max_damage: i32, + pub multiattack: Option, + pub is_humanoid: bool, + pub is_animal: bool, + pub patrols: bool, + pub prefers_dark: bool, +} + +impl CreatureDef { + fn parse(header: Tag, tags: &[Tag]) -> Result { + let mut result = Self { + id: header.parse_value().unwrap(), + ..Default::default() + }; + for tag in tags { + match tag.name.as_str() { + "NAME" => (result.name_singular, result.name_plural) = tag.parse_value()?, + "HEALTH" => result.health = tag.parse_value()?, + "AC" => result.armor_class = tag.parse_value()?, + "ATTACK" => (result.to_hit_modifier, result.min_damage, result.max_damage) = tag.parse_value()?, + "MULTIATTACK" => result.multiattack = Some(tag.parse_value()?), + "HUMANOID" => result.is_humanoid = true, + "ANIMAL" => result.is_animal = true, + "PATROL" => result.patrols = true, + "PREFER_DARK" => result.prefers_dark = true, + _ => { + eprintln!("Unknown tag '{}' in CREATURE defs", tag.name); + } + } + } + Ok(result) + } +} + +pub enum ValueToken { + String(String), + Integer(i32), +} + +#[derive(Debug)] +pub enum ParseError { + Lexer(LexerError), + Syntax(String), + Definition(String), + Parser(String), + Unexpected, +} + +#[derive(Debug)] +pub enum LexerError { + Io(std::io::Error), + UnclosedTag, +} + +pub struct DefinitionParser; + +impl DefinitionParser { + pub fn parse(source: impl Read) -> Result { + let mut defs = Definitions { + sites: vec![], + creatures: vec![], + }; + + let tags = match Self::lexer(source) { + Ok(tags) => tags, + Err(err) => { + eprintln!("{err:?}"); + return Err(ParseError::Lexer(err)); + } + }; + + let mut unparsed_defs: Vec<(Tag, Vec)> = Vec::new(); + + let mut definition_builder: Option<(Tag, Vec)> = None; + for tag in tags { + match Self::match_top_level_tag(&tag.name) { + Some(_) => { + if let Some(builder) = definition_builder { + unparsed_defs.push(builder); + } + definition_builder = Some((tag, vec![])); + } + None => match definition_builder.borrow_mut() { + Some(builder) => builder.1.push(tag.clone()), + None => { + return Err(ParseError::Syntax( + "First tag must be a top-level-tag".into(), + )) + } + }, + } + } + + match definition_builder { + Some(builder) => unparsed_defs.push(builder), + None => { + return Err(ParseError::Definition("No definitions found".into())); + } + } + + for (header, tags) in unparsed_defs { + match Self::match_top_level_tag(&header.name) { + Some(def_type) => match def_type { + TopLevelTag::Site => defs.sites.push(SiteDef::parse(header, &tags)?), + TopLevelTag::Creature => { + defs.creatures.push(CreatureDef::parse(header, &tags)?) + } + }, + None => { + return Err(ParseError::Unexpected); + } + } + } + + println!("{defs:?}"); + + Ok(defs) + } + + fn lexer(mut source: impl Read) -> Result, LexerError> { + let mut tags = vec![]; + let mut tag_builder: Option = None; + let mut buffer = String::new(); + source + .read_to_string(&mut buffer) + .map_err(|err| LexerError::Io(err))?; + for c in buffer.chars() { + match tag_builder.borrow_mut() { + None => match c { + '[' => tag_builder = Some(Tag::default()), + _ => {} + }, + Some(tag) => match c { + ']' => { + tags.push(tag.clone()); + tag_builder = None + } + ':' => tag.values.push("".into()), + '[' => return Err(LexerError::UnclosedTag), + _ => { + if tag.values.is_empty() { + tag.name.push(c) + } else { + tag.values.last_mut().unwrap().push(c) + } + } + }, + } + } + if tag_builder.is_some() { + return Err(LexerError::UnclosedTag); + } + Ok(tags) + } + + fn match_top_level_tag(tag: &str) -> Option { + Some(match tag { + "SITE" => TopLevelTag::Site, + "CREATURE" => TopLevelTag::Creature, + _ => return None, + }) + } +} diff --git a/src/main.rs b/src/main.rs index 3d8ec24..522f465 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ use std::{ collections::{HashMap, HashSet}, fmt::Display, num::NonZeroUsize, - ops::Sub, time::Duration, }; @@ -10,6 +9,8 @@ use enum_dispatch::enum_dispatch; use serde::{Deserialize, Serialize}; use uuid::Uuid; +pub mod definitions; + const SAVE_FILE: &'static str = "world.bin"; fn main() { @@ -30,24 +31,27 @@ fn main() { } }; - for site in world.sites.iter() { - println!("{site}") - } + let source = std::fs::File::open("defs/data.def").unwrap(); + definitions::DefinitionParser::parse(source).unwrap(); - let mut input = String::new(); - loop { - std::io::stdin().read_line(&mut input).unwrap(); - if vec!["q", "quit", "exit"] - .iter() - .any(|quit_cmd| input.trim() == *quit_cmd) - { - break; - } - let start = std::time::Instant::now(); - world.advance_time(Duration::from_secs(3600)); - let end = std::time::Instant::now(); - println!("World tick: {}us", end.sub(start).as_micros()); - } + // for site in world.sites.iter() { + // println!("{site}") + // } + + // let mut input = String::new(); + // loop { + // std::io::stdin().read_line(&mut input).unwrap(); + // if vec!["q", "quit", "exit"] + // .iter() + // .any(|quit_cmd| input.trim() == *quit_cmd) + // { + // break; + // } + // let start = std::time::Instant::now(); + // world.advance_time(Duration::from_secs(3600)); + // let end = std::time::Instant::now(); + // println!("World tick: {}us", end.sub(start).as_micros()); + // } } #[derive(Debug, Default, Serialize, Deserialize)] @@ -113,7 +117,7 @@ impl Site { .get_mut(index) .expect("Creature generation index for depth is out of bounds") .population - .push(Creature::new(CreatureData::Bandit(Bandit::default()))) + .push(Creature::new(CreatureBehaviour::Bandit(Bandit::default()))) } } { @@ -126,7 +130,7 @@ impl Site { .get_mut(index) .expect("Creature generation index for depth is out of bounds") .population - .push(Creature::new(CreatureData::Spider(Spider::default()))) + .push(Creature::new(CreatureBehaviour::Spider(Spider::default()))) } } } @@ -188,7 +192,7 @@ pub struct SiteArea { #[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] pub struct Creature { pub uuid: Uuid, - pub data: CreatureData, + pub data: CreatureBehaviour, } impl Display for Creature { @@ -198,7 +202,7 @@ impl Display for Creature { } impl Creature { - pub fn new(data: CreatureData) -> Self { + pub fn new(data: CreatureBehaviour) -> Self { Self { uuid: Uuid::new_v4(), data, @@ -206,7 +210,7 @@ impl Creature { } } -#[enum_dispatch(CreatureData)] +#[enum_dispatch(CreatureBehaviour)] pub trait CreatureImpl { fn species_id(&self) -> String; @@ -223,11 +227,21 @@ pub trait CreatureImpl { #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[enum_dispatch] -pub enum CreatureData { +pub enum CreatureBehaviour { + Default(DefaultBehaviour), Bandit(Bandit), Spider(Spider), } +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct DefaultBehaviour; + +impl CreatureImpl for DefaultBehaviour { + fn species_id(&self) -> String { + "default".into() + } +} + #[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct Bandit {}