1#![warn(missing_docs)]
14
15mod multiset;
16mod unescaped;
17
18use multiset::BTreeMultiSet;
19use nom::{
20 branch::alt,
21 bytes::complete::{escaped, tag},
22 character::complete::{char, digit0, digit1, multispace0, multispace1, none_of, one_of, u64},
23 combinator::{all_consuming, map, opt},
24 error::{ErrorKind, ParseError},
25 multi::separated_list1,
26 sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
27 IResult, Parser,
28};
29use num::rational::BigRational;
30use num::{BigInt, One, Zero};
31use std::collections::hash_map::Entry;
32use std::collections::HashMap;
33use std::error::Error as StdError;
34use std::fmt::{Display, Error as FmtError, Formatter};
35pub use unescaped::{EscapedStr, Unescaped};
36
37#[derive(Debug, PartialEq, Clone)]
51pub struct ExtensiveFormGame<'a> {
52 name: &'a EscapedStr,
53 player_names: Box<[&'a EscapedStr]>,
54 comment: Option<&'a EscapedStr>,
55 root: Node<'a>,
56}
57
58impl<'a> ExtensiveFormGame<'a> {
59 pub fn name(&self) -> &'a EscapedStr {
61 self.name
62 }
63
64 pub fn player_names(&self) -> &[&'a EscapedStr] {
66 &self.player_names
67 }
68
69 pub fn comment(&self) -> Option<&'a EscapedStr> {
71 self.comment
72 }
73
74 pub fn root(&self) -> &Node<'a> {
76 &self.root
77 }
78}
79
80impl<'a> Display for ExtensiveFormGame<'a> {
81 fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), FmtError> {
82 write!(out, "EFG 2 R \"{}\" {{ ", self.name.as_raw_str())?;
83 for name in self.player_names.iter() {
84 write!(out, "\"{}\" ", name.as_raw_str())?;
85 }
86 writeln!(out, "}}")?;
87 if let Some(comment) = self.comment {
88 writeln!(out, "\"{}\"", comment)?;
89 }
90 writeln!(out, "{}", self.root)
91 }
92}
93
94#[derive(Debug)]
96#[non_exhaustive]
97pub enum Error<'a> {
98 Parse(&'a str),
102 Validation(ValidationError),
104}
105
106impl<'a> Display for Error<'a> {
107 fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), FmtError> {
108 match self {
109 Error::Parse(rem) => write!(fmt, "error parsing game at: '{}'", rem),
110 Error::Validation(err) => write!(fmt, "invalid efg: {}", err),
111 }
112 }
113}
114
115impl<'a> StdError for Error<'a> {}
116
117#[derive(Debug, PartialEq, Eq)]
119#[non_exhaustive]
120pub enum ValidationError {
121 ChanceNotDistribution,
123 InvalidPlayerNum,
125 NonMatchingInfosetNames,
127 NonMatchingInfosetActions,
129 NullOutcomePayoffs,
131 InvalidNumberOfPayoffs,
133 NonMatchingOutcomeNames,
135 NonMatchingOutcomePayoffs,
137 NoOutcomePayoffs,
139}
140
141impl Display for ValidationError {
142 fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), FmtError> {
143 write!(fmt, "{:?}", self)
144 }
145}
146
147impl<'a> From<ValidationError> for Error<'a> {
148 fn from(err: ValidationError) -> Self {
149 Error::Validation(err)
150 }
151}
152
153impl<'a> From<nom::Err<nom::error::Error<&'a str>>> for Error<'a> {
154 fn from(err: nom::Err<nom::error::Error<&'a str>>) -> Self {
155 match err {
156 nom::Err::Incomplete(_) => panic!("internal error: incomplete parsing"),
157 nom::Err::Error(err) => Error::Parse(err.input),
158 nom::Err::Failure(err) => Error::Parse(err.input),
159 }
160 }
161}
162
163impl<'a> ExtensiveFormGame<'a> {
164 pub fn try_from_str(input: &'a str) -> Result<Self, Error<'a>> {
168 let (_, game) = all_consuming(efg)(input)?;
169 game.validate()?;
170 Ok(game)
171 }
172
173 fn validate(&self) -> Result<(), ValidationError> {
174 let num_players = self.player_names.len();
175 let mut chance_infosets = HashMap::new();
176 let mut player_infosets = vec![HashMap::new(); num_players].into_boxed_slice();
177 let mut outcomes = HashMap::new();
178 let mut queue = vec![&self.root];
179 while let Some(node) = queue.pop() {
180 match node {
181 Node::Chance(chance) => {
182 let total: BigRational = chance.actions.iter().map(|(_, prob, _)| prob).sum();
183 if total != BigRational::one() {
184 return Err(ValidationError::ChanceNotDistribution);
185 }
186
187 Self::validate_infoset(
188 chance.infoset,
189 chance.infoset_name,
190 chance.actions.iter().map(|(a, p, _)| (a, p)).collect(),
191 &mut chance_infosets,
192 )?;
193
194 self.validate_outcome(
195 chance.outcome,
196 None,
197 chance.outcome_payoffs.as_deref(),
198 &mut outcomes,
199 )?;
200
201 queue.extend(chance.actions.iter().map(|(_, _, next)| next));
202 }
203 Node::Player(player) => {
204 if !(1..=num_players).contains(&player.player_num) {
205 return Err(ValidationError::InvalidPlayerNum);
206 }
207
208 Self::validate_infoset(
209 player.infoset,
210 player.infoset_name,
211 player.actions.iter().map(|(action, _)| action).collect(),
212 &mut player_infosets[player.player_num - 1],
213 )?;
214
215 self.validate_outcome(
216 player.outcome,
217 player.outcome_name,
218 player.outcome_payoffs.as_deref(),
219 &mut outcomes,
220 )?;
221
222 queue.extend(player.actions.iter().map(|(_, next)| next));
223 }
224 Node::Terminal(term) => {
225 self.validate_outcome(
226 term.outcome,
227 term.outcome_name,
228 Some(&term.outcome_payoffs),
229 &mut outcomes,
230 )?;
231 }
232 }
233 }
234
235 for (_, (_, pays)) in outcomes {
236 if pays.is_none() {
237 return Err(ValidationError::NoOutcomePayoffs);
238 }
239 }
240
241 Ok(())
242 }
243
244 fn validate_infoset<T: Eq>(
245 infoset: u64,
246 infoset_name: Option<&'a EscapedStr>,
247 actions: BTreeMultiSet<T>,
248 infosets: &mut HashMap<u64, (Option<&'a EscapedStr>, BTreeMultiSet<T>)>,
249 ) -> Result<(), ValidationError> {
250 match infosets.entry(infoset) {
251 Entry::Vacant(ent) => {
252 ent.insert((infoset_name, actions));
253 }
254 Entry::Occupied(mut ent) => {
255 let (name, acts) = ent.get_mut();
256 match (name, infoset_name) {
257 (Some(old), Some(new)) if old != &new => {
258 return Err(ValidationError::NonMatchingInfosetNames)
259 }
260 (old @ None, Some(new)) => {
261 *old = Some(new);
262 }
263 _ => (),
264 };
265 if acts != &actions {
266 return Err(ValidationError::NonMatchingInfosetActions);
267 }
268 }
269 }
270 Ok(())
271 }
272
273 fn validate_outcome<'b>(
274 &self,
275 outcome: u64,
276 outcome_name: Option<&'a EscapedStr>,
277 outcome_payoffs: Option<&'b [BigRational]>,
278 outcomes: &mut HashMap<u64, (Option<&'a EscapedStr>, Option<&'b [BigRational]>)>,
279 ) -> Result<(), ValidationError> {
280 if outcome == 0 {
281 if outcome_payoffs.is_some() {
282 return Err(ValidationError::NullOutcomePayoffs);
283 }
284 } else {
285 match outcomes.entry(outcome) {
286 Entry::Vacant(ent) => match outcome_payoffs {
287 Some(pays) => {
288 if pays.len() == self.player_names.len() {
289 ent.insert((outcome_name, Some(pays)));
290 } else {
291 return Err(ValidationError::InvalidNumberOfPayoffs);
292 }
293 }
294 None => {
295 ent.insert((outcome_name, None));
296 }
297 },
298 Entry::Occupied(mut ent) => {
299 let (name, payoffs) = ent.get_mut();
300 match (name, outcome_name) {
301 (Some(old), Some(new)) if old != &new => {
302 return Err(ValidationError::NonMatchingOutcomeNames);
303 }
304 (old @ None, Some(new)) => {
305 *old = Some(new);
306 }
307 _ => (),
308 };
309 match (payoffs, outcome_payoffs) {
310 (Some(old), Some(new)) if old != &new => {
311 return Err(ValidationError::NonMatchingOutcomePayoffs);
312 }
313 (old @ None, Some(new)) => {
314 *old = Some(new);
315 }
316 _ => (),
317 }
318 }
319 }
320 }
321 Ok(())
322 }
323}
324
325impl<'a> TryFrom<&'a str> for ExtensiveFormGame<'a> {
326 type Error = Error<'a>;
327
328 fn try_from(input: &'a str) -> Result<Self, Self::Error> {
329 Self::try_from_str(input)
330 }
331}
332
333#[derive(Debug, PartialEq, Clone)]
335pub enum Node<'a> {
336 Chance(Chance<'a>),
338 Player(Player<'a>),
340 Terminal(Terminal<'a>),
342}
343
344impl<'a> Display for Node<'a> {
345 fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), FmtError> {
346 let mut queue = vec![self];
347 while let Some(node) = queue.pop() {
348 match node {
349 Node::Chance(chance) => {
350 queue.extend(chance.actions.iter().rev().map(|(_, _, next)| next));
351 write!(out, "\nc {}", chance)?
352 }
353 Node::Player(player) => {
354 queue.extend(player.actions.iter().rev().map(|(_, next)| next));
355 write!(out, "\np {}", player)?
356 }
357 Node::Terminal(terminal) => write!(out, "\nt {}", terminal)?,
358 }
359 }
360 Ok(())
361 }
362}
363
364#[derive(Debug, PartialEq, Clone)]
369pub struct Chance<'a> {
370 name: &'a EscapedStr,
371 infoset: u64,
372 infoset_name: Option<&'a EscapedStr>,
373 actions: Box<[(&'a EscapedStr, BigRational, Node<'a>)]>,
374 outcome: u64,
375 outcome_payoffs: Option<Box<[BigRational]>>,
376}
377
378impl<'a> Chance<'a> {
379 pub fn name(&self) -> &'a EscapedStr {
381 self.name
382 }
383
384 pub fn infoset(&self) -> u64 {
386 self.infoset
387 }
388
389 pub fn infoset_name(&self) -> Option<&'a EscapedStr> {
394 self.infoset_name
395 }
396
397 pub fn actions(&self) -> &[(&'a EscapedStr, BigRational, Node<'a>)] {
399 &self.actions
400 }
401
402 pub fn outcome(&self) -> u64 {
404 self.outcome
405 }
406
407 pub fn outcome_payoffs(&self) -> Option<&[BigRational]> {
412 self.outcome_payoffs.as_deref()
413 }
414}
415
416impl<'a> Display for Chance<'a> {
417 fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), FmtError> {
418 write!(out, "\"{}\" {}", self.name.as_raw_str(), self.infoset)?;
419 if let Some(name) = self.infoset_name {
420 write!(out, " \"{}\"", name.as_raw_str())?;
421 }
422 write!(out, " {{ ")?;
423 for (action, prob, _) in self.actions.iter() {
424 write!(out, "\"{}\" {} ", action, prob)?;
425 }
426 write!(out, "}} {}", self.outcome)?;
427 if let Some(payoffs) = &self.outcome_payoffs {
428 write!(out, " {{ ")?;
429 for payoff in payoffs.iter() {
430 write!(out, "{} ", payoff)?;
431 }
432 write!(out, "}}")?;
433 }
434 Ok(())
435 }
436}
437
438#[derive(Debug, PartialEq, Clone)]
442pub struct Player<'a> {
443 name: &'a EscapedStr,
444 player_num: usize,
445 infoset: u64,
446 infoset_name: Option<&'a EscapedStr>,
447 actions: Box<[(&'a EscapedStr, Node<'a>)]>,
448 outcome: u64,
449 outcome_name: Option<&'a EscapedStr>,
450 outcome_payoffs: Option<Box<[BigRational]>>,
451}
452
453impl<'a> Player<'a> {
454 pub fn name(&self) -> &'a EscapedStr {
456 self.name
457 }
458
459 pub fn player_num(&self) -> usize {
463 self.player_num
464 }
465
466 pub fn infoset(&self) -> u64 {
468 self.infoset
469 }
470
471 pub fn infoset_name(&self) -> Option<&'a EscapedStr> {
475 self.infoset_name
476 }
477
478 pub fn actions(&self) -> &[(&'a EscapedStr, Node<'a>)] {
480 &self.actions
481 }
482
483 pub fn outcome(&self) -> u64 {
485 self.outcome
486 }
487
488 pub fn outcome_name(&self) -> Option<&'a EscapedStr> {
492 self.outcome_name
493 }
494
495 pub fn outcome_payoffs(&self) -> Option<&[BigRational]> {
499 self.outcome_payoffs.as_deref()
500 }
501}
502
503impl<'a> Display for Player<'a> {
504 fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), FmtError> {
505 write!(
506 out,
507 "\"{}\" {} {}",
508 self.name.as_raw_str(),
509 self.player_num,
510 self.infoset
511 )?;
512 if let Some(name) = self.infoset_name {
513 write!(out, " \"{}\"", name.as_raw_str())?;
514 }
515 write!(out, " {{ ")?;
516 for (action, _) in self.actions.iter() {
517 write!(out, "\"{}\" ", action)?;
518 }
519 write!(out, "}} {}", self.outcome)?;
520 if let Some(name) = self.outcome_name {
521 write!(out, " \"{}\"", name.as_raw_str())?;
522 }
523 if let Some(payoffs) = &self.outcome_payoffs {
524 write!(out, " {{ ")?;
525 for payoff in payoffs.iter() {
526 write!(out, "{} ", payoff)?;
527 }
528 write!(out, "}}")?;
529 }
530 Ok(())
531 }
532}
533
534#[derive(Debug, PartialEq, Eq, Clone)]
538pub struct Terminal<'a> {
539 name: &'a EscapedStr,
540 outcome: u64,
541 outcome_name: Option<&'a EscapedStr>,
542 outcome_payoffs: Box<[BigRational]>,
543}
544
545impl<'a> Terminal<'a> {
546 pub fn name(&self) -> &'a EscapedStr {
548 self.name
549 }
550
551 pub fn outcome(&self) -> u64 {
553 self.outcome
554 }
555
556 pub fn outcome_name(&self) -> Option<&'a EscapedStr> {
560 self.outcome_name
561 }
562
563 pub fn outcome_payoffs(&self) -> &[BigRational] {
565 &self.outcome_payoffs
566 }
567}
568
569impl<'a> Display for Terminal<'a> {
570 fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), FmtError> {
571 write!(out, "\"{}\" {}", self.name.as_raw_str(), self.outcome)?;
572 if let Some(name) = self.outcome_name {
573 write!(out, " \"{}\"", name.as_raw_str())?;
574 }
575 write!(out, " {{ ")?;
576 for payoff in self.outcome_payoffs.iter() {
577 write!(out, "{} ", payoff)?;
578 }
579 write!(out, "}}")
580 }
581}
582
583fn negate(input: &str) -> IResult<&str, bool> {
584 let (input, res) = opt(one_of("+-"))(input)?;
585 Ok((input, res == Some('-')))
586}
587
588fn fail(input: &str) -> nom::Err<nom::error::Error<&str>> {
589 nom::Err::Error(nom::error::Error::new(input, ErrorKind::Fail))
590}
591
592fn big_float(input: &str) -> IResult<&str, BigRational> {
593 let (res_input, (main_neg, (int, dec), exp)) = tuple((
594 negate,
595 alt((
596 pair(
597 digit1,
598 map(opt(preceded(char('.'), digit0)), Option::unwrap_or_default),
599 ),
600 separated_pair(digit0, char('.'), digit1),
601 )),
602 opt(preceded(one_of("eE"), pair(negate, digit1))),
603 ))(input)?;
604 let mut res = if int.is_empty() {
605 BigRational::zero()
606 } else {
607 BigRational::from_integer(int.parse().unwrap())
608 };
609 if !dec.is_empty() {
610 let pow: u32 = dec.len().try_into().map_err(|_| fail(input))?;
611 res += BigRational::new(dec.parse().unwrap(), BigInt::from(10).pow(pow));
612 };
613 if let Some((neg, exp)) = exp {
614 let exp: i32 = exp.parse().map_err(|_| fail(input))?;
615 res *= BigRational::from_integer(10.into()).pow(if neg { -exp } else { exp });
616 };
617 if main_neg {
618 res = -res;
619 };
620 Ok((res_input, res))
621}
622
623fn big_rational(input: &str) -> IResult<&str, BigRational> {
624 let (input, (num, denom)) = pair(big_float, opt(preceded(char('/'), big_float)))(input)?;
625 Ok((
626 input,
627 match denom {
628 Some(denom) => num / denom,
629 None => num,
630 },
631 ))
632}
633
634fn label(input: &str) -> IResult<&str, &EscapedStr> {
635 map(
636 delimited(
637 char('"'),
638 alt((escaped(none_of("\\\""), '\\', one_of("\\\"")), tag(""))),
639 char('"'),
640 ),
641 EscapedStr::new,
642 )(input)
643}
644
645fn spacelist<'a, O, E, F>(f: F) -> impl FnMut(&'a str) -> IResult<&'a str, Vec<O>, E>
646where
647 F: Parser<&'a str, O, E>,
648 E: ParseError<&'a str>,
649{
650 delimited(
651 pair(char('{'), multispace1),
652 separated_list1(multispace1, f),
653 pair(multispace1, char('}')),
654 )
655}
656
657fn commalist<'a, O, E, F>(f: F) -> impl FnMut(&'a str) -> IResult<&'a str, Vec<O>, E>
658where
659 F: Parser<&'a str, O, E>,
660 E: ParseError<&'a str>,
661{
662 delimited(
663 pair(char('{'), multispace1),
664 separated_list1(pair(opt(char(',')), multispace1), f),
665 pair(multispace1, char('}')),
666 )
667}
668
669fn node(input: &str) -> IResult<&str, Node<'_>> {
670 let (input, style) = preceded(multispace1, one_of("cpt"))(input)?;
671 match style {
672 'c' => {
673 let (input, chance) = chance(input)?;
674 Ok((input, Node::Chance(chance)))
675 }
676 'p' => {
677 let (input, play) = player(input)?;
678 Ok((input, Node::Player(play)))
679 }
680 't' => {
681 let (input, term) = terminal(input)?;
682 Ok((input, Node::Terminal(term)))
683 }
684 _ => panic!(),
685 }
686}
687
688fn chance(input: &str) -> IResult<&str, Chance<'_>> {
689 let (mut input, (name, infoset, infoset_name, action_probs, outcome, outcome_payoffs)) =
690 tuple((
691 preceded(multispace1, label),
692 preceded(multispace1, u64),
693 opt(preceded(multispace1, label)),
694 preceded(
695 multispace1,
696 spacelist(separated_pair(label, multispace1, big_rational)),
697 ),
698 preceded(multispace1, u64),
699 opt(preceded(multispace1, commalist(big_rational))),
700 ))(input)?;
701 let mut actions = Vec::with_capacity(action_probs.len());
702 for (name, prob) in action_probs {
703 let (next_inp, next) = node(input)?;
704 input = next_inp;
705 actions.push((name, prob, next));
706 }
707 Ok((
708 input,
709 Chance {
710 name,
711 infoset,
712 infoset_name,
713 actions: actions.into(),
714 outcome,
715 outcome_payoffs: outcome_payoffs.map(|p| p.into()),
716 },
717 ))
718}
719
720fn player(input: &str) -> IResult<&str, Player<'_>> {
721 let (
722 mut res_input,
723 (
724 name,
725 player_num,
726 infoset,
727 infoset_name,
728 action_names,
729 outcome,
730 outcome_name,
731 outcome_payoffs,
732 ),
733 ) = tuple((
734 preceded(multispace1, label),
735 preceded(multispace1, u64),
736 preceded(multispace1, u64),
737 opt(preceded(multispace1, label)),
738 preceded(multispace1, spacelist(label)),
739 preceded(multispace1, u64),
740 opt(preceded(multispace1, label)),
741 opt(preceded(multispace1, commalist(big_rational))),
742 ))(input)?;
743 let player_num = player_num.try_into().map_err(|_| fail(input))?;
744 let mut actions = Vec::with_capacity(action_names.len());
745 for name in action_names {
746 let (next_inp, next) = node(res_input)?;
747 res_input = next_inp;
748 actions.push((name, next));
749 }
750 Ok((
751 res_input,
752 Player {
753 name,
754 player_num,
755 infoset,
756 infoset_name,
757 actions: actions.into(),
758 outcome,
759 outcome_name,
760 outcome_payoffs: outcome_payoffs.map(|p| p.into()),
761 },
762 ))
763}
764
765fn terminal(input: &str) -> IResult<&str, Terminal<'_>> {
766 let (input, (name, outcome, outcome_name, payoffs)) = tuple((
767 preceded(multispace1, label),
768 preceded(multispace1, u64),
769 opt(preceded(multispace1, label)),
770 preceded(multispace1, commalist(big_rational)),
771 ))(input)?;
772 Ok((
773 input,
774 Terminal {
775 name,
776 outcome,
777 outcome_name,
778 outcome_payoffs: payoffs.into(),
779 },
780 ))
781}
782
783fn efg(input: &str) -> IResult<&str, ExtensiveFormGame<'_>> {
784 let (input, (name, player_names, comment, root)) = tuple((
785 preceded(
786 tuple((
787 multispace0,
788 tag("EFG"),
789 multispace1,
790 tag("2"),
791 multispace1,
792 tag("R"),
793 multispace1,
794 )),
795 label,
796 ),
797 preceded(multispace1, spacelist(label)),
798 opt(preceded(multispace1, label)),
799 terminated(node, multispace0),
800 ))(input)?;
801 Ok((
802 input,
803 ExtensiveFormGame {
804 name,
805 player_names: player_names.into(),
806 comment,
807 root,
808 },
809 ))
810}
811
812#[cfg(test)]
813mod tests {
814 use super::{Chance, EscapedStr, ExtensiveFormGame, Node, Player, Terminal, ValidationError};
815 use num::rational::BigRational;
816 use num::{One, Zero};
817
818 fn new_game<'a>(
819 name: &'a str,
820 player_names: impl IntoIterator<Item = &'a str>,
821 comment: Option<&'a str>,
822 root: Node<'a>,
823 ) -> ExtensiveFormGame<'a> {
824 ExtensiveFormGame {
825 name: EscapedStr::new(name),
826 player_names: player_names.into_iter().map(EscapedStr::new).collect(),
827 comment: comment.map(EscapedStr::new),
828 root,
829 }
830 }
831
832 fn new_chance<'a>(
833 name: &'a str,
834 infoset: u64,
835 infoset_name: Option<&'a str>,
836 actions: impl IntoIterator<Item = (&'a str, BigRational, Node<'a>)>,
837 outcome: u64,
838 outcome_payoffs: Option<Box<[BigRational]>>,
839 ) -> Chance<'a> {
840 Chance {
841 name: EscapedStr::new(name),
842 infoset,
843 infoset_name: infoset_name.map(EscapedStr::new),
844 actions: actions
845 .into_iter()
846 .map(|(name, prob, node)| (EscapedStr::new(name), prob, node))
847 .collect(),
848 outcome,
849 outcome_payoffs,
850 }
851 }
852
853 fn new_player<'a>(
854 name: &'a str,
855 player_num: usize,
856 infoset: u64,
857 infoset_name: Option<&'a str>,
858 actions: impl IntoIterator<Item = (&'a str, Node<'a>)>,
859 outcome: u64,
860 outcome_name: Option<&'a str>,
861 outcome_payoffs: Option<Box<[BigRational]>>,
862 ) -> Player<'a> {
863 Player {
864 name: EscapedStr::new(name),
865 player_num,
866 infoset,
867 infoset_name: infoset_name.map(EscapedStr::new),
868 actions: actions
869 .into_iter()
870 .map(|(name, node)| (EscapedStr::new(name), node))
871 .collect(),
872 outcome,
873 outcome_name: outcome_name.map(EscapedStr::new),
874 outcome_payoffs,
875 }
876 }
877
878 fn new_terminal<'a>(
879 name: &'a str,
880 outcome: u64,
881 outcome_name: Option<&'a str>,
882 outcome_payoffs: impl Into<Box<[BigRational]>>,
883 ) -> Terminal<'a> {
884 Terminal {
885 name: EscapedStr::new(name),
886 outcome,
887 outcome_name: outcome_name.map(EscapedStr::new),
888 outcome_payoffs: outcome_payoffs.into(),
889 }
890 }
891
892 #[test]
893 fn test_big_float() {
894 let (input, num) = super::big_float("3 ").unwrap();
895 assert_eq!(input, " ");
896 assert_eq!(num, BigRational::from_integer(3.into()));
897
898 let (input, num) = super::big_float("-2. ").unwrap();
899 assert_eq!(input, " ");
900 assert_eq!(num, BigRational::from_integer((-2).into()));
901
902 let (input, num) = super::big_float("+.56 ").unwrap();
903 assert_eq!(input, " ");
904 assert_eq!(num, BigRational::new(56.into(), 100.into()));
905
906 let (input, num) = super::big_float("3.14e-1 ").unwrap();
907 assert_eq!(input, " ");
908 assert_eq!(num, BigRational::new(314.into(), 1000.into()));
909 }
910
911 #[test]
912 fn test_big_rational() {
913 let (input, num) = super::big_rational("3 ").unwrap();
914 assert_eq!(input, " ");
915 assert_eq!(num, BigRational::from_integer(3.into()));
916
917 let (input, num) = super::big_rational("99/100 ").unwrap();
918 assert_eq!(input, " ");
919 assert_eq!(num, BigRational::new(99.into(), 100.into()));
920
921 let (input, num) = super::big_rational(".1e3/+1.e2 ").unwrap();
922 assert_eq!(input, " ");
923 assert_eq!(num, BigRational::one());
924 }
925
926 #[test]
927 fn test_label() {
928 let (input, label) = super::label(r#""" "#).unwrap();
929 assert_eq!(input, " ");
930 assert_eq!(label.as_raw_str(), "");
931
932 let (input, label) = super::label(r#""normal" "#).unwrap();
933 assert_eq!(input, " ");
934 assert_eq!(label.as_raw_str(), "normal");
935
936 let (input, label) = super::label(r#""esca\"ped" "#).unwrap();
937 assert_eq!(input, " ");
938 assert_eq!(label.as_raw_str(), "esca\\\"ped");
939 }
940
941 #[test]
942 fn test_terminal() {
943 let (input, term) =
944 super::terminal(r#" "name" 1 "outcome name" { 10.000000 2.000000 }"#).unwrap();
945 let expected = new_terminal(
946 "name",
947 1,
948 Some("outcome name"),
949 [
950 BigRational::from_integer(10.into()),
951 BigRational::from_integer(2.into()),
952 ],
953 );
954 assert_eq!(input, "");
955 assert_eq!(term, expected);
956 assert_eq!(expected.to_string(), r#""name" 1 "outcome name" { 10 2 }"#);
957
958 let (input, term) = super::terminal(r#" "" 2 { -1, 4/6 }"#).unwrap();
959 let expected = new_terminal(
960 "",
961 2,
962 None,
963 [
964 BigRational::from_integer((-1).into()),
965 BigRational::new(4.into(), 6.into()),
966 ],
967 );
968 assert_eq!(input, "");
969 assert_eq!(term, expected);
970 assert_eq!(expected.to_string(), r#""" 2 { -1 2/3 }"#);
971 }
972
973 #[test]
974 fn test_player() {
975 let (input, player) = super::player(
976 r#" "name" 1 2 "infoset name" { "action" } 0
977 t "" 1 { 10 }"#,
978 )
979 .unwrap();
980 let expected = new_player(
981 "name",
982 1,
983 2,
984 Some("infoset name"),
985 [(
986 "action",
987 Node::Terminal(new_terminal(
988 "",
989 1,
990 None,
991 [BigRational::from_integer(10.into())],
992 )),
993 )],
994 0,
995 None,
996 None,
997 );
998 assert_eq!(input, "");
999 assert_eq!(player, expected);
1000 assert_eq!(
1001 expected.to_string(),
1002 r#""name" 1 2 "infoset name" { "action" } 0"#
1003 );
1004
1005 let (input, player) = super::player(
1006 r#" "" 2 3 { "" } 1 "outcome name" { -4.5, 6.7 }
1007 t "" 1 { 10 }"#,
1008 )
1009 .unwrap();
1010 let expected = new_player(
1011 "",
1012 2,
1013 3,
1014 None,
1015 [(
1016 "",
1017 Node::Terminal(new_terminal(
1018 "",
1019 1,
1020 None,
1021 [BigRational::from_integer(10.into())],
1022 )),
1023 )],
1024 1,
1025 Some("outcome name"),
1026 Some(
1027 [
1028 BigRational::new((-45).into(), 10.into()),
1029 BigRational::new(67.into(), 10.into()),
1030 ]
1031 .into(),
1032 ),
1033 );
1034 assert_eq!(input, "");
1035 assert_eq!(player, expected);
1036 assert_eq!(
1037 expected.to_string(),
1038 r#""" 2 3 { "" } 1 "outcome name" { -9/2 67/10 }"#
1039 );
1040 }
1041
1042 #[test]
1043 fn test_chance() {
1044 let (input, chance) = super::chance(
1045 r#" "name" 1 "infoset name" { "action" 1/2 } 0
1046 t "" 1 { 10 }"#,
1047 )
1048 .unwrap();
1049 let expected = new_chance(
1050 "name",
1051 1,
1052 Some("infoset name"),
1053 [(
1054 "action",
1055 BigRational::new(1.into(), 2.into()),
1056 Node::Terminal(new_terminal(
1057 "",
1058 1,
1059 None,
1060 [BigRational::from_integer(10.into())],
1061 )),
1062 )],
1063 0,
1064 None,
1065 );
1066 assert_eq!(input, "");
1067 assert_eq!(chance, expected);
1068 assert_eq!(
1069 expected.to_string(),
1070 r#""name" 1 "infoset name" { "action" 1/2 } 0"#
1071 );
1072
1073 let (input, chance) = super::chance(
1074 r#" "" 2 { "" 1 } 4 { 0.1, 4 }
1075 t "" 1 { 10 }"#,
1076 )
1077 .unwrap();
1078 let expected = new_chance(
1079 "",
1080 2,
1081 None,
1082 [(
1083 "",
1084 BigRational::from_integer(1.into()),
1085 Node::Terminal(new_terminal(
1086 "",
1087 1,
1088 None,
1089 [BigRational::from_integer(10.into())],
1090 )),
1091 )],
1092 4,
1093 Some(
1094 [
1095 BigRational::new(1.into(), 10.into()),
1096 BigRational::from_integer(4.into()),
1097 ]
1098 .into(),
1099 ),
1100 );
1101 assert_eq!(input, "");
1102 assert_eq!(chance, expected);
1103 assert_eq!(expected.to_string(), r#""" 2 { "" 1 } 4 { 1/10 4 }"#);
1104 }
1105
1106 #[test]
1107 fn simple_test() {
1108 let game_str = r#"
1109 EFG 2 R "General Bayes game, one stage" { "Player 1" "Player 2" }
1110 "A single stage General Bayes Game"
1111
1112 c "ROOT" 1 "(0,1)" { "1G" 0.500000 "1B" 0.500000 } 0
1113 p "" 1 1 "(1,1)" { "H" "L" } 0
1114 t "" 1 "Outcome 1" { 10.000000 2.000000 }
1115 t "" 2 "Outcome 2" { 0.000000 10.000000 }
1116 p "" 2 1 "(2,1)" { "h" "l" } 0
1117 t "" 3 "Outcome 3" { 2.000000 4.000000 }
1118 t "" 4 "Outcome 4" { 4.000000 0.000000 }
1119 "#;
1120 let (input, efg) = super::efg(game_str).unwrap();
1121 let expected = new_game(
1122 "General Bayes game, one stage",
1123 ["Player 1", "Player 2"],
1124 Some("A single stage General Bayes Game"),
1125 Node::Chance(new_chance(
1126 "ROOT",
1127 1,
1128 Some("(0,1)"),
1129 [
1130 (
1131 "1G",
1132 BigRational::new(1.into(), 2.into()),
1133 Node::Player(new_player(
1134 "",
1135 1,
1136 1,
1137 Some("(1,1)"),
1138 [
1139 (
1140 "H",
1141 Node::Terminal(new_terminal(
1142 "",
1143 1,
1144 Some("Outcome 1"),
1145 [
1146 BigRational::from_integer(10.into()),
1147 BigRational::from_integer(2.into()),
1148 ],
1149 )),
1150 ),
1151 (
1152 "L",
1153 Node::Terminal(new_terminal(
1154 "",
1155 2,
1156 Some("Outcome 2"),
1157 [
1158 BigRational::from_integer(0.into()),
1159 BigRational::from_integer(10.into()),
1160 ],
1161 )),
1162 ),
1163 ],
1164 0,
1165 None,
1166 None,
1167 )),
1168 ),
1169 (
1170 "1B",
1171 BigRational::new(1.into(), 2.into()),
1172 Node::Player(new_player(
1173 "",
1174 2,
1175 1,
1176 Some("(2,1)"),
1177 [
1178 (
1179 "h",
1180 Node::Terminal(new_terminal(
1181 "",
1182 3,
1183 Some("Outcome 3"),
1184 [
1185 BigRational::from_integer(2.into()),
1186 BigRational::from_integer(4.into()),
1187 ],
1188 )),
1189 ),
1190 (
1191 "l",
1192 Node::Terminal(new_terminal(
1193 "",
1194 4,
1195 Some("Outcome 4"),
1196 [
1197 BigRational::from_integer(4.into()),
1198 BigRational::from_integer(0.into()),
1199 ],
1200 )),
1201 ),
1202 ],
1203 0,
1204 None,
1205 None,
1206 )),
1207 ),
1208 ],
1209 0,
1210 None,
1211 )),
1212 );
1213 assert_eq!(input, "");
1214 assert_eq!(efg, expected);
1215 assert_eq!(
1216 expected.to_string(),
1217 r#"EFG 2 R "General Bayes game, one stage" { "Player 1" "Player 2" }
1218"A single stage General Bayes Game"
1219
1220c "ROOT" 1 "(0,1)" { "1G" 1/2 "1B" 1/2 } 0
1221p "" 1 1 "(1,1)" { "H" "L" } 0
1222t "" 1 "Outcome 1" { 10 2 }
1223t "" 2 "Outcome 2" { 0 10 }
1224p "" 2 1 "(2,1)" { "h" "l" } 0
1225t "" 3 "Outcome 3" { 2 4 }
1226t "" 4 "Outcome 4" { 4 0 }
1227"#
1228 );
1229 }
1230
1231 fn empty_game<'a>(
1232 player_names: impl IntoIterator<Item = &'a str>,
1233 root: Node<'a>,
1234 ) -> ExtensiveFormGame<'a> {
1235 ExtensiveFormGame {
1236 name: EscapedStr::new(""),
1237 player_names: player_names.into_iter().map(EscapedStr::new).collect(),
1238 comment: None,
1239 root,
1240 }
1241 }
1242
1243 #[test]
1244 fn not_distribution() {
1245 let game = empty_game(
1246 ["1", "2"],
1247 Node::Chance(new_chance(
1248 "",
1249 1,
1250 None,
1251 [(
1252 "",
1253 BigRational::new(9.into(), 10.into()),
1254 Node::Terminal(new_terminal("", 1, None, vec![BigRational::zero(); 2])),
1255 )],
1256 0,
1257 None,
1258 )),
1259 );
1260 assert_eq!(
1261 game.validate().unwrap_err(),
1262 ValidationError::ChanceNotDistribution
1263 );
1264 }
1265
1266 #[test]
1267 fn invalid_player_num() {
1268 let game = empty_game(
1269 ["1", "2"],
1270 Node::Player(new_player("", 3, 1, None, [], 0, None, None)),
1271 );
1272 assert_eq!(
1273 game.validate().unwrap_err(),
1274 ValidationError::InvalidPlayerNum
1275 );
1276 }
1277
1278 #[test]
1279 fn invalid_infoset_names() {
1280 let game = empty_game(
1281 ["1", "2"],
1282 Node::Player(new_player(
1283 "",
1284 1,
1285 1,
1286 Some("a"),
1287 [(
1288 "",
1289 Node::Player(new_player("", 1, 1, Some("b"), [], 0, None, None)),
1290 )],
1291 0,
1292 None,
1293 None,
1294 )),
1295 );
1296 assert_eq!(
1297 game.validate().unwrap_err(),
1298 ValidationError::NonMatchingInfosetNames
1299 );
1300 }
1301
1302 #[test]
1303 fn invalid_chance_infoset_names() {
1304 let game = empty_game(
1305 ["1", "2"],
1306 Node::Chance(new_chance(
1307 "",
1308 1,
1309 Some("a"),
1310 [(
1311 "",
1312 BigRational::one(),
1313 Node::Chance(new_chance(
1314 "",
1315 1,
1316 Some("b"),
1317 [(
1318 "",
1319 BigRational::one(),
1320 Node::Terminal(new_terminal("", 1, None, vec![BigRational::zero(); 2])),
1321 )],
1322 0,
1323 None,
1324 )),
1325 )],
1326 0,
1327 None,
1328 )),
1329 );
1330 assert_eq!(
1331 game.validate().unwrap_err(),
1332 ValidationError::NonMatchingInfosetNames
1333 );
1334 }
1335
1336 #[test]
1337 fn invalid_infoset_actions() {
1338 let game = empty_game(
1339 ["1", "2"],
1340 Node::Player(new_player(
1341 "",
1342 1,
1343 1,
1344 None,
1345 [(
1346 "",
1347 Node::Player(new_player("", 1, 1, None, [], 0, None, None)),
1348 )],
1349 0,
1350 None,
1351 None,
1352 )),
1353 );
1354 assert_eq!(
1355 game.validate().unwrap_err(),
1356 ValidationError::NonMatchingInfosetActions
1357 );
1358 }
1359
1360 #[test]
1361 fn invalid_chance_infoset_actions() {
1362 let game = empty_game(
1363 ["1", "2"],
1364 Node::Chance(new_chance(
1365 "",
1366 1,
1367 None,
1368 [(
1369 "",
1370 BigRational::one(),
1371 Node::Chance(new_chance(
1372 "",
1373 1,
1374 None,
1375 [(
1376 "a",
1377 BigRational::one(),
1378 Node::Terminal(new_terminal("", 0, None, vec![BigRational::zero(); 2])),
1379 )],
1380 0,
1381 None,
1382 )),
1383 )],
1384 0,
1385 None,
1386 )),
1387 );
1388 assert_eq!(
1389 game.validate().unwrap_err(),
1390 ValidationError::NonMatchingInfosetActions
1391 );
1392 }
1393
1394 #[test]
1395 fn null_outcome_payoffs() {
1396 let game = empty_game(
1397 ["1", "2"],
1398 Node::Player(new_player(
1399 "",
1400 1,
1401 1,
1402 None,
1403 [],
1404 0,
1405 None,
1406 Some(vec![BigRational::zero(); 2].into()),
1407 )),
1408 );
1409 assert_eq!(
1410 game.validate().unwrap_err(),
1411 ValidationError::NullOutcomePayoffs
1412 );
1413 }
1414
1415 #[test]
1416 fn invalid_payoff_number() {
1417 let game = empty_game(
1418 ["1", "2"],
1419 Node::Terminal(new_terminal("", 1, None, [BigRational::zero()])),
1420 );
1421 assert_eq!(
1422 game.validate().unwrap_err(),
1423 ValidationError::InvalidNumberOfPayoffs
1424 );
1425 }
1426
1427 #[test]
1428 fn non_matching_outcome_names() {
1429 let game = empty_game(
1430 ["1", "2"],
1431 Node::Player(new_player(
1432 "",
1433 1,
1434 1,
1435 None,
1436 [(
1437 "",
1438 Node::Player(new_player("", 1, 2, None, [], 1, Some("a"), None)),
1439 )],
1440 1,
1441 Some("b"),
1442 None,
1443 )),
1444 );
1445 assert_eq!(
1446 game.validate().unwrap_err(),
1447 ValidationError::NonMatchingOutcomeNames
1448 );
1449 }
1450
1451 #[test]
1452 fn non_matching_outcome_payoffs() {
1453 let game = empty_game(
1454 ["1", "2"],
1455 Node::Player(new_player(
1456 "",
1457 1,
1458 1,
1459 None,
1460 [(
1461 "",
1462 Node::Player(new_player(
1463 "",
1464 1,
1465 2,
1466 None,
1467 [],
1468 1,
1469 None,
1470 Some(vec![BigRational::one(); 2].into()),
1471 )),
1472 )],
1473 1,
1474 None,
1475 Some(vec![BigRational::zero(); 2].into()),
1476 )),
1477 );
1478 assert_eq!(
1479 game.validate().unwrap_err(),
1480 ValidationError::NonMatchingOutcomePayoffs
1481 );
1482 }
1483
1484 #[test]
1485 fn no_outcome_payoffs() {
1486 let game = empty_game(
1487 ["1", "2"],
1488 Node::Player(new_player("", 1, 1, None, [], 1, None, None)),
1489 );
1490 assert_eq!(
1491 game.validate().unwrap_err(),
1492 ValidationError::NoOutcomePayoffs
1493 );
1494 }
1495}