Added parser to read creature and site definitions from a file
parent
8bba930fbd
commit
ee8db7cb09
|
|
@ -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]
|
||||||
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/main.rs
62
src/main.rs
|
|
@ -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 {}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue