use std::{
cmp,
collections::{HashMap, HashSet},
sync::Arc,
};
use tracing::info;
use crate::{agent::Agent, match_runner::MatchResult};
pub trait TournamentStrategy<S: PartialOrd> {
type FinalScore: Ord;
fn add_agents(&mut self, agents: Vec<Arc<Agent>>);
fn advance_round(&mut self, scores: Vec<MatchResult<S>>) -> Vec<Vec<Arc<Agent>>>;
fn players_per_match(&self) -> usize;
fn get_final_scores(&self) -> HashMap<Arc<Agent>, Self::FinalScore>;
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Debug, Clone, Copy)]
pub struct TwoPlayersGameScore {
pub num_win: u32,
pub num_draw: u32,
pub num_lose: u32,
pub tie_breaker: u32,
}
impl std::fmt::Display for TwoPlayersGameScore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"win: {}, draw: {}, loose: {}, tie-breaker: {}",
self.num_win, self.num_draw, self.num_lose, self.tie_breaker
)
}
}
pub struct SwissTournament {
agents: Vec<Arc<Agent>>,
round: usize,
max_rounds: usize,
num_match_per_pair: usize,
scores: HashMap<Arc<Agent>, (TwoPlayersGameScore, HashSet<Arc<Agent>>)>,
}
impl SwissTournament {
pub fn with_auto_rounds(num_match_per_pair: usize) -> Self {
Self::new(0, num_match_per_pair)
}
pub fn new(max_rounds: usize, num_match_per_pair: usize) -> Self {
assert!(
num_match_per_pair >= 1,
"Must play at least one match per pairing."
);
Self {
agents: vec![],
round: 0,
max_rounds,
num_match_per_pair,
scores: HashMap::new(),
}
}
fn update_tie_breakers(&mut self) {
for agent in &self.agents {
let mut adv_scores = vec![];
for adv in &self.scores[agent].1 {
let adv_score = &self.scores[adv].0;
let adv_score = adv_score.num_win * 2 + adv_score.num_draw;
adv_scores.push(adv_score);
}
let min = *adv_scores.iter().min().unwrap_or(&0);
let max = *adv_scores.iter().max().unwrap_or(&0);
self.scores.get_mut(agent).unwrap().0.tie_breaker = if adv_scores.len() <= 1 {
0
} else {
adv_scores.iter().sum::<u32>() - min - max
};
}
}
fn update_scores(&mut self, match_results: Vec<MatchResult<f32>>) {
let mut pair_results: HashMap<(Arc<Agent>, Arc<Agent>), Vec<(f32, f32)>> =
HashMap::with_capacity(match_results.len() / self.num_match_per_pair);
for result in match_results {
assert!(result.len() == 2, "not two players match ??");
let (a, score_a) = &result[0];
let (b, score_b) = &result[1];
assert!(
!Arc::ptr_eq(a, b) && a.id != b.id,
"should not be able to play against yourself"
);
let key = if a.id < b.id {
(a.clone(), b.clone())
} else {
(b.clone(), a.clone())
};
let score = if a.id < b.id {
(*score_a, *score_b)
} else {
(*score_b, *score_a)
};
pair_results.entry(key).or_default().push(score);
}
for ((a, b), scores) in pair_results.into_iter() {
let (score_a, score_b) = scores
.into_iter()
.fold((0.0, 0.0), |acu, (score_a, score_b)| {
(acu.0 + score_a, acu.1 + score_b)
});
let is_draw = (score_a - score_b).abs() < f32::EPSILON;
if is_draw {
self.scores.get_mut(&a).unwrap().0.num_draw += 1;
self.scores.get_mut(&b).unwrap().0.num_draw += 1;
} else if score_a > score_b {
self.scores.get_mut(&a).unwrap().0.num_win += 1;
self.scores.get_mut(&b).unwrap().0.num_lose += 1;
} else {
self.scores.get_mut(&a).unwrap().0.num_lose += 1;
self.scores.get_mut(&b).unwrap().0.num_win += 1;
}
self.scores.get_mut(&a).unwrap().1.insert(b.clone());
self.scores.get_mut(&b).unwrap().1.insert(a.clone());
}
}
}
impl TournamentStrategy<f32> for SwissTournament {
fn advance_round(&mut self, scores: Vec<MatchResult<f32>>) -> Vec<Vec<Arc<Agent>>> {
self.update_scores(scores);
self.update_tie_breakers();
if self.round >= self.max_rounds {
return vec![];
}
let mut sorted = self.agents.clone();
sorted.sort_by_key(|agent| cmp::Reverse(self.scores[agent].0));
let pending = sorted
.chunks(2)
.flat_map(|chunk| {
if chunk.len() == 2 {
let a = &chunk[0];
let b = &chunk[1];
(0..self.num_match_per_pair)
.map(|i| {
if i % 2 == 0 {
vec![a.clone(), b.clone()]
} else {
vec![b.clone(), a.clone()]
}
})
.collect::<Vec<_>>()
} else {
let a = &chunk[0];
self.scores.get_mut(a).unwrap().0.num_win += 1;
vec![] }
})
.collect::<Vec<_>>();
self.round += 1;
pending
}
fn players_per_match(&self) -> usize {
2
}
fn add_agents(&mut self, agents: Vec<Arc<Agent>>) {
self.agents = agents;
if self.max_rounds == 0 {
let n = self.agents.len();
self.max_rounds = f32::log2(n as f32).ceil() as usize;
info!(
"Swiss tournament auto number of rounds: {}",
self.max_rounds
);
}
for agent in &self.agents {
self.scores.insert(
agent.clone(),
(TwoPlayersGameScore::default(), HashSet::new()),
);
}
}
type FinalScore = TwoPlayersGameScore;
fn get_final_scores(&self) -> HashMap<Arc<Agent>, Self::FinalScore> {
self.scores
.iter()
.map(|(agent, (score, _adv))| (agent.clone(), *score))
.collect()
}
}
pub struct RoundRobinTournament {
scores: HashMap<Arc<Agent>, TwoPlayersGameScore>,
agents: Vec<Arc<Agent>>,
symmetric: bool,
}
impl RoundRobinTournament {
pub fn new(symmetric: bool) -> Self {
Self {
symmetric,
agents: vec![],
scores: HashMap::new(),
}
}
}
impl<S: PartialOrd> TournamentStrategy<S> for RoundRobinTournament {
fn advance_round(&mut self, scores: Vec<MatchResult<S>>) -> Vec<Vec<Arc<Agent>>> {
for match_result in scores {
let mut best_score = &match_result[0].1;
for i in 1..match_result.len() {
if *best_score < match_result[i].1 {
best_score = &match_result[i].1;
}
}
let is_draw = match_result
.iter()
.all(|(_agent, score)| *score == *best_score);
for (agent, score) in &match_result {
if is_draw {
self.scores.entry(agent.clone()).or_default().num_draw += 1;
} else if *score == *best_score {
self.scores.entry(agent.clone()).or_default().num_win += 1;
} else
{
self.scores.entry(agent.clone()).or_default().num_lose += 1;
}
}
}
if !self.scores.is_empty() {
return vec![];
}
let n = self.agents.len();
let mut pending = vec![];
for i in 0..n {
for j in i..n {
pending.push(vec![self.agents[i].clone(), self.agents[j].clone()]);
if !self.symmetric {
pending.push(vec![self.agents[j].clone(), self.agents[i].clone()]);
}
}
}
pending
}
fn players_per_match(&self) -> usize {
2
}
fn add_agents(&mut self, agents: Vec<Arc<Agent>>) {
self.agents = agents;
}
type FinalScore = TwoPlayersGameScore;
fn get_final_scores(&self) -> HashMap<Arc<Agent>, Self::FinalScore> {
self.scores.clone()
}
}
#[derive(PartialEq, PartialOrd, Debug, Clone)]
pub struct SinglePlayerScore<S: PartialOrd>(pub Vec<S>);
impl<S: PartialOrd> Default for SinglePlayerScore<S> {
fn default() -> Self {
Self(vec![])
}
}
impl<S: PartialOrd> Eq for SinglePlayerScore<S> {}
impl<S: PartialOrd> Ord for SinglePlayerScore<S> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0.partial_cmp(&other.0).unwrap()
}
}
pub struct SinglePlayerTournament<S: PartialOrd> {
game_per_agent: usize,
agents: Vec<Arc<Agent>>,
scores: HashMap<Arc<Agent>, SinglePlayerScore<S>>,
}
impl<S: PartialOrd> SinglePlayerTournament<S> {
pub fn new(game_per_agent: usize) -> Self {
Self {
game_per_agent,
agents: vec![],
scores: HashMap::new(),
}
}
}
impl<S: PartialOrd + Clone> TournamentStrategy<S> for SinglePlayerTournament<S> {
fn advance_round(&mut self, match_results: Vec<MatchResult<S>>) -> Vec<Vec<Arc<Agent>>> {
for match_result in match_results {
for (agent, score) in match_result {
self.scores.entry(agent).or_default().0.push(score);
}
}
let mut pending = vec![];
for agent in self.agents.drain(..) {
pending.append(&mut vec![vec![agent.clone()]; self.game_per_agent]);
}
pending
}
fn players_per_match(&self) -> usize {
1
}
fn add_agents(&mut self, agents: Vec<Arc<Agent>>) {
self.agents = agents;
}
type FinalScore = SinglePlayerScore<S>;
fn get_final_scores(&self) -> HashMap<Arc<Agent>, Self::FinalScore> {
self.scores.clone()
}
}