#[cfg(doc)]
use crate::GameTree;
use rayon::ThreadPoolBuildError;
use std::error::Error;
use std::fmt::{Display, Error as FmtError, Formatter};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum GameError {
EmptyChance,
NonPositiveChance,
ProbabilitiesNotEqual,
ImperfectRecall,
EmptyPlayer,
ActionsNotEqual,
ActionsNotUnique,
TooManyInfosets,
}
impl Display for GameError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), FmtError> {
write!(fmt, "{self:?}")
}
}
impl Error for GameError {}
#[cfg(test)]
mod tree {
use crate::{Game, Moves, NodeType, Outcomes, PlayerNum};
use std::rc::Rc;
pub(super) type Tree = Rc<Node>;
#[derive(Debug)]
pub(super) enum Node {
Terminal(f64),
Chance(Option<&'static str>, Vec<(f64, Tree)>),
Player(PlayerNum, &'static str, Vec<(&'static str, Tree)>),
}
pub(super) fn terminal(payoff: f64) -> Tree {
Rc::new(Node::Terminal(payoff))
}
pub(super) fn chance(infoset: Option<&'static str>, outcomes: Vec<(f64, Tree)>) -> Tree {
Rc::new(Node::Chance(infoset, outcomes))
}
pub(super) fn player(
num: PlayerNum,
infoset: &'static str,
actions: Vec<(&'static str, Tree)>,
) -> Tree {
Rc::new(Node::Player(num, infoset, actions))
}
pub(super) struct TreeOutcomes(Vec<(f64, Tree)>);
pub(super) struct TreeMoves(Vec<(&'static str, Tree)>);
impl Game for Tree {
type Action = &'static str;
type Infoset = &'static str;
type ChanceInfoset = &'static str;
type Chance = TreeOutcomes;
type Player = TreeMoves;
fn into_node(self) -> NodeType<Self> {
match &*self {
Node::Terminal(payoff) => NodeType::Terminal(*payoff),
Node::Chance(infoset, outcomes) => {
NodeType::Chance(*infoset, TreeOutcomes(outcomes.clone()))
}
Node::Player(num, infoset, actions) => {
NodeType::Player(*num, *infoset, TreeMoves(actions.clone()))
}
}
}
}
impl Outcomes<Tree> for TreeOutcomes {
fn len(&self) -> usize {
self.0.len()
}
fn get(&self, index: usize) -> (f64, Tree) {
self.0[index].clone()
}
}
impl Moves<Tree> for TreeMoves {
fn len(&self) -> usize {
self.0.len()
}
fn action(&self, index: usize) -> &'static str {
self.0[index].0
}
fn apply(&self, index: usize) -> Tree {
self.0[index].1.clone()
}
}
}
#[cfg(test)]
mod game_errors {
use super::tree::{Tree, chance, player, terminal};
use crate::{GameError, GameTree, PlayerNum};
#[test]
fn empty_chance() {
let err_game = chance(None, vec![]);
let err = GameTree::from_game(err_game).unwrap_err();
assert_eq!(err, GameError::EmptyChance);
}
#[test]
fn non_positive_chance() {
let err_game = chance(None, vec![(0.0, terminal(0.0))]);
let err = GameTree::from_game(err_game).unwrap_err();
assert_eq!(err, GameError::NonPositiveChance);
}
#[test]
fn probabilities_not_equal() {
let err_game = chance(
None,
vec![
(
0.5,
chance(
Some("x"),
vec![(1.0, terminal(0.0)), (1.0, terminal(0.0))],
),
),
(
0.5,
chance(
Some("x"),
vec![(1.0, terminal(0.0)), (2.0, terminal(0.0))],
),
),
],
);
let err = GameTree::from_game(err_game).unwrap_err();
assert_eq!(err, GameError::ProbabilitiesNotEqual);
}
#[test]
fn imperfect_recall() {
let err_game = chance(
None,
vec![
(
0.5,
player(
PlayerNum::One,
"x",
vec![("a", terminal(0.0)), ("b", terminal(0.0))],
),
),
(
0.5,
player(
PlayerNum::One,
"y",
vec![
(
"a",
player(
PlayerNum::One,
"x",
vec![
("a", terminal(0.0)),
("b", terminal(0.0)),
],
),
),
("b", terminal(0.0)),
],
),
),
],
);
let err = GameTree::from_game(err_game).unwrap_err();
assert_eq!(err, GameError::ImperfectRecall);
}
#[test]
fn imperfect_recall_forgotten_action() {
fn branch() -> Tree {
player(
PlayerNum::One,
"x",
vec![("c", terminal(0.0)), ("d", terminal(0.0))],
)
}
let err_game = player(PlayerNum::One, "p", vec![("a", branch()), ("b", branch())]);
let err = GameTree::from_game(err_game).unwrap_err();
assert_eq!(err, GameError::ImperfectRecall);
}
#[test]
fn empty_player() {
let err_game = player(PlayerNum::One, "", vec![]);
let err = GameTree::from_game(err_game).unwrap_err();
assert_eq!(err, GameError::EmptyPlayer);
}
#[test]
fn actions_not_equal() {
let ord_game = chance(
None,
vec![
(
0.5,
player(
PlayerNum::One,
"x",
vec![("a", terminal(0.0)), ("b", terminal(0.0))],
),
),
(
0.5,
player(
PlayerNum::One,
"x",
vec![("b", terminal(0.0)), ("a", terminal(0.0))],
),
),
],
);
let err = GameTree::from_game(ord_game).unwrap_err();
assert_eq!(err, GameError::ActionsNotEqual);
}
#[test]
fn actions_not_equal_single_vs_multi() {
let mixed_game = chance(
None,
vec![
(0.5, player(PlayerNum::One, "x", vec![("a", terminal(0.0))])),
(
0.5,
player(
PlayerNum::One,
"x",
vec![("a", terminal(0.0)), ("b", terminal(0.0))],
),
),
],
);
let err = GameTree::from_game(mixed_game).unwrap_err();
assert_eq!(err, GameError::ActionsNotEqual);
}
#[test]
fn actions_not_unique() {
let dup_game = player(
PlayerNum::One,
"x",
vec![("a", terminal(0.0)), ("a", terminal(0.0))],
);
let err = GameTree::from_game(dup_game).unwrap_err();
assert_eq!(err, GameError::ActionsNotUnique);
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum StratError {
InvalidInfoset,
InvalidAction,
InvalidProbability,
UninitializedInfoset,
}
impl Display for StratError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), FmtError> {
write!(fmt, "{self:?}")
}
}
impl Error for StratError {}
#[cfg(test)]
mod strat_errors {
use super::tree::{player, terminal};
use crate::{GameTree, PlayerNum, StratError};
fn create_game() -> GameTree<&'static str, &'static str> {
let node = player(
PlayerNum::One,
"x",
vec![(
"a",
player(
PlayerNum::Two,
"z",
vec![
(
"b",
player(
PlayerNum::One,
"y",
vec![
("c", terminal(0.0)),
("d", terminal(0.0)),
],
),
),
("c", terminal(0.0)),
],
),
)],
);
GameTree::from_game(node).unwrap()
}
#[test]
fn invalid_infoset() {
let game = create_game();
let err = game
.from_named([vec![("a", vec![("b", 1.0)])], vec![]])
.unwrap_err();
assert_eq!(err, StratError::InvalidInfoset);
let err = game
.from_named_eq([vec![("a", vec![("b", 1.0)])], vec![]])
.unwrap_err();
assert_eq!(err, StratError::InvalidInfoset);
}
#[test]
fn invalid_infoset_action() {
let game = create_game();
let err = game
.from_named([vec![("x", vec![("b", 1.0)])], vec![]])
.unwrap_err();
assert_eq!(err, StratError::InvalidAction);
let err = game
.from_named_eq([vec![("x", vec![("b", 1.0)])], vec![]])
.unwrap_err();
assert_eq!(err, StratError::InvalidAction);
}
#[test]
fn invalid_probability() {
let game = create_game();
let err = game
.from_named([vec![("x", vec![("a", -1.0)])], vec![]])
.unwrap_err();
assert_eq!(err, StratError::InvalidProbability);
let err = game
.from_named_eq([vec![("x", vec![("a", -1.0)])], vec![]])
.unwrap_err();
assert_eq!(err, StratError::InvalidProbability);
}
#[test]
fn invalid_infoset_probability_normal() {
let game = create_game();
let err = game
.from_named([vec![("x", vec![("a", 1.0)])], vec![("z", vec![("c", 1.0)])]])
.unwrap_err();
assert_eq!(err, StratError::UninitializedInfoset);
let err = game
.from_named_eq([vec![("x", vec![("a", 1.0)])], vec![("z", vec![("c", 1.0)])]])
.unwrap_err();
assert_eq!(err, StratError::UninitializedInfoset);
}
#[test]
fn invalid_infoset_probability_single() {
let game = create_game();
let err = game
.from_named([vec![("y", vec![("d", 1.0)])], vec![("z", vec![("c", 1.0)])]])
.unwrap_err();
assert_eq!(err, StratError::UninitializedInfoset);
let err = game
.from_named_eq([vec![("y", vec![("d", 1.0)])], vec![("z", vec![("c", 1.0)])]])
.unwrap_err();
assert_eq!(err, StratError::UninitializedInfoset);
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum SolveError {
ThreadSpawnError,
}
impl Display for SolveError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), FmtError> {
write!(fmt, "{self:?}")
}
}
impl Error for SolveError {}
impl From<ThreadPoolBuildError> for SolveError {
fn from(_: ThreadPoolBuildError) -> Self {
SolveError::ThreadSpawnError
}
}