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},
|
||||
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 {}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue