1use crate::util::DHashMap;
4use std::{env, fmt, fs::OpenOptions, io::Write};
5use tracing::{debug, error};
6
7#[derive(Eq, Hash, PartialEq, Copy, Clone)]
13pub enum GenStatPlotKind {
14 InitialPlaza,
15 Plaza,
16 Workshop,
17 House,
18 GuardTower,
19 Castle,
20 AirshipDock,
21 Tavern,
22 Yard,
23 MultiPlot,
24 Temple,
25}
26
27impl fmt::Display for GenStatPlotKind {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 let s = match self {
30 GenStatPlotKind::InitialPlaza => "InitialPlaza",
31 GenStatPlotKind::Plaza => "Plaza",
32 GenStatPlotKind::Workshop => "Workshop",
33 GenStatPlotKind::House => "House",
34 GenStatPlotKind::GuardTower => "GuardTower",
35 GenStatPlotKind::Castle => "Castle",
36 GenStatPlotKind::AirshipDock => "AirshipDock",
37 GenStatPlotKind::Tavern => "Tavern",
38 GenStatPlotKind::Yard => "Yard",
39 GenStatPlotKind::MultiPlot => "MultiPlot",
40 GenStatPlotKind::Temple => "Temple",
41 };
42 write!(f, "{}", s)
43 }
44}
45
46#[derive(Eq, Hash, PartialEq, Copy, Clone, Default)]
50pub enum GenStatSiteKind {
51 Terracotta,
52 Myrmidon,
53 #[default]
54 City,
55 CliffTown,
56 SavannahTown,
57 CoastalTown,
58 DesertCity,
59}
60
61impl fmt::Display for GenStatSiteKind {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 let s = match self {
64 GenStatSiteKind::Terracotta => "Terracotta",
65 GenStatSiteKind::Myrmidon => "Myrmidon",
66 GenStatSiteKind::City => "City",
67 GenStatSiteKind::CliffTown => "CliffTown",
68 GenStatSiteKind::SavannahTown => "SavannahTown",
69 GenStatSiteKind::CoastalTown => "CoastalTown",
70 GenStatSiteKind::DesertCity => "DesertCity",
71 };
72 write!(f, "{}", s)
73 }
74}
75
76pub struct GenPlot {
81 attempts: u32,
82 successful: u32,
83}
84impl Default for GenPlot {
85 fn default() -> Self { Self::new() }
86}
87
88impl GenPlot {
89 pub fn new() -> Self {
90 Self {
91 attempts: 0,
92 successful: 0,
93 }
94 }
95
96 pub fn attempt(&mut self) { self.attempts += 1; }
97
98 pub fn success(&mut self) { self.successful += 1; }
99}
100
101pub struct GenSite {
103 kind: GenStatSiteKind,
104 name: String,
105 stats: DHashMap<GenStatPlotKind, GenPlot>,
106}
107
108impl GenSite {
109 pub fn new(kind: GenStatSiteKind, name: &str) -> Self {
110 Self {
111 kind,
112 name: name.to_owned(),
113 stats: DHashMap::default(),
114 }
115 }
116
117 pub fn kind(&self) -> &GenStatSiteKind { &self.kind }
118
119 pub fn attempt(&mut self, kind: GenStatPlotKind) {
120 self.stats.entry(kind).or_default().attempt();
121 }
122
123 pub fn success(&mut self, kind: GenStatPlotKind) {
124 self.stats.entry(kind).or_default().success();
125 }
126
127 fn at_least(
128 &self,
129 count: u32,
130 plotkind: &GenStatPlotKind,
131 genplot: &GenPlot,
132 statstr: &mut String,
133 ) {
134 if genplot.successful < count {
135 statstr.push_str(&format!(
136 " {} {} {}: {}/{} GenError: expected at least {}\n",
137 self.kind, self.name, plotkind, genplot.successful, genplot.attempts, count
138 ));
139 }
140 }
141
142 fn at_most(
143 &self,
144 count: u32,
145 plotkind: &GenStatPlotKind,
146 genplot: &GenPlot,
147 statstr: &mut String,
148 ) {
149 if genplot.successful > count {
150 statstr.push_str(&format!(
151 " {} {} {}: {}/{} GenError: expected at most {}\n",
152 self.kind, self.name, plotkind, genplot.successful, genplot.attempts, count
153 ));
154 }
155 }
156
157 fn should_not_be_zero(
158 &self,
159 plotkind: &GenStatPlotKind,
160 genplot: &GenPlot,
161 statstr: &mut String,
162 ) {
163 if genplot.successful == 0 {
164 statstr.push_str(&format!(
165 " {} {} {}: {}/{} GenWarn: should not be zero\n",
166 self.kind, self.name, plotkind, genplot.successful, genplot.attempts
167 ));
168 }
169 }
170
171 fn success_rate(
172 &self,
173 rate: f32,
174 plotkind: &GenStatPlotKind,
175 genplot: &GenPlot,
176 statstr: &mut String,
177 ) {
178 if (genplot.successful as f32 / genplot.attempts as f32) < rate {
179 statstr.push_str(&format!(
180 " {} {} {}: GenWarn: success rate less than {} ({}/{})\n",
181 self.kind, self.name, plotkind, rate, genplot.successful, genplot.attempts
182 ));
183 }
184 }
185}
186
187pub struct SitesGenMeta {
190 seed: u32,
191 sites: DHashMap<String, GenSite>,
192}
193
194fn append_statstr_to_file(file_path: &str, statstr: &str) -> std::io::Result<()> {
195 let mut file = OpenOptions::new()
196 .append(true)
197 .create(true)
198 .open(file_path)?;
199 file.write_all(statstr.as_bytes())?;
200 Ok(())
201}
202
203fn get_bool_env_var(var_name: &str) -> bool {
204 match env::var(var_name).ok().as_deref() {
205 Some("true") => true,
206 Some("false") => false,
207 _ => false,
208 }
209}
210
211fn get_log_opts() -> (bool, Option<String>) {
212 let site_generation_stats_verbose = get_bool_env_var("SITE_GENERATION_STATS_VERBOSE");
213 let site_generation_stats_file_path: Option<String> =
214 env::var("SITE_GENERATION_STATS_LOG").ok();
215 (
216 site_generation_stats_verbose,
217 site_generation_stats_file_path,
218 )
219}
220
221impl SitesGenMeta {
222 pub fn new(seed: u32) -> Self {
223 Self {
224 seed,
225 sites: DHashMap::default(),
226 }
227 }
228
229 pub fn add(&mut self, site_name: &str, kind: GenStatSiteKind) {
230 self.sites
231 .entry(site_name.to_owned())
232 .or_insert_with(|| GenSite::new(kind, site_name));
233 }
234
235 pub fn attempt(&mut self, site_name: &String, kind: GenStatPlotKind) {
236 if let Some(gensite) = self.sites.get_mut(site_name) {
237 gensite.attempt(kind);
238 } else {
239 error!("Site not found: {}", site_name);
240 }
241 }
242
243 pub fn success(&mut self, site_name: &String, kind: GenStatPlotKind) {
244 if let Some(gensite) = self.sites.get_mut(site_name) {
245 gensite.success(kind);
246 } else {
247 error!("Site not found: {}", site_name);
248 }
249 }
250
251 pub fn log(&self) {
263 let current_log_level = tracing::level_filters::LevelFilter::current();
266 if current_log_level == tracing::Level::DEBUG {
267 let (verbose, log_path) = get_log_opts();
268
269 let mut num_sites: u32 = 0;
302 let mut site_counts: DHashMap<GenStatSiteKind, u32> = DHashMap::default();
303 let mut stat_stat_str = String::new();
304 for (_, gensite) in self.sites.iter() {
305 num_sites += 1;
306 *site_counts.entry(*gensite.kind()).or_insert(0) += 1;
307 }
308 stat_stat_str.push_str(&format!(
309 "------------------ SitesGenMeta seed {}\n",
310 self.seed
311 ));
312 if verbose {
313 stat_stat_str.push_str(&format!("Sites: {}\n", num_sites));
314 for (site_kind, count) in site_counts.iter() {
315 stat_stat_str.push_str(&format!(" {}: {}\n", site_kind, count));
316 }
317 }
318 for (site_name, gensite) in self.sites.iter() {
319 let mut stat_err_str = String::new();
320 let mut stat_warn_str = String::new();
321 let mut num_plots: u32 = 0;
322 let mut plot_counts: DHashMap<GenStatPlotKind, (u32, u32)> = DHashMap::default();
323 for (plotkind, genplot) in gensite.stats.iter() {
324 num_plots += 1;
325 plot_counts.entry(*plotkind).or_insert((0, 0)).0 += genplot.successful;
326 plot_counts.entry(*plotkind).or_insert((0, 0)).1 += genplot.attempts;
327 }
328 match &gensite.kind() {
329 GenStatSiteKind::Terracotta => {
330 for (kind, genplot) in gensite.stats.iter() {
331 match &kind {
332 GenStatPlotKind::InitialPlaza => {
333 gensite.at_least(1, kind, genplot, &mut stat_err_str);
334 },
335 GenStatPlotKind::Plaza => {
336 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
337 },
338 GenStatPlotKind::House => {
339 gensite.at_least(1, kind, genplot, &mut stat_err_str);
340 gensite.success_rate(0.1, kind, genplot, &mut stat_warn_str);
341 },
342 GenStatPlotKind::Yard => {
343 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
344 },
345 _ => {},
346 }
347 }
348 },
349 GenStatSiteKind::Myrmidon => {
350 for (kind, genplot) in gensite.stats.iter() {
351 match &kind {
352 GenStatPlotKind::InitialPlaza => {
353 gensite.at_least(1, kind, genplot, &mut stat_err_str);
354 },
355 GenStatPlotKind::Plaza => {
356 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
357 },
358 GenStatPlotKind::House => {
359 gensite.at_least(1, kind, genplot, &mut stat_err_str);
360 gensite.success_rate(0.1, kind, genplot, &mut stat_warn_str);
361 },
362 _ => {},
363 }
364 }
365 },
366 GenStatSiteKind::City => {
367 for (kind, genplot) in gensite.stats.iter() {
368 match &kind {
369 GenStatPlotKind::InitialPlaza => {
370 gensite.at_least(1, kind, genplot, &mut stat_err_str);
371 },
372 GenStatPlotKind::Plaza => {
373 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
374 },
375 GenStatPlotKind::Workshop => {
376 gensite.at_least(1, kind, genplot, &mut stat_err_str);
377 },
378 GenStatPlotKind::House => {
379 gensite.at_least(1, kind, genplot, &mut stat_err_str);
380 gensite.success_rate(0.2, kind, genplot, &mut stat_warn_str);
381 },
382 _ => {},
383 }
384 }
385 },
386 GenStatSiteKind::CliffTown => {
387 for (kind, genplot) in gensite.stats.iter() {
388 match &kind {
389 GenStatPlotKind::InitialPlaza => {
390 gensite.at_least(1, kind, genplot, &mut stat_err_str);
391 },
392 GenStatPlotKind::Plaza => {
393 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
394 },
395 GenStatPlotKind::House => {
396 gensite.at_least(5, kind, genplot, &mut stat_err_str);
397 gensite.success_rate(0.5, kind, genplot, &mut stat_warn_str);
398 },
399 GenStatPlotKind::AirshipDock => {
400 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
401 gensite.success_rate(0.1, kind, genplot, &mut stat_warn_str);
402 },
403 _ => {},
404 }
405 }
406 },
407 GenStatSiteKind::SavannahTown => {
408 for (kind, genplot) in gensite.stats.iter() {
409 match &kind {
410 GenStatPlotKind::InitialPlaza => {
411 gensite.at_least(1, kind, genplot, &mut stat_err_str);
412 },
413 GenStatPlotKind::Plaza => {
414 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
415 },
416 GenStatPlotKind::Workshop => {
417 gensite.at_least(1, kind, genplot, &mut stat_err_str);
418 },
419 GenStatPlotKind::House => {
420 gensite.at_least(1, kind, genplot, &mut stat_err_str);
421 gensite.success_rate(0.5, kind, genplot, &mut stat_warn_str);
422 },
423 GenStatPlotKind::AirshipDock => {
424 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
425 },
426 _ => {},
427 }
428 }
429 },
430 GenStatSiteKind::CoastalTown => {
431 for (kind, genplot) in gensite.stats.iter() {
432 match &kind {
433 GenStatPlotKind::InitialPlaza => {
434 gensite.at_least(1, kind, genplot, &mut stat_err_str);
435 },
436 GenStatPlotKind::Plaza => {
437 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
438 },
439 GenStatPlotKind::Workshop => {
440 gensite.at_least(1, kind, genplot, &mut stat_err_str);
441 },
442 GenStatPlotKind::House => {
443 gensite.at_least(1, kind, genplot, &mut stat_err_str);
444 gensite.success_rate(0.5, kind, genplot, &mut stat_warn_str);
445 },
446 GenStatPlotKind::AirshipDock => {
447 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
448 gensite.at_most(1, kind, genplot, &mut stat_err_str);
449 },
450 _ => {},
451 }
452 }
453 },
454 GenStatSiteKind::DesertCity => {
455 for (kind, genplot) in gensite.stats.iter() {
456 match &kind {
457 GenStatPlotKind::InitialPlaza => {
458 gensite.at_least(1, kind, genplot, &mut stat_err_str);
459 },
460 GenStatPlotKind::Plaza => {
461 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
462 },
463 GenStatPlotKind::MultiPlot => {
464 gensite.at_least(1, kind, genplot, &mut stat_err_str);
465 },
466 GenStatPlotKind::Temple => {
467 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
468 },
469 GenStatPlotKind::AirshipDock => {
470 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
471 },
472 _ => {},
473 }
474 }
475 },
476 }
477 if verbose {
478 stat_stat_str.push_str(&format!("{} {}\n", gensite.kind(), site_name));
479 stat_stat_str.push_str(&format!(" Number of plots: {}\n", num_plots));
480 for (plotkind, count) in plot_counts.iter() {
481 stat_stat_str
482 .push_str(&format!(" {}: {}/{}\n", plotkind, count.0, count.1));
483 }
484 }
485 if !stat_err_str.is_empty() {
486 stat_stat_str.push_str(&stat_err_str.to_string());
487 }
488 if verbose && !stat_warn_str.is_empty() {
489 stat_stat_str.push_str(&stat_warn_str.to_string());
490 }
491 }
492 debug!("{}", stat_stat_str);
493 if let Some(log_path) = log_path {
494 if let Err(e) = append_statstr_to_file(&log_path, &stat_stat_str) {
495 eprintln!("Failed to write to file: {}", e);
496 } else {
497 println!("Statistics written to {}", log_path);
498 }
499 }
500 }
501 }
502}