1use crate::node::{NodeData, NodeId, Payoff, PlayerId};
4use std::collections::HashMap;
5
6#[derive(Clone, Debug)]
8pub struct GameTree {
9 pub name: String,
11 pub nodes: HashMap<NodeId, NodeData>,
13 pub root: Option<NodeId>,
15 next_id: NodeId,
17 pub num_players: usize,
19}
20
21impl GameTree {
22 pub fn new(name: &str) -> Self {
24 Self {
25 name: name.to_string(),
26 nodes: HashMap::new(),
27 root: None,
28 next_id: 0,
29 num_players: 2,
30 }
31 }
32
33 pub fn with_players(name: &str, num_players: usize) -> Self {
35 Self {
36 name: name.to_string(),
37 nodes: HashMap::new(),
38 root: None,
39 next_id: 0,
40 num_players,
41 }
42 }
43
44 fn alloc_id(&mut self) -> NodeId {
46 let id = self.next_id;
47 self.next_id += 1;
48 id
49 }
50
51 pub fn add_decision(&mut self, player: PlayerId, label: &str) -> NodeId {
53 let id = self.alloc_id();
54 let node = NodeData::decision(id, player, label);
55 self.nodes.insert(id, node);
56 if self.root.is_none() {
57 self.root = Some(id);
58 }
59 id
60 }
61
62 pub fn add_terminal(&mut self, payoffs: Vec<Payoff>, label: &str) -> NodeId {
64 let id = self.alloc_id();
65 let node = NodeData::terminal(id, payoffs, label);
66 self.nodes.insert(id, node);
67 id
68 }
69
70 pub fn add_chance(&mut self, probabilities: Vec<f64>, label: &str) -> NodeId {
72 let id = self.alloc_id();
73 let node = NodeData::chance(id, probabilities, label);
74 self.nodes.insert(id, node);
75 if self.root.is_none() {
76 self.root = Some(id);
77 }
78 id
79 }
80
81 pub fn add_action(&mut self, parent: NodeId, action: &str, child: NodeId) {
83 if let Some(node) = self.nodes.get_mut(&parent) {
84 node.actions.push(action.to_string());
85 node.children.push(child);
86 }
87 if let Some(child_node) = self.nodes.get_mut(&child) {
88 child_node.parent = Some(parent);
89 child_node.incoming_action = Some(action.to_string());
90 }
91 }
92
93 pub fn get_node(&self, id: NodeId) -> Option<&NodeData> {
95 self.nodes.get(&id)
96 }
97
98 pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut NodeData> {
100 self.nodes.get_mut(&id)
101 }
102
103 pub fn terminals(&self) -> Vec<&NodeData> {
105 self.nodes.values().filter(|n| n.is_terminal()).collect()
106 }
107
108 pub fn depth(&self, id: NodeId) -> usize {
110 let mut depth = 0;
111 let mut current = id;
112 while let Some(node) = self.nodes.get(¤t) {
113 match node.parent {
114 Some(p) => {
115 depth += 1;
116 current = p;
117 }
118 None => break,
119 }
120 }
121 depth
122 }
123
124 pub fn path_to(&self, id: NodeId) -> Vec<NodeId> {
126 let mut path = vec![id];
127 let mut current = id;
128 while let Some(node) = self.nodes.get(¤t) {
129 match node.parent {
130 Some(p) => {
131 path.push(p);
132 current = p;
133 }
134 None => break,
135 }
136 }
137 path.reverse();
138 path
139 }
140
141 pub fn node_count(&self) -> usize {
143 self.nodes.len()
144 }
145
146 pub fn ultimatum_game(total: Payoff) -> Self {
148 let mut tree = Self::new("Ultimatum Game");
149 tree.num_players = 2;
150
151 let root = tree.add_decision(0, "P1 Proposes");
153 let high = total * 0.8;
154 let low = total * 0.2;
155
156 let p2_high = tree.add_decision(1, "P2 Responds to High");
158 let t_ha = tree.add_terminal(vec![total - high, high], "High Accepted");
159 let t_hr = tree.add_terminal(vec![0.0, 0.0], "High Rejected");
160
161 tree.add_action(p2_high, "Accept", t_ha);
162 tree.add_action(p2_high, "Reject", t_hr);
163
164 let p2_low = tree.add_decision(1, "P2 Responds to Low");
166 let t_la = tree.add_terminal(vec![total - low, low], "Low Accepted");
167 let t_lr = tree.add_terminal(vec![0.0, 0.0], "Low Rejected");
168
169 tree.add_action(p2_low, "Accept", t_la);
170 tree.add_action(p2_low, "Reject", t_lr);
171
172 tree.add_action(root, "High Offer", p2_high);
173 tree.add_action(root, "Low Offer", p2_low);
174
175 tree
176 }
177
178 pub fn centipede_game(n: usize, pot: Payoff, growth: Payoff) -> Self {
180 let mut tree = Self::new("Centipede Game");
181 tree.num_players = 2;
182
183 let root = tree.add_decision(0, "P1 Turn 1");
184 Self::build_centipede(&mut tree, root, n, pot, growth, 0);
185 tree
186 }
187
188 fn build_centipede(
189 tree: &mut Self,
190 current: NodeId,
191 rounds_left: usize,
192 pot: Payoff,
193 growth: Payoff,
194 round: usize,
195 ) {
196 let player = round % 2;
197 let take_share = if player == 0 { 0.7 } else { 0.3 };
198 let pass_pot = pot + growth;
199
200 let t_take = tree.add_terminal(
202 vec![pot * take_share, pot * (1.0 - take_share)],
203 &format!("P{} Takes", player),
204 );
205 tree.add_action(current, "Take", t_take);
206
207 if rounds_left > 1 {
209 let next = tree.add_decision(1 - player, &format!("P{} Turn {}", 1 - player, round + 2));
210 tree.add_action(current, "Pass", next);
211 Self::build_centipede(tree, next, rounds_left - 1, pass_pot, growth, round + 1);
212 } else {
213 let t_end = tree.add_terminal(
215 vec![pass_pot * 0.5, pass_pot * 0.5],
216 "Game Ends Cooperatively",
217 );
218 tree.add_action(current, "Pass", t_end);
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_empty_tree() {
229 let tree = GameTree::new("Empty");
230 assert_eq!(tree.node_count(), 0);
231 assert!(tree.root.is_none());
232 }
233
234 #[test]
235 fn test_simple_tree() {
236 let mut tree = GameTree::new("Simple");
237 let root = tree.add_decision(0, "Root");
238 let left = tree.add_terminal(vec![1.0, 2.0], "Left");
239 let right = tree.add_terminal(vec![3.0, 0.0], "Right");
240 tree.add_action(root, "L", left);
241 tree.add_action(root, "R", right);
242 assert_eq!(tree.node_count(), 3);
243 assert_eq!(tree.root, Some(root));
244 }
245
246 #[test]
247 fn test_ultimatum_game() {
248 let tree = GameTree::ultimatum_game(10.0);
249 assert_eq!(tree.node_count(), 7);
250 assert_eq!(tree.terminals().len(), 4);
251 }
252
253 #[test]
254 fn test_centipede_game() {
255 let tree = GameTree::centipede_game(3, 1.0, 1.0);
256 assert!(tree.node_count() > 3);
257 assert!(tree.terminals().len() >= 3);
258 }
259
260 #[test]
261 fn test_depth() {
262 let mut tree = GameTree::new("Depth");
263 let root = tree.add_decision(0, "Root");
264 let child = tree.add_terminal(vec![1.0], "Child");
265 tree.add_action(root, "Go", child);
266 assert_eq!(tree.depth(root), 0);
267 assert_eq!(tree.depth(child), 1);
268 }
269
270 #[test]
271 fn test_path_to() {
272 let mut tree = GameTree::new("Path");
273 let root = tree.add_decision(0, "Root");
274 let mid = tree.add_decision(1, "Mid");
275 let leaf = tree.add_terminal(vec![1.0, 2.0], "Leaf");
276 tree.add_action(root, "Go", mid);
277 tree.add_action(mid, "Go", leaf);
278 let path = tree.path_to(leaf);
279 assert_eq!(path.len(), 3);
280 }
281}