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 {
191 seed: u32,
192 sites: DHashMap<String, GenSite>,
193}
194
195fn append_statstr_to_file(file_path: &str, statstr: &str) -> std::io::Result<()> {
196 let mut file = OpenOptions::new()
197 .append(true)
198 .create(true)
199 .open(file_path)?;
200 file.write_all(statstr.as_bytes())?;
201 Ok(())
202}
203
204fn get_bool_env_var(var_name: &str) -> bool {
205 match env::var(var_name).ok().as_deref() {
206 Some("true") => true,
207 Some("false") => false,
208 _ => false,
209 }
210}
211
212fn get_log_opts() -> (bool, Option<String>) {
213 let site_generation_stats_verbose = get_bool_env_var("SITE_GENERATION_STATS_VERBOSE");
214 let site_generation_stats_file_path: Option<String> =
215 env::var("SITE_GENERATION_STATS_LOG").ok();
216 (
217 site_generation_stats_verbose,
218 site_generation_stats_file_path,
219 )
220}
221
222impl SitesGenMeta {
223 pub fn new(seed: u32) -> Self {
224 Self {
225 seed,
226 sites: DHashMap::default(),
227 }
228 }
229
230 pub fn add<'a>(&mut self, site_name: impl Into<Option<&'a str>>, kind: GenStatSiteKind) {
231 let site_name = site_name.into().unwrap_or("");
232 self.sites
233 .entry(site_name.to_owned())
234 .or_insert_with(|| GenSite::new(kind, site_name));
235 }
236
237 pub fn attempt<'a>(&mut self, site_name: impl Into<Option<&'a str>>, kind: GenStatPlotKind) {
238 let site_name = site_name.into().unwrap_or("");
239 if let Some(gensite) = self.sites.get_mut(site_name) {
240 gensite.attempt(kind);
241 } else {
242 error!("Site not found: {}", site_name);
243 }
244 }
245
246 pub fn success<'a>(&mut self, site_name: impl Into<Option<&'a str>>, kind: GenStatPlotKind) {
247 let site_name = site_name.into().unwrap_or("");
248 if let Some(gensite) = self.sites.get_mut(site_name) {
249 gensite.success(kind);
250 } else {
251 error!("Site not found: {}", site_name);
252 }
253 }
254
255 pub fn log(&self) {
267 let current_log_level = tracing::level_filters::LevelFilter::current();
270 if current_log_level == tracing::Level::DEBUG {
271 let (verbose, log_path) = get_log_opts();
272
273 let mut num_sites: u32 = 0;
306 let mut site_counts: DHashMap<GenStatSiteKind, u32> = DHashMap::default();
307 let mut stat_stat_str = String::new();
308 for (_, gensite) in self.sites.iter() {
309 num_sites += 1;
310 *site_counts.entry(*gensite.kind()).or_insert(0) += 1;
311 }
312 stat_stat_str.push_str(&format!(
313 "------------------ SitesGenMeta seed {}\n",
314 self.seed
315 ));
316 if verbose {
317 stat_stat_str.push_str(&format!("Sites: {}\n", num_sites));
318 for (site_kind, count) in site_counts.iter() {
319 stat_stat_str.push_str(&format!(" {}: {}\n", site_kind, count));
320 }
321 }
322 for (site_name, gensite) in self.sites.iter() {
323 let mut stat_err_str = String::new();
324 let mut stat_warn_str = String::new();
325 let mut num_plots: u32 = 0;
326 let mut plot_counts: DHashMap<GenStatPlotKind, (u32, u32)> = DHashMap::default();
327 for (plotkind, genplot) in gensite.stats.iter() {
328 num_plots += 1;
329 plot_counts.entry(*plotkind).or_insert((0, 0)).0 += genplot.successful;
330 plot_counts.entry(*plotkind).or_insert((0, 0)).1 += genplot.attempts;
331 }
332 match &gensite.kind() {
333 GenStatSiteKind::Terracotta => {
334 for (kind, genplot) in gensite.stats.iter() {
335 match &kind {
336 GenStatPlotKind::InitialPlaza => {
337 gensite.at_least(1, kind, genplot, &mut stat_err_str);
338 },
339 GenStatPlotKind::Plaza => {
340 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
341 },
342 GenStatPlotKind::House => {
343 gensite.at_least(1, kind, genplot, &mut stat_err_str);
344 gensite.success_rate(0.1, kind, genplot, &mut stat_warn_str);
345 },
346 GenStatPlotKind::Yard => {
347 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
348 },
349 _ => {},
350 }
351 }
352 },
353 GenStatSiteKind::Myrmidon => {
354 for (kind, genplot) in gensite.stats.iter() {
355 match &kind {
356 GenStatPlotKind::InitialPlaza => {
357 gensite.at_least(1, kind, genplot, &mut stat_err_str);
358 },
359 GenStatPlotKind::Plaza => {
360 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
361 },
362 GenStatPlotKind::House => {
363 gensite.at_least(1, kind, genplot, &mut stat_err_str);
364 gensite.success_rate(0.1, kind, genplot, &mut stat_warn_str);
365 },
366 _ => {},
367 }
368 }
369 },
370 GenStatSiteKind::City => {
371 for (kind, genplot) in gensite.stats.iter() {
372 match &kind {
373 GenStatPlotKind::InitialPlaza => {
374 gensite.at_least(1, kind, genplot, &mut stat_err_str);
375 },
376 GenStatPlotKind::Plaza => {
377 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
378 },
379 GenStatPlotKind::Workshop => {
380 gensite.at_least(1, kind, genplot, &mut stat_err_str);
381 },
382 GenStatPlotKind::House => {
383 gensite.at_least(1, kind, genplot, &mut stat_err_str);
384 gensite.success_rate(0.2, kind, genplot, &mut stat_warn_str);
385 },
386 _ => {},
387 }
388 }
389 },
390 GenStatSiteKind::CliffTown => {
391 for (kind, genplot) in gensite.stats.iter() {
392 match &kind {
393 GenStatPlotKind::InitialPlaza => {
394 gensite.at_least(1, kind, genplot, &mut stat_err_str);
395 },
396 GenStatPlotKind::Plaza => {
397 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
398 },
399 GenStatPlotKind::House => {
400 gensite.at_least(5, kind, genplot, &mut stat_err_str);
401 gensite.success_rate(0.5, kind, genplot, &mut stat_warn_str);
402 },
403 GenStatPlotKind::AirshipDock => {
404 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
405 gensite.success_rate(0.1, kind, genplot, &mut stat_warn_str);
406 },
407 _ => {},
408 }
409 }
410 },
411 GenStatSiteKind::SavannahTown => {
412 for (kind, genplot) in gensite.stats.iter() {
413 match &kind {
414 GenStatPlotKind::InitialPlaza => {
415 gensite.at_least(1, kind, genplot, &mut stat_err_str);
416 },
417 GenStatPlotKind::Plaza => {
418 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
419 },
420 GenStatPlotKind::Workshop => {
421 gensite.at_least(1, kind, genplot, &mut stat_err_str);
422 },
423 GenStatPlotKind::House => {
424 gensite.at_least(1, kind, genplot, &mut stat_err_str);
425 gensite.success_rate(0.5, kind, genplot, &mut stat_warn_str);
426 },
427 GenStatPlotKind::AirshipDock => {
428 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
429 },
430 _ => {},
431 }
432 }
433 },
434 GenStatSiteKind::CoastalTown => {
435 for (kind, genplot) in gensite.stats.iter() {
436 match &kind {
437 GenStatPlotKind::InitialPlaza => {
438 gensite.at_least(1, kind, genplot, &mut stat_err_str);
439 },
440 GenStatPlotKind::Plaza => {
441 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
442 },
443 GenStatPlotKind::Workshop => {
444 gensite.at_least(1, kind, genplot, &mut stat_err_str);
445 },
446 GenStatPlotKind::House => {
447 gensite.at_least(1, kind, genplot, &mut stat_err_str);
448 gensite.success_rate(0.5, kind, genplot, &mut stat_warn_str);
449 },
450 GenStatPlotKind::AirshipDock => {
451 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
452 gensite.at_most(1, kind, genplot, &mut stat_err_str);
453 },
454 _ => {},
455 }
456 }
457 },
458 GenStatSiteKind::DesertCity => {
459 for (kind, genplot) in gensite.stats.iter() {
460 match &kind {
461 GenStatPlotKind::InitialPlaza => {
462 gensite.at_least(1, kind, genplot, &mut stat_err_str);
463 },
464 GenStatPlotKind::Plaza => {
465 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
466 },
467 GenStatPlotKind::MultiPlot => {
468 gensite.at_least(1, kind, genplot, &mut stat_err_str);
469 },
470 GenStatPlotKind::Temple => {
471 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
472 },
473 GenStatPlotKind::AirshipDock => {
474 gensite.should_not_be_zero(kind, genplot, &mut stat_warn_str);
475 },
476 _ => {},
477 }
478 }
479 },
480 }
481 if verbose {
482 stat_stat_str.push_str(&format!("{} {}\n", gensite.kind(), site_name));
483 stat_stat_str.push_str(&format!(" Number of plots: {}\n", num_plots));
484 for (plotkind, count) in plot_counts.iter() {
485 stat_stat_str
486 .push_str(&format!(" {}: {}/{}\n", plotkind, count.0, count.1));
487 }
488 }
489 if !stat_err_str.is_empty() {
490 stat_stat_str.push_str(&stat_err_str.to_string());
491 }
492 if verbose && !stat_warn_str.is_empty() {
493 stat_stat_str.push_str(&stat_warn_str.to_string());
494 }
495 }
496 debug!("{}", stat_stat_str);
497 if let Some(log_path) = log_path {
498 if let Err(e) = append_statstr_to_file(&log_path, &stat_stat_str) {
499 eprintln!("Failed to write to file: {}", e);
500 } else {
501 println!("Statistics written to {}", log_path);
502 }
503 }
504 }
505 }
506}