use std::fmt;
pub type NodeId = usize;
pub type PlayerId = usize;
pub type Payoff = f64;
#[derive(Clone, Debug, PartialEq)]
pub enum NodeType {
Decision { player: PlayerId },
Chance { probabilities: Vec<f64> },
Terminal { payoffs: Vec<Payoff> },
}
#[derive(Clone, Debug)]
pub struct NodeData {
pub id: NodeId,
pub node_type: NodeType,
pub label: String,
pub actions: Vec<String>,
pub children: Vec<NodeId>,
pub parent: Option<NodeId>,
pub incoming_action: Option<String>,
}
impl NodeData {
pub fn decision(id: NodeId, player: PlayerId, label: &str) -> Self {
Self {
id,
node_type: NodeType::Decision { player },
label: label.to_string(),
actions: Vec::new(),
children: Vec::new(),
parent: None,
incoming_action: None,
}
}
pub fn chance(id: NodeId, probabilities: Vec<f64>, label: &str) -> Self {
Self {
id,
node_type: NodeType::Chance { probabilities },
label: label.to_string(),
actions: Vec::new(),
children: Vec::new(),
parent: None,
incoming_action: None,
}
}
pub fn terminal(id: NodeId, payoffs: Vec<Payoff>, label: &str) -> Self {
Self {
id,
node_type: NodeType::Terminal { payoffs },
label: label.to_string(),
actions: Vec::new(),
children: Vec::new(),
parent: None,
incoming_action: None,
}
}
pub fn is_terminal(&self) -> bool {
matches!(self.node_type, NodeType::Terminal { .. })
}
pub fn is_decision(&self) -> bool {
matches!(self.node_type, NodeType::Decision { .. })
}
pub fn player(&self) -> PlayerId {
match &self.node_type {
NodeType::Decision { player } => *player,
_ => panic!("Not a decision node"),
}
}
pub fn payoffs(&self) -> &[Payoff] {
match &self.node_type {
NodeType::Terminal { payoffs } => payoffs,
_ => panic!("Not a terminal node"),
}
}
pub fn num_actions(&self) -> usize {
self.actions.len()
}
}
impl fmt::Display for NodeData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.node_type {
NodeType::Decision { player } => {
write!(f, "Node {} [P{}: {}]", self.id, player, self.label)
}
NodeType::Chance { .. } => {
write!(f, "Node {} [Chance: {}]", self.id, self.label)
}
NodeType::Terminal { payoffs } => {
write!(f, "Node {} [Terminal: {:?}]", self.id, payoffs)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decision_node() {
let n = NodeData::decision(0, 0, "Root");
assert!(n.is_decision());
assert!(!n.is_terminal());
assert_eq!(n.player(), 0);
}
#[test]
fn test_terminal_node() {
let n = NodeData::terminal(3, vec![2.0, 1.0], "End");
assert!(n.is_terminal());
assert_eq!(n.payoffs(), &[2.0, 1.0]);
}
#[test]
fn test_chance_node() {
let n = NodeData::chance(1, vec![0.5, 0.5], "Nature");
assert!(matches!(n.node_type, NodeType::Chance { .. }));
}
#[test]
fn test_node_display() {
let n = NodeData::decision(0, 1, "Choose");
let s = format!("{}", n);
assert!(s.contains("P1"));
}
}