use std::{
collections::{HashMap, HashSet},
sync::{Arc, Mutex},
};
use chrono::{DateTime, Utc};
use rand::seq::SliceRandom;
use serde::{Deserialize, Serialize};
use crate::{Id, accounts::Accounts, glicko::Rating, server_game::ServerGame, status::Status};
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Tournament {
pub id: u64,
pub players: HashSet<String>,
pub players_left: HashSet<String>,
pub date: DateTime<Utc>,
pub groups: Option<Vec<Vec<Arc<Mutex<Group>>>>>,
pub tournament_games: HashMap<Id, Arc<Mutex<Group>>>,
}
impl Tournament {
#[allow(clippy::float_cmp, clippy::too_many_lines)]
#[must_use]
pub fn game_over(&mut self, game: &ServerGame) -> bool {
let mut next_round = false;
if let Some(group) = self.tournament_games.get_mut(&game.id) {
if let Ok(mut group) = group.lock() {
match game.game.status {
Status::AttackerWins => {
if let Some(record) = group.records.get_mut(game.attacker.as_str()) {
record.wins += 1;
}
if let Some(record) = group.records.get_mut(game.defender.as_str()) {
record.losses += 1;
}
}
Status::Draw => {
if let Some(record) = group.records.get_mut(game.attacker.as_str()) {
record.draws += 1;
}
if let Some(record) = group.records.get_mut(game.defender.as_str()) {
record.draws += 1;
}
}
Status::Ongoing => {}
Status::DefenderWins => {
if let Some(record) = group.records.get_mut(game.attacker.as_str()) {
record.losses += 1;
}
if let Some(record) = group.records.get_mut(game.defender.as_str()) {
record.wins += 1;
}
}
}
let mut games_count = 0;
for record in group.records.values() {
games_count += record.games_count();
}
if group.total_games == games_count / 2 {
let mut standings = Vec::new();
let mut players = Vec::new();
let mut previous_score = u64::MAX;
for (name, record) in &group.records {
players.push(name.clone());
let score = record.score();
if score != previous_score {
standings.push(Standing {
score,
players: players.clone(),
});
} else if let Some(standing) = standings.last_mut() {
standing.players.push(name.clone());
}
previous_score = score;
}
group.finishing_standings = standings;
}
}
self.tournament_games.remove(&game.id);
if let Some(round) = &self.groups
&& let Some(groups) = round.last()
{
let mut finished = true;
for group in groups {
if let Ok(group) = group.lock() {
let mut games_count = 0;
for record in group.records.values() {
games_count += record.games_count();
}
if group.total_games != games_count / 2 {
finished = false;
break;
}
}
}
if finished {
let mut players_left = HashSet::new();
for group in groups {
if let Ok(group) = group.lock()
&& let Some(top_score) = group.records.values().map(Record::score).max()
{
for (name, record) in &group.records {
if record.score() == top_score {
players_left.insert(name.clone());
} else {
next_round = true;
}
}
}
}
self.players_left = players_left;
}
}
}
next_round
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn generate_round(&mut self, accounts: &Accounts, mut group_size: usize) -> Vec<Group> {
let mut players_vec = Vec::new();
for player in &self.players_left {
let mut rating = Rating::default();
if let Some(account) = accounts.0.get(player.as_str()) {
rating = account.rating.clone();
}
players_vec.push((player.clone(), rating));
}
let players_len = players_vec.len();
let mut rng = rand::rng();
players_vec.shuffle(&mut rng);
players_vec.sort_unstable_by(|a, b| b.1.rating.total_cmp(&a.1.rating));
let mut groups_number = players_len / group_size;
let mut remainder = 0;
if groups_number == 0 {
groups_number = 1;
group_size = players_len;
} else {
remainder = players_len % group_size;
}
let mut groups = Vec::new();
for _ in 0..groups_number {
let mut group = self.new_group();
for _ in 0..group_size {
let player = players_vec.pop().expect("There should be a player to pop.");
group.records.insert(
player.0,
Record {
rating: player.1,
..Record::default()
},
);
}
groups.push(group);
}
if remainder != 0
&& let Some(mut group_1) = groups.pop()
{
for _ in 0..remainder {
let player = players_vec.pop().expect("There should be a player to pop.");
group_1.records.insert(
player.0,
Record {
rating: player.1,
..Record::default()
},
);
}
let len = group_1.records.len();
let mut records_1: Vec<_> = group_1.records.into_iter().take(len).collect();
records_1.shuffle(&mut rng);
records_1.sort_unstable_by(|(_, record_1), (_, record_2)| {
record_2.rating.rating.total_cmp(&record_1.rating.rating)
});
let records_2 = records_1.split_off(len / 2);
let mut records_1a = HashMap::new();
for (name, record) in records_1 {
records_1a.insert(name, record);
}
group_1.records = records_1a;
let mut group_2 = self.new_group();
let mut records_2a = HashMap::new();
for (name, record) in records_2 {
records_2a.insert(name, record);
}
group_2.records = records_2a;
groups.push(group_1);
groups.push(group_2);
}
groups
}
pub fn remove_duplicate_ids(&mut self) {
if let Some(groups) = &self.groups {
for round in groups {
for group_1 in round {
if let Ok(group_1a) = group_1.lock() {
for group_2 in self.tournament_games.values_mut() {
if let Ok(group_2a) = group_2.clone().lock()
&& group_1a.id == group_2a.id
{
*group_2 = group_1.clone();
}
}
}
}
}
}
}
pub fn new_group(&mut self) -> Group {
let group = Group {
id: self.id,
..Group::default()
};
self.id += 1;
group
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Group {
pub id: u64,
pub total_games: u64,
pub records: HashMap<String, Record>,
pub finishing_standings: Vec<Standing>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Standing {
pub score: u64,
pub players: Vec<String>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Record {
pub rating: Rating,
pub wins: u64,
pub losses: u64,
pub draws: u64,
}
impl Record {
#[must_use]
pub fn games_count(&self) -> u64 {
self.wins + self.losses + self.draws
}
pub fn reset(&mut self) {
self.wins = 0;
self.losses = 0;
self.draws = 0;
}
#[must_use]
pub fn score(&self) -> u64 {
2 * self.wins + self.draws
}
}