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}