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>;