Skip to main content

ai_tournament/
lib.rs

1//! # Ai Tournament
2//!
3//! A modular Rust crate system for evaluating AI agents via customizable tournaments, supporting sandboxed execution and flexible constraints.
4//!
5//! It provides:
6//! - Match scheduling and execution (`Evaluator`)
7//! - Tournament logic via the `TournamentStrategy` trait
8//! - Built-in strategies like `SinglePlayerTournament`, `SwissTournament` and `RoundRobin`
9//! - Resource constraints enforced through Linux cgroups v2 and `taskset`
10//!
11//! Each match consists of one or more agents, each running as a separate OS process.
12//! Process-level isolation applies constraints such as CPU affinity, memory limits, and timeouts.
13//!
14//! # Documentation Overview
15//!
16//! - For details about the core tournament execution and agent lifecycle, see the [`server`] module.
17//! - For configuring evaluation behavior, resource limits, and execution environment,
18//!   see [`Configuration`](crate::configuration::Configuration) and [`constraints`].
19//! - To understand tournament formats and match scheduling, see the [`TournamentStrategy`](crate::tournament_strategy::TournamentStrategy) trait and its implementations.
20//! - For implementing custom games and agents, check out the [`Game`] and [`GameFactory`] traits.
21//!
22//! This crate is designed to be modular and extensible, allowing you to customize agent compilation, match execution, and resource management.
23//!
24//! # Usage Example
25//!
26//! Below is a minimal example of using the evaluator with a custom game and built-in tournament:
27//!
28//! ```no_run
29//! # struct YourAgent;
30//! # impl YourAgent {
31//! #     pub fn new() -> Self { YourAgent }
32//! #     pub fn select_action(&mut self, _action: u32) -> u32 { 0 }
33//! # }
34//! # #[derive(Clone)]
35//! # struct YourGame;
36//! # impl YourGame {
37//! #     pub fn new() -> YourGame { YourGame }
38//! # }
39//! # impl ai_tournament::game_interface::Game for YourGame {
40//! #     type State = u32;
41//! #     type Action = u32;
42//! #     type Score = f32;
43//! #     fn apply_action(&mut self, _action: &Option<Self::Action>) -> anyhow::Result<()> { Ok(()) }
44//! #     fn get_state(&self) -> Self::State { 0 }
45//! #     fn get_current_player_number(&self) -> usize { 0 }
46//! #     fn is_finished(&self) -> bool { true }
47//! #     fn get_player_score(&self, _player_number: u32) -> f32 { 0.0 }
48//! # }
49//! # impl ai_tournament::game_interface::GameFactory<YourGame> for YourGame {
50//! #     fn new_game(&self) -> YourGame { YourGame }
51//! # }
52//! use std::{collections::HashMap, time::Duration};
53//! use anyhow;
54//! use ai_tournament::prelude::*;
55//!
56//! fn main() -> anyhow::Result<()> {
57//!     // Define per-agent constraints
58//!     let constraints = ConstraintsBuilder::new()
59//!         .with_ram_per_agent(1000) // in MB
60//!         .with_action_timeout(Duration::from_millis(100))
61//!         .build()?;
62//!
63//!     // Create a configuration allowing uncontained execution if cgroup v2 or taskset are not
64//!     // available
65//!     let config = Configuration::new().with_allow_uncontained(true);
66//!
67//!     // Your custom game implementing the Game + GameFactory traits
68//!     let factory = YourGame::new();
69//!     let evaluator = Evaluator::new(factory, config, constraints);
70//!
71//!     let tournament = SinglePlayerTournament::new(10); // Run 10 games per agent
72//!     let (results, errors): (HashMap<String, SinglePlayerScore<_>>, _) =
73//!         evaluator.evaluate("path_to_agents_directory", tournament)?;
74//!
75//!     // Sort and display scores
76//!     let mut sorted = results.iter().collect::<Vec<_>>();
77//!     sorted.sort_by(|a, b| b.1.cmp(a.1));
78//!     for (agent_name, score) in sorted {
79//!         println!("{agent_name}: {score:?}");
80//!     }
81//!     // Print non-compiling agents and the associated error
82//!     println!("\nNon-compiling agents:");
83//!     for (agent_name, error) in errors.into_iter() {
84//!         println!("{agent_name}: {error}");
85//!     }
86//!
87//!     Ok(())
88//! }
89//! ```
90//!
91//! # Example Agent
92//!
93//! Here’s a minimal agent implementation that communicates over TCP:
94//!
95//! ```no_run
96//! # struct YourAgent;
97//! # impl YourAgent {
98//! #     pub fn new() -> Self { YourAgent }
99//! #     pub fn select_action(&mut self, _action: u32) -> u32 { 0 }
100//! # }
101//! # struct YourGame;
102//! # impl ai_tournament::game_interface::Game for YourGame {
103//! #     type State = u32;
104//! #     type Action = u32;
105//! #     type Score = f32;
106//! #     fn apply_action(&mut self, _action: &Option<Self::Action>) -> anyhow::Result<()> { Ok(()) }
107//! #     fn get_state(&self) -> Self::State { 0 }
108//! #     fn get_current_player_number(&self) -> usize { 0 }
109//! #     fn is_finished(&self) -> bool { true }
110//! #     fn get_player_score(&self, _player_number: u32) -> f32 { 0.0 }
111//! # }
112//! use std::{
113//!     env,
114//!     io::{Read, Write},
115//!     net::{Ipv4Addr, SocketAddrV4, TcpStream},
116//!     str::{self, FromStr},
117//!     time::Duration,
118//! };
119//!
120//! use anyhow;
121//!
122//! use ai_tournament::game_interface::Game;
123//!
124//! fn main() -> anyhow::Result<()> {
125//!     let mut args = env::args();
126//!     let _ = args.next(); // Skip binary name
127//!
128//!     // Read the port number to connect to
129//!     let port = args.next().unwrap().parse()?;
130//!     let addr = SocketAddrV4::new(Ipv4Addr::from_str("127.0.0.1")?, port);
131//!     let mut stream = TcpStream::connect(addr)?;
132//!
133//!     // Optionally, reading time_budget and action_timeout from next args
134//!     let total_time_budget = Duration::from_micros(args.next().unwrap().parse()?);
135//!     let action_timeout = Duration::from_micros(args.next().unwrap().parse()?);
136//!     // After the four first arguments (binary name, port number, time budget, and action
137//!     // timeout) will follow your arguments defined in your config file
138//!
139//!     let mut agent = YourAgent::new();
140//!
141//!     // Interaction loop
142//!     loop {
143//!         let mut buf = [0; 4096];
144//!         let n = stream.read(&mut buf)?;
145//!         let string = str::from_utf8(&buf[..n])?;
146//!
147//!         // Parse game state, compute action, send it back
148//!         let game_state = string.parse::<<YourGame as Game>::State>()?;
149//!         let action = agent.select_action(game_state);
150//!         stream.write_all(action.to_string().as_bytes())?;
151//!     }
152//! }
153//! ```
154//!
155//! ## Agent Requirements
156//!
157//! - `Game::State` and `Game::Action` must implement `ToString` and `FromStr`
158//! - Agent logic must terminate within the configured timeout
159//! - Communication is done over TCP using a basic protocol:
160//!  * Server -> Agent : string of Game::State
161//!  * Agent -> Server : string of Game::Action
162#![warn(missing_docs)]
163
164mod cgroup_manager;
165pub mod game_interface;
166pub use anyhow;
167mod agent;
168mod agent_collector;
169mod client_handler;
170pub mod configuration;
171pub mod constraints;
172mod logger;
173mod match_runner;
174pub mod server;
175mod tournament_scheduler;
176pub mod tournament_strategy;
177
178/// Commonly used types and traits for quick access.
179///
180/// Import this prelude to get started easily:
181/// ```rust
182/// use ai_tournament::prelude::*;
183/// ```
184///
185/// Includes:
186/// - [`Configuration`](crate::configuration::Configuration)
187/// - [`ConstraintsBuilder`](crate::constraints::ConstraintsBuilder)
188/// - [`Evaluator`](crate::server::Evaluator)
189/// - all built-in [`Tournament strategies`](crate::tournament_strategy)
190pub mod prelude {
191    pub use crate::configuration::Configuration;
192    pub use crate::constraints::ConstraintsBuilder;
193    pub use crate::game_interface::Game;
194    pub use crate::game_interface::GameFactory;
195    pub use crate::server::Evaluator;
196    pub use crate::tournament_strategy::*;
197}