1use std::fmt;
2use std::str::FromStr;
3use std::hash::{Hash, Hasher};
4use std::io::prelude::*;
5use std::collections::HashSet;
6
7use regex::Regex;
8
9use crate::error::Error;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12enum Sex { Male, Female, Any }
13
14impl fmt::Display for Sex
15{
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
17 {
18 match self
19 {
20 Self::Male => write!(f, "M"),
21 Self::Female => write!(f, "F"),
22 Self::Any => write!(f, "?"),
23 }
24 }
25}
26
27impl FromStr for Sex
28{
29 type Err = Error;
30 fn from_str(s: &str) -> Result<Self, Self::Err>
31 {
32 match s
33 {
34 "M" => Ok(Self::Male),
35 "F" => Ok(Self::Female),
36 "?" => Ok(Self::Any),
37 _ => Err(error!(FormatError, "Invalid sex: {}", s)),
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43enum Role { Base, Mate }
44
45#[derive(Debug, Clone)]
49struct Monster
50{
51 name: String,
53 sex: Sex,
54 index: u16,
58 plus_level_min: u16,
61}
62
63impl Monster
64{
65 #[allow(dead_code)]
66 fn new(name: &str, sex: Sex) -> Self
67 {
68 Self {
69 name: name.to_owned(),
70 sex: sex,
71 index: 0,
72 plus_level_min: 0,
73 }
74 }
75}
76
77impl PartialEq for Monster
78{
79 fn eq(&self, other: &Self) -> bool
80 {
81 self.name == other.name && self.sex == other.sex &&
82 self.index == other.index
83 }
84}
85
86impl Eq for Monster {}
87
88impl Hash for Monster
89{
90 fn hash<H: Hasher>(&self, state: &mut H)
91 {
92 self.name.hash(state);
93 self.sex.hash(state);
94 self.index.hash(state);
95 }
96}
97
98impl fmt::Display for Monster
99{
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
104 {
105 let sex_str = if self.sex == Sex::Any
106 {
107 String::new()
108 }
109 else
110 {
111 format!("({})", self.sex)
112 };
113
114 let index_str = if self.index > 0 { format!("/{}", self.index) }
115 else {String::new()};
116
117 write!(f, "{}{}{}", self.name, sex_str, index_str)
118 }
119}
120
121impl FromStr for Monster
122{
123 type Err = Error;
124
125 fn from_str(s: &str) -> Result<Self, Self::Err>
127 {
128 let pattern = Regex::new(
129 r"([a-zA-Z0-9]+)(\([MF?]\))?(/[0-9]+)?(\+[0-9]+)?"
130 ).unwrap();
131 if let Some(groups) = pattern.captures(s)
132 {
133 let whole = groups.get(0).unwrap();
135 if whole.start() != 0 || whole.end() != s.len()
136 {
137 return Err(error!(FormatError,
138 "Invalid monster specification: {}",
139 s));
140 }
141
142 Ok(Self {
143 name: groups.get(1).ok_or_else(
144 || error!(FormatError, "Name not specified for monster"))?
145 .as_str().to_owned(),
146 sex: if let Some(m) = groups.get(2)
147 {
148 m.as_str()[1..2].parse()?
149 }
150 else
151 {
152 Sex::Any
153 },
154 plus_level_min: if let Some(m) = groups.get(4)
155 {
156 m.as_str()[1..].parse().map_err(
157 |_| error!(FormatError, "Invalid +lvl"))?
158 }
159 else
160 {
161 0
162 },
163 index : if let Some(m) = groups.get(3)
164 {
165 m.as_str()[1..].parse().map_err(
166 |_| error!(FormatError, "Invalid index"))?
167 }
168 else
169 {
170 0
171 }
172 })
173 }
174 else
175 {
176 Err(error!(FormatError, "Invalid monster specification: {}", s))
177 }
178 }
179}
180
181#[derive(Debug, Clone)]
184struct MonsterVis
185{
186 monster: Monster,
187 role: Option<Role>,
188 name: Option<String>,
191}
192
193impl MonsterVis
194{
195 fn fromMonster(m: Monster, role: Option<Role>, name: Option<String>) -> Self
196 {
197 Self {
198 monster: m,
199 role: role,
200 name: name,
201 }
202 }
203
204 fn label(&self) -> String
206 {
207 let plus_str = if self.monster.plus_level_min > 0
208 {
209 format!("+{}", self.monster.plus_level_min)
210 }
211 else
212 {
213 String::new()
214 };
215
216 let custom_name_str = if let Some(n) = &self.name
217 {
218 format!("<br/><font point-size=\"10\">“{}”</font>", n)
219 }
220 else
221 {
222 String::new()
223 };
224
225 self.monster.name.clone() + &plus_str + &custom_name_str
226 }
227
228 fn toDotSpec(&self) -> String
229 {
230 let color = match self.monster.sex
231 {
232 Sex::Male => "#70a1ff",
233 Sex::Female => "#ff4757",
234 Sex::Any => "#eccc68",
235 };
236
237 let border_str = if self.role == Some(Role::Base)
238 {
239 String::from(", penwidth=2")
240 }
241 else
242 {
243 String::new()
244 };
245
246 format!("\"{}\"[label=<{}>, style=\"filled\", fillcolor=\"{}\"{}, \
247 URL=\"https://darksair.org/dwm2-breed/monster/{}\"];",
248 self.monster.to_string(), self.label(), color, border_str,
249 self.monster.name)
250 }
251
252 fn update(&mut self, new: Self)
257 {
258 if self.role == None
259 {
260 self.role = new.role;
261 }
262 if new.monster.plus_level_min > self.monster.plus_level_min
263 {
264 self.monster.plus_level_min = new.monster.plus_level_min;
265 }
266 if self.name == None
267 {
268 self.name = new.name;
269 }
270 }
271}
272
273impl FromStr for MonsterVis
274{
275 type Err = Error;
276 fn from_str(s: &str) -> Result<Self, Self::Err>
277 {
278 let pattern = Regex::new(r"(.+):[ \t]+(.+)").unwrap();
279 if let Some(groups) = pattern.captures(s)
280 {
281 let whole = groups.get(0).unwrap();
283 if whole.start() != 0 || whole.end() != s.len()
284 {
285 return Err(error!(FormatError,
286 "Invalid monster specification: {}",
287 s));
288 }
289
290 let monster: Monster = groups.get(1).unwrap().as_str().parse()?;
291 let name = groups.get(2).unwrap().as_str();
292 Ok(Self {
293 monster: monster,
294 role: None,
295 name: Some(String::from(name)),
296 })
297 }
298 else
299 {
300 Err(error!(FormatError, "Invalid monster specification: {}", s))
301 }
302 }
303}
304
305impl PartialEq for MonsterVis
306{
307 fn eq(&self, other: &Self) -> bool
308 {
309 self.monster == other.monster
310 }
311}
312
313impl Eq for MonsterVis {}
314
315impl Hash for MonsterVis
316{
317 fn hash<H: Hasher>(&self, state: &mut H)
318 {
319 self.monster.hash(state);
320 }
321}
322
323#[derive(Debug, Clone, PartialEq, Eq)]
324struct Breed
325{
326 base: Monster,
327 mate: Monster,
328 outcome: Monster,
329}
330
331impl FromStr for Breed
332{
333 type Err = Error;
334
335 fn from_str(s: &str) -> Result<Self, Self::Err>
336 {
337 let s = s.trim();
338
339 let (lhs, rhs) = s.split_once('=')
340 .ok_or_else(|| error!(FormatError, "Invalid breed: {}", s))?;
341
342 let outcome_str = rhs.trim();
343
344 let (base_str, mate_str) = lhs.split_once('+')
345 .ok_or_else(|| error!(FormatError, "Invalid breed: {}", s))?;
346
347 Ok(Self {
348 base: base_str.trim().parse()?,
349 mate: mate_str.trim().parse()?,
350 outcome: outcome_str.parse()?,
351 })
352 }
353}
354
355
356#[derive(Debug, Clone, PartialEq, Eq)]
357enum BreedOrSpec
358{
359 Breed(Breed),
360 Spec(MonsterVis),
361}
362
363impl FromStr for BreedOrSpec
364{
365 type Err = Error;
366 fn from_str(s: &str) -> Result<Self, Self::Err>
367 {
368 if let Ok(breed) = s.parse::<Breed>()
369 {
370 Ok(Self::Breed(breed))
371 }
372 else
373 {
374 Ok(Self::Spec(s.parse()?))
375 }
376 }
377}
378
379pub struct BreedPlan
380{
381 steps: Vec<Breed>,
382 specs: HashSet<MonsterVis>,
383}
384
385impl BreedPlan
386{
387 pub fn new() -> Self
388 {
389 Self { steps: Vec::new(), specs: HashSet::new() }
390 }
391
392 fn addStep(&mut self, breed: Breed)
393 {
394 self.steps.push(breed);
395 }
396
397 fn addSpec(&mut self, spec: MonsterVis) -> bool
398 {
399 if self.specs.contains(&spec)
400 {
401 false
402 }
403 else
404 {
405 self.specs.insert(spec);
406 true
407 }
408 }
409
410 pub fn fromStream(stream: &mut dyn BufRead) -> Result<Self, Error>
411 {
412 let mut plan = Self::new();
413 for line in stream.lines()
414 {
415 let line = line.map_err(
416 |e| rterr!("Failed to read a line: {}", e))?;
417 if line.trim().is_empty()
418 {
419 continue;
420 }
421 if line.chars().next() == Some('#')
422 {
423 continue;
424 }
425
426 match line.parse::<BreedOrSpec>()?
427 {
428 BreedOrSpec::Breed(b) => { plan.addStep(b); },
429 BreedOrSpec::Spec(vis) => {
430 let monster_str = vis.monster.to_string();
431 if !plan.addSpec(vis)
432 {
433 println!("WARNING: duplicated monster spec for {}, \
434 ignoring...",
435 monster_str);
436 }
437 },
438 }
439 }
440 Ok(plan)
441 }
442
443 pub fn toDot(&self) -> String
444 {
445 let mut lines: Vec<String> = Vec::new();
446 let mut monsters: HashSet<MonsterVis> = self.specs.clone();
447
448 lines.push(String::from("digraph G {"));
449 lines.push(String::from("node[shape=\"box\"];"));
450 for breed in &self.steps
451 {
452 let base_vis = MonsterVis::fromMonster(
453 breed.base.clone(), Some(Role::Base), None);
454 let mate_vis = MonsterVis::fromMonster(
455 breed.mate.clone(), Some(Role::Mate), None);
456 let result_vis = MonsterVis::fromMonster(
457 breed.outcome.clone(), None, None);
458
459 match monsters.take(&base_vis)
460 {
461 Some(mut m) => {
462 m.update(base_vis);
463 monsters.insert(m);
464 },
465 None => { monsters.insert(base_vis); },
466 }
467 match monsters.take(&mate_vis)
468 {
469 Some(mut m) => {
470 m.update(mate_vis);
471 monsters.insert(m);
472 },
473 None => { monsters.insert(mate_vis); },
474 }
475 match monsters.take(&result_vis)
476 {
477 Some(mut m) => {
478 m.update(result_vis);
479 monsters.insert(m);
480 },
481 None => { monsters.insert(result_vis); },
482 }
483 lines.push(format!("\"{}\" -> \"{}\";", breed.base.to_string(),
484 breed.outcome.to_string()));
485 lines.push(format!("\"{}\" -> \"{}\";", breed.mate.to_string(),
486 breed.outcome.to_string()));
487 }
488
489 for m in monsters
490 {
491 lines.push(m.toDotSpec());
492 }
493
494 lines.push(String::from("}"));
495 lines.join("\n")
496 }
497}
498
499#[cfg(test)]
500mod tests
501{
502 use super::*;
504
505 #[test]
506 fn printMonster()
507 {
508 assert_eq!(Monster::new("Zapbird", Sex::Female).to_string(),
509 "Zapbird(F)");
510 assert_eq!(Monster
511 {
512 name: String::from("Zapbird"),
513 sex: Sex::Any,
514 index: 1,
515 plus_level_min: 0,
516 }.to_string(),
517 "Zapbird/1");
518 assert_eq!(Monster
519 {
520 name: String::from("Zapbird"),
521 sex: Sex::Male,
522 index: 1,
523 plus_level_min: 2,
524 }.to_string(),
525 "Zapbird(M)/1");
526 }
527
528 #[test]
529 fn parseMonster() -> Result<(), Error>
530 {
531 assert_eq!("Zapbird(M)/3+2".parse::<Monster>()?,
532 Monster
533 {
534 name: String::from("Zapbird"),
535 sex: Sex::Male,
536 index: 3,
537 plus_level_min: 2,
538 });
539 assert_eq!("Zapbird".parse::<Monster>()?,
540 Monster
541 {
542 name: String::from("Zapbird"),
543 sex: Sex::Any,
544 index: 0,
545 plus_level_min: 0,
546 });
547 assert_eq!("Zapbird/2".parse::<Monster>()?,
548 Monster
549 {
550 name: String::from("Zapbird"),
551 sex: Sex::Any,
552 index: 2,
553 plus_level_min: 0,
554 });
555 assert_eq!("Zapbird+5".parse::<Monster>()?,
556 Monster
557 {
558 name: String::from("Zapbird"),
559 sex: Sex::Any,
560 index: 0,
561 plus_level_min: 5,
562 });
563 Ok(())
564 }
565
566 #[test]
567 fn invalidMonster() -> Result<(), Error>
568 {
569 assert!("Zapbird\\1".parse::<Monster>().is_err());
570 assert!("".parse::<Monster>().is_err());
571 assert!("Zapbird+".parse::<Monster>().is_err());
572 assert!("Zapbird\\4+1".parse::<Monster>().is_err());
573 assert!("Zapbird+2/1".parse::<Monster>().is_err());
574 assert!("Zapbird+2(F)".parse::<Monster>().is_err());
575 Ok(())
576 }
577
578 #[test]
579 fn parseBreed() -> Result<(), Error>
580 {
581 assert_eq!("Base + Mate = Result".parse::<Breed>()?,
582 Breed {
583 base: Monster::new("Base", Sex::Any),
584 mate: Monster::new("Mate", Sex::Any),
585 outcome: Monster::new("Result", Sex::Any),
586 });
587
588 assert_eq!("Blizzardy + Phoenix = RainHawk".parse::<Breed>()?,
590 Breed {
591 base: Monster::new("Blizzardy", Sex::Any),
592 mate: Monster::new("Phoenix", Sex::Any),
593 outcome: Monster::new("RainHawk", Sex::Any),
594 });
595
596 assert_eq!("Base+Mate=Result".parse::<Breed>()?,
598 Breed {
599 base: Monster::new("Base", Sex::Any),
600 mate: Monster::new("Mate", Sex::Any),
601 outcome: Monster::new("Result", Sex::Any),
602 });
603
604 Ok(())
605 }
606
607 #[test]
608 fn invalidBreed() -> Result<(), Error>
609 {
610 assert!("Base + = Result".parse::<Breed>().is_err());
611 assert!("Base + + = Result".parse::<Breed>().is_err());
612 assert!("Base + Result".parse::<Breed>().is_err());
613 assert!("".parse::<Breed>().is_err());
614 Ok(())
615 }
616}