1use alloc::vec::Vec;
19use core::fmt;
20
21use crate::rng::SimRng;
22use crate::turn::TurnRules;
23
24pub mod balance;
25pub mod determinize;
26pub mod order;
27pub mod plan;
28pub mod playtest;
29pub mod rollout;
30pub mod weigh;
31
32pub use balance::{BalanceSettings, Balanced, Knob, Stop, autobalance, autobalance_by};
33pub use determinize::{Determinize, Evaluate};
34pub use order::{Ranked, ranked_order};
35pub use plan::{PlanSearch, PlanSettings, TurnPlan};
36pub use rollout::{Horizon, Order, Playout, Rollout, RolloutSearch, RolloutSettings, Unordered};
37pub use weigh::{Persona, UnknownFeature, Weighted, Weights};
38
39pub trait Sides: TurnRules {
42 type Side: Copy + Eq + fmt::Debug;
44
45 fn side_to_move(&self) -> Self::Side;
47
48 fn turn(&self) -> u32;
50}
51
52pub trait Bot<R: TurnRules> {
60 fn choose(&mut self, rules: &R) -> R::Command;
64}
65
66impl<R: TurnRules, F: FnMut(&R) -> R::Command> Bot<R> for F {
69 fn choose(&mut self, rules: &R) -> R::Command {
70 self(rules)
71 }
72}
73
74pub struct Report<R: TurnRules> {
76 pub outcome: Option<R::Outcome>,
78 pub turns: u32,
81 pub commands: Vec<R::Command>,
83}
84
85#[non_exhaustive]
88pub struct PlayError<R: Sides> {
89 pub command: R::Command,
91 pub side: R::Side,
93 pub turn: u32,
95 pub error: R::Error,
97}
98
99impl<R: Sides> PlayError<R> {
100 pub fn new(command: R::Command, side: R::Side, turn: u32, error: R::Error) -> Self {
103 Self {
104 command,
105 side,
106 turn,
107 error,
108 }
109 }
110}
111
112impl<R: TurnRules> fmt::Debug for Report<R>
117where
118 R::Outcome: fmt::Debug,
119 R::Command: fmt::Debug,
120{
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 f.debug_struct("Report")
123 .field("outcome", &self.outcome)
124 .field("turns", &self.turns)
125 .field("commands", &self.commands)
126 .finish()
127 }
128}
129
130impl<R: TurnRules> Clone for Report<R>
131where
132 R::Outcome: Clone,
133 R::Command: Clone,
134{
135 fn clone(&self) -> Self {
136 Self {
137 outcome: self.outcome.clone(),
138 turns: self.turns,
139 commands: self.commands.clone(),
140 }
141 }
142}
143
144impl<R: TurnRules> PartialEq for Report<R>
145where
146 R::Outcome: PartialEq,
147 R::Command: PartialEq,
148{
149 fn eq(&self, other: &Self) -> bool {
150 self.outcome == other.outcome
151 && self.turns == other.turns
152 && self.commands == other.commands
153 }
154}
155
156impl<R: Sides> fmt::Debug for PlayError<R>
157where
158 R::Command: fmt::Debug,
159 R::Error: fmt::Debug,
160{
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 f.debug_struct("PlayError")
163 .field("command", &self.command)
164 .field("side", &self.side)
165 .field("turn", &self.turn)
166 .field("error", &self.error)
167 .finish()
168 }
169}
170
171impl<R: Sides> fmt::Display for PlayError<R>
172where
173 R::Command: fmt::Debug,
174 R::Error: fmt::Display,
175{
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 write!(
178 f,
179 "turn {}, {:?} to move: {:?} refused: {}",
180 self.turn, self.side, self.command, self.error
181 )
182 }
183}
184
185pub fn play<R: Sides>(
193 rules: &mut R,
194 bots_by_side: &mut [(R::Side, &mut dyn Bot<R>)],
195 limit: u32,
196) -> Result<Report<R>, PlayError<R>>
197where
198 R::Command: Clone,
199{
200 play_observed(rules, bots_by_side, limit, |_, _, _| {})
201}
202
203pub fn play_observed<R: Sides>(
207 rules: &mut R,
208 bots_by_side: &mut [(R::Side, &mut dyn Bot<R>)],
209 limit: u32,
210 mut observe: impl FnMut(&R, &R::Command, &[R::Event]),
211) -> Result<Report<R>, PlayError<R>>
212where
213 R::Command: Clone,
214{
215 let mut commands = Vec::new();
216 while rules.outcome().is_none() && rules.turn() <= limit {
217 let side = rules.side_to_move();
218 let Some((_, bot)) = bots_by_side.iter_mut().find(|(s, _)| *s == side) else {
219 panic!("no bot plays side {side:?}");
220 };
221 let command = bot.choose(rules);
222 match rules.apply(&command) {
223 Ok(events) => {
224 observe(rules, &command, &events);
225 commands.push(command);
226 }
227 Err(error) => {
228 return Err(PlayError {
229 command,
230 side,
231 turn: rules.turn(),
232 error,
233 });
234 }
235 }
236 }
237 Ok(Report {
238 outcome: rules.outcome(),
239 turns: rules.turn(),
240 commands,
241 })
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct RandomBot {
248 rng: SimRng,
249}
250
251impl RandomBot {
252 pub fn new(seed: u64) -> Self {
254 Self {
255 rng: SimRng::new(seed),
256 }
257 }
258}
259
260impl<R: TurnRules> Bot<R> for RandomBot {
261 fn choose(&mut self, rules: &R) -> R::Command {
267 let mut legal = rules.legal_commands();
268 assert!(!legal.is_empty(), "no legal command to choose from");
269 let count = u32::try_from(legal.len()).unwrap_or(u32::MAX);
270 let pick = usize::try_from(self.rng.roll_below(count)).unwrap_or(0);
271 legal.swap_remove(pick)
272 }
273}
274
275#[cfg(test)]
276pub(crate) mod fixtures;
277#[cfg(test)]
278mod tests;