1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
use crate::dice;
use include_dir::{include_dir, Dir};
use serde::Deserialize;
use serde_yaml;
use std::collections::HashMap;
use std::error::Error;
use std::string::String;
#[derive(Debug, Deserialize)]
struct RandomTable {
formula: String,
rows: Vec<TableRow>,
}
#[derive(Debug, Deserialize)]
struct TableRow {
roll: String,
steps: Vec<TableRowStep>,
}
#[derive(Debug, Deserialize)]
struct TableRowStep {
text: Option<String>,
table: Option<String>,
}
pub struct RandomTables {
tables: HashMap<String, RandomTable>,
}
const YAML_DIR: Dir = include_dir!("src/data/random_tables/");
impl RandomTables {
pub fn new() -> Result<Self, Box<dyn Error>> {
let mut tables_yaml = String::new();
for entry in YAML_DIR.files() {
tables_yaml.push_str(entry.contents_utf8().unwrap());
}
let tables: HashMap<String, RandomTable> = serde_yaml::from_str(tables_yaml.as_str())?;
Ok(RandomTables { tables })
}
/// Rolls on a given random table.
///
/// # Examples
///
/// ```
/// random_tables::roll_table("npc_alignment");
/// ```
pub fn roll_table(&self, table_name: &str) -> String {
let random_table = self.tables.get(table_name);
if let Some(table) = random_table {
// TODO: Probably return an error instead of using #unwrap.
let roll_result = dice::roll_formula(&table.formula).unwrap();
for table_row in &table.rows {
if let Some((start, end)) = RandomTables::parse_roll(&table_row.roll) {
if start <= roll_result.total() && roll_result.total() <= end {
let mut output_text = String::new();
for step in &table_row.steps {
if let Some(text) = &step.text {
output_text.push_str(text);
}
if let Some(table) = &step.table {
let inner_output = self.roll_table(table);
output_text.push_str(&inner_output);
}
output_text.push_str("\n");
}
return output_text.trim().replace("\n", ", ").to_string();
}
} else {
panic!("Invalid roll format in yaml")
}
}
}
String::new()
}
/// Parses a roll string in the random tables yaml.
// TODO: Add validator somewhere that ensures all possible rolls are
// covered by the YAML, and that none overlap.
fn parse_roll(roll: &str) -> Option<(u32, u32)> {
let parts: Vec<&str> = roll.split('-').collect();
match parts.len() {
1 => {
if let Ok(num) = parts[0].parse::<u32>() {
Some((num, num))
} else {
None
}
}
2 => {
if let (Ok(start), Ok(end)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) {
Some((start, end))
} else {
None
}
}
_ => None,
}
}
}
|