Added parser to read creature and site definitions from a file

master
hheik 2024-03-20 18:39:58 +02:00
parent 8bba930fbd
commit ee8db7cb09
3 changed files with 323 additions and 24 deletions

20
defs/data.def Normal file
View File

@ -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]

265
src/definitions.rs Normal file
View File

@ -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<String>,
}
impl Tag {
pub fn parse_value<T: ParseQuery>(&self) -> Result<T, ParseError> {
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<String>) -> Result<Self, ()>;
}
trait Parseable: Sized + FromStr {
fn parse(string: &str) -> Result<Self, ()> {
string.parse().map_err(|_| ())
}
}
impl Parseable for String {}
impl Parseable for i32 {}
impl Parseable for f32 {}
impl Parseable for bool {}
impl<A: Parseable> ParseQuery for A {
fn parse(values: &Vec<String>) -> Result<Self, ()> {
Ok(A::parse(values.get(0).ok_or(())?)?)
}
}
impl<A: Parseable, B: Parseable> ParseQuery for (A, B) {
fn parse(values: &Vec<String>) -> Result<Self, ()> {
Ok((
A::parse(values.get(0).ok_or(())?)?,
B::parse(values.get(1).ok_or(())?)?,
))
}
}
impl<A: Parseable, B: Parseable, C: Parseable> ParseQuery for (A, B, C) {
fn parse(values: &Vec<String>) -> Result<Self, ()> {
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<SiteDef>,
pub creatures: Vec<CreatureDef>,
}
#[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<Self, ParseError> {
let mut result = Self {
id: header.parse_value::<String>().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<i32>,
pub is_humanoid: bool,
pub is_animal: bool,
pub patrols: bool,
pub prefers_dark: bool,
}
impl CreatureDef {
fn parse(header: Tag, tags: &[Tag]) -> Result<Self, ParseError> {
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<Definitions, ParseError> {
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<Tag>)> = Vec::new();
let mut definition_builder: Option<(Tag, Vec<Tag>)> = 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<Vec<Tag>, LexerError> {
let mut tags = vec![];
let mut tag_builder: Option<Tag> = 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<TopLevelTag> {
Some(match tag {
"SITE" => TopLevelTag::Site,
"CREATURE" => TopLevelTag::Creature,
_ => return None,
})
}
}

View File

@ -2,7 +2,6 @@ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt::Display, fmt::Display,
num::NonZeroUsize, num::NonZeroUsize,
ops::Sub,
time::Duration, time::Duration,
}; };
@ -10,6 +9,8 @@ use enum_dispatch::enum_dispatch;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
pub mod definitions;
const SAVE_FILE: &'static str = "world.bin"; const SAVE_FILE: &'static str = "world.bin";
fn main() { fn main() {
@ -30,24 +31,27 @@ fn main() {
} }
}; };
for site in world.sites.iter() { let source = std::fs::File::open("defs/data.def").unwrap();
println!("{site}") definitions::DefinitionParser::parse(source).unwrap();
}
let mut input = String::new(); // for site in world.sites.iter() {
loop { // println!("{site}")
std::io::stdin().read_line(&mut input).unwrap(); // }
if vec!["q", "quit", "exit"]
.iter() // let mut input = String::new();
.any(|quit_cmd| input.trim() == *quit_cmd) // loop {
{ // std::io::stdin().read_line(&mut input).unwrap();
break; // if vec!["q", "quit", "exit"]
} // .iter()
let start = std::time::Instant::now(); // .any(|quit_cmd| input.trim() == *quit_cmd)
world.advance_time(Duration::from_secs(3600)); // {
let end = std::time::Instant::now(); // break;
println!("World tick: {}us", end.sub(start).as_micros()); // }
} // 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)] #[derive(Debug, Default, Serialize, Deserialize)]
@ -113,7 +117,7 @@ impl Site {
.get_mut(index) .get_mut(index)
.expect("Creature generation index for depth is out of bounds") .expect("Creature generation index for depth is out of bounds")
.population .population
.push(Creature::new(CreatureData::Bandit(Bandit::default()))) .push(Creature::new(CreatureBehaviour::Bandit(Bandit::default())))
} }
} }
{ {
@ -126,7 +130,7 @@ impl Site {
.get_mut(index) .get_mut(index)
.expect("Creature generation index for depth is out of bounds") .expect("Creature generation index for depth is out of bounds")
.population .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)] #[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
pub struct Creature { pub struct Creature {
pub uuid: Uuid, pub uuid: Uuid,
pub data: CreatureData, pub data: CreatureBehaviour,
} }
impl Display for Creature { impl Display for Creature {
@ -198,7 +202,7 @@ impl Display for Creature {
} }
impl Creature { impl Creature {
pub fn new(data: CreatureData) -> Self { pub fn new(data: CreatureBehaviour) -> Self {
Self { Self {
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
data, data,
@ -206,7 +210,7 @@ impl Creature {
} }
} }
#[enum_dispatch(CreatureData)] #[enum_dispatch(CreatureBehaviour)]
pub trait CreatureImpl { pub trait CreatureImpl {
fn species_id(&self) -> String; fn species_id(&self) -> String;
@ -223,11 +227,21 @@ pub trait CreatureImpl {
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[enum_dispatch] #[enum_dispatch]
pub enum CreatureData { pub enum CreatureBehaviour {
Default(DefaultBehaviour),
Bandit(Bandit), Bandit(Bandit),
Spider(Spider), 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)] #[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Bandit {} pub struct Bandit {}