arboriter_mcts/
lib.rs

1//! # arboriter-mcts
2//!
3//! A Monte Carlo Tree Search (MCTS) implementation built on top of the arboriter tree traversal primitive.
4//!
5//! This crate provides a flexible and efficient implementation of the MCTS algorithm, which can be
6//! used for decision making in games, planning problems, and other domains where a tree of possible
7//! futures can be explored.
8//!
9//! ## Features
10//!
11//! - Generic implementation that works with any game or decision process
12//! - Multiple selection policies (UCB1, UCB1-Tuned, PUCT)
13//! - Customizable simulation strategies
14//! - Detailed search statistics and visualization
15//! - Built on arboriter's tree traversal primitives for elegant implementation
16//!
17//! ## Basic Usage
18//!
19//! ```rust
20//! use arboriter_mcts::{MCTS, MCTSConfig, GameState};
21//!
22//! // Implement GameState for your game
23//! #[derive(Clone)]
24//! struct MyGame {
25//!     // Game state fields would go here
26//!     player_turn: MyPlayer,
27//!     is_over: bool,
28//! }
29//!
30//! // Define an action type
31//! #[derive(Clone, Debug, PartialEq, Eq)]
32//! struct MyAction(u8);
33//!
34//! impl arboriter_mcts::Action for MyAction {
35//!     fn id(&self) -> usize {
36//!         self.0 as usize
37//!     }
38//! }
39//!
40//! // Define a player type
41//! #[derive(Clone, Debug, PartialEq, Eq)]
42//! struct MyPlayer(u8);
43//!
44//! // Implement the Player trait for our custom type
45//! impl arboriter_mcts::Player for MyPlayer {}
46//!
47//! impl GameState for MyGame {
48//!     type Action = MyAction;
49//!     type Player = MyPlayer;
50//!
51//!     fn get_legal_actions(&self) -> Vec<Self::Action> {
52//!         // Return legal actions from this state
53//!         vec![MyAction(0), MyAction(1), MyAction(2)]
54//!     }
55//!
56//!     fn apply_action(&self, action: &Self::Action) -> Self {
57//!         // Apply the action and return the new state
58//!         let mut new_state = self.clone();
59//!         // Logic to apply action would go here
60//!         new_state.player_turn = MyPlayer(1 - self.player_turn.0); // Switch players
61//!
62//!         // Mark the state as terminal after an action to prevent infinite search
63//!         // In a real game, you would have proper termination conditions
64//!         new_state.is_over = true;
65//!
66//!         new_state
67//!     }
68//!
69//!     fn is_terminal(&self) -> bool {
70//!         // Return true if this is a terminal (game over) state
71//!         self.is_over
72//!     }
73//!
74//!     fn get_result(&self, for_player: &Self::Player) -> f64 {
75//!         // Return the outcome from for_player's perspective
76//!         // 1.0 = win, 0.5 = draw, 0.0 = loss
77//!         if self.player_turn.0 == for_player.0 { 0.7 } else { 0.3 }
78//!     }
79//!
80//!     fn get_current_player(&self) -> Self::Player {
81//!         // Return the player whose turn it is
82//!         self.player_turn.clone()
83//!     }
84//! }
85//!
86//! # fn main() -> Result<(), arboriter_mcts::MCTSError> {
87//! // Create initial game state
88//! let initial_state = MyGame {
89//!     player_turn: MyPlayer(0),
90//!     is_over: false,
91//! };
92//!
93// Create a configuration for the search
94//! let config = MCTSConfig::default()
95//!     .with_exploration_constant(1.414)
96//!     .with_max_iterations(10); // Small number of iterations for doctest
97//!
98//! // Create the MCTS searcher with initial state
99//! let mut mcts = MCTS::new(initial_state, config);
100//!
101//! // Find the best action
102//! let best_action = mcts.search()?;
103//!
104//! // Get search statistics
105//! println!("{}", mcts.get_statistics().summary());
106//!
107//! // Use the action in your game
108//! println!("Best action: {:?}", best_action);
109//! # Ok(())
110//! # }
111//! ```
112//!
113//! ## How It Works
114//!
115//! MCTS consists of four main phases:
116//!
117//! 1. **Selection**: Starting from the root, select successive child nodes down to a leaf node.
118//!    This phase uses a selection policy (like UCB1) to balance exploration and exploitation.
119//!
120//! 2. **Expansion**: If the leaf node is not terminal and has untried actions, create one or more
121//!    child nodes by applying those actions.
122//!
123//! 3. **Simulation**: From the new node, simulate a game to completion using a default policy.
124//!    This often involves random play, but can use domain-specific heuristics.
125//!
126//! 4. **Backpropagation**: Update the statistics (visit counts and rewards) for all nodes
127//!    in the path from the selected node to the root.
128//!
129//! This process is repeated many times, gradually building a tree of game states and improving
130//! the value estimates for each action.
131//!
132//! ## Customizing Policies
133//!
134//! You can customize each aspect of the MCTS algorithm by providing different policies:
135//!
136//! ```rust
137//! use arboriter_mcts::{MCTS, MCTSConfig, GameState};
138//!
139//! // Example with minimal configuration for customizing policies
140//!
141//! // Define the types and structures needed
142//! #[derive(Clone, Debug, PartialEq, Eq)]
143//! struct MyPlayer(u8);
144//!
145//! impl arboriter_mcts::Player for MyPlayer {}
146//!
147//! #[derive(Clone, Debug, PartialEq, Eq)]
148//! struct MyAction(u8);
149//!
150//! impl arboriter_mcts::Action for MyAction {
151//!     fn id(&self) -> usize { self.0 as usize }
152//! }
153//!
154//! // Create a simple game with a terminal state
155//! #[derive(Clone)]
156//! struct MyGame {
157//!     player_turn: MyPlayer,
158//!     is_over: bool,
159//! }
160//!
161//! impl GameState for MyGame {
162//!     type Action = MyAction;
163//!     type Player = MyPlayer;
164//!
165//!     fn get_legal_actions(&self) -> Vec<Self::Action> {
166//!         if self.is_over { return vec![]; }
167//!         vec![MyAction(0)]
168//!     }
169//!
170//!     fn apply_action(&self, _: &Self::Action) -> Self {
171//!         // Create a terminal state to ensure search completes quickly
172//!         Self {
173//!             player_turn: self.player_turn.clone(),
174//!             is_over: true, // Make sure we reach a terminal state
175//!         }
176//!     }
177//!
178//!     fn is_terminal(&self) -> bool { self.is_over }
179//!
180//!     fn get_result(&self, _: &Self::Player) -> f64 { 0.5 }
181//!
182//!     fn get_current_player(&self) -> Self::Player { self.player_turn.clone() }
183//! }
184//!
185//! // Import the policy types
186//! use arboriter_mcts::policy::{
187//!     selection::UCB1Policy,
188//!     simulation::RandomPolicy,
189//!     backpropagation::StandardPolicy,
190//! };
191//!
192//! fn main() -> Result<(), arboriter_mcts::MCTSError> {
193//!     // Create a terminal state game to ensure fast completion
194//!     let initial_state = MyGame {
195//!         player_turn: MyPlayer(0),
196//!         is_over: false,
197//!     };
198//!
199//!     // Configure with very few iterations
200//!     let config = MCTSConfig::default()
201//!         .with_max_iterations(10); // Only do a few iterations for the doctest
202//!
203//!     // Customize policies
204//!     let mut mcts = MCTS::new(initial_state, config)
205//!         .with_selection_policy(UCB1Policy::new(1.5))
206//!         .with_simulation_policy(RandomPolicy::new())
207//!         .with_backpropagation_policy(StandardPolicy::new());
208//!
209//!     // Run a quick search
210//!     let result = mcts.search();
211//!     println!("Search completed with result: {:?}", result);
212//!
213//!     Ok(())
214//! }
215//! ```
216//!
217//! ## Examples
218//!
219//! The crate includes complete examples for:
220//!
221//! - Tic-Tac-Toe: A simple 3x3 game
222//! - Connect Four: A more complex 7x6 game
223//!
224//! To run the examples:
225//!
226//! ```bash
227//! cargo run --example tic_tac_toe
228//! cargo run --example connect_four
229//! ```
230
231pub mod config;
232pub mod game_state;
233pub mod mcts;
234pub mod policy;
235pub mod stats;
236pub mod tree;
237pub mod utils;
238
239pub use config::MCTSConfig;
240pub use game_state::{Action, GameState, Player};
241pub use mcts::MCTS;
242pub use policy::{BackpropagationPolicy, SelectionPolicy, SimulationPolicy};
243pub use stats::SearchStatistics;
244pub use tree::{MCTSNode, NodePath};
245
246/// Error types for the MCTS algorithm
247#[derive(thiserror::Error, Debug)]
248pub enum MCTSError {
249    /// No legal actions are available from the current state
250    #[error("No legal actions available from current state")]
251    NoLegalActions,
252
253    /// Search was stopped before completion
254    #[error("Search stopped: {0}")]
255    SearchStopped(String),
256
257    /// Invalid configuration
258    #[error("Invalid configuration: {0}")]
259    InvalidConfiguration(String),
260}
261
262/// Result type for MCTS operations
263pub type Result<T> = std::result::Result<T, MCTSError>;