serverstat 0.1.4

Get server info from QuakeWorld servers.
Documentation
use crate::gameserver::Player;
use quake_text::unicode;
use std::cmp::Ordering;
use std::collections::HashMap;

#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Team {
    pub name: String,
    pub frags: i32,
    pub ping: u32,
    pub top_color: u8,
    pub bottom_color: u8,
}

#[derive(Default)]
#[allow(dead_code)]
struct TempTeam {
    name: String,
    frags: i32,
    ping_sum: f32,
    player_count: usize,
    colors: Vec<(u8, u8)>,
}

pub fn from_players(players: &[Player]) -> Vec<Team> {
    let mut temp: HashMap<String, TempTeam> = HashMap::new();

    for player in players {
        let team = temp.entry(player.team.clone()).or_default();
        team.name = player.team.clone();
        team.frags += player.frags;
        team.ping_sum += player.ping as f32;
        team.player_count += 1;
        team.colors.push((player.top_color, player.bottom_color));
    }

    let mut teams: Vec<Team> = Vec::new();
    for team in temp.values() {
        let (top_color, bottom_color) = get_majority_color(&team.colors);
        teams.push(Team {
            name: team.name.clone(),
            frags: team.frags,
            ping: (team.ping_sum / team.player_count as f32).round() as u32,
            top_color,
            bottom_color,
        });
    }
    teams.sort();
    teams
}

impl PartialOrd for Team {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Team {
    fn cmp(&self, other: &Self) -> Ordering {
        unicode::ord(&self.name, &other.name)
    }
}

fn get_majority_color(colors: &[(u8, u8)]) -> (u8, u8) {
    if colors.is_empty() {
        return (0, 0);
    } else if colors.len() < 3 {
        return colors[0];
    }

    let mut color_count: HashMap<(u8, u8), usize> = HashMap::new();

    for color in colors {
        let count = color_count.entry(*color).or_default();
        *count += 1;
    }

    let mut max_count = 0;
    let mut majority_color = (0, 0);

    for (color, count) in color_count {
        if count > max_count {
            max_count = count;
            majority_color = color;
        }
    }

    majority_color
}

#[cfg(test)]
mod tests {
    use super::*;
    use anyhow::Result;
    use pretty_assertions::assert_eq;

    #[test]
    fn test_from_clients() -> Result<()> {
        let clients = vec![
            Player {
                team: "red".to_string(),
                frags: 10,
                ping: 12,
                top_color: 0,
                bottom_color: 0,
                ..Default::default()
            },
            Player {
                team: "red".to_string(),
                frags: 5,
                ping: 25,
                top_color: 4,
                bottom_color: 4,
                ..Default::default()
            },
            Player {
                team: "red".to_string(),
                frags: 2,
                ping: 25,
                top_color: 4,
                bottom_color: 4,
                ..Default::default()
            },
            Player {
                team: "blue".to_string(),
                frags: 7,
                ping: 52,
                top_color: 13,
                bottom_color: 13,
                ..Default::default()
            },
        ];

        let teams = from_players(&clients);
        assert_eq!(teams.len(), 2);

        assert_eq!(
            teams[0],
            Team {
                name: "blue".to_string(),
                frags: 7,
                ping: 52,
                top_color: 13,
                bottom_color: 13,
            }
        );

        assert_eq!(
            teams[1],
            Team {
                name: "red".to_string(),
                frags: 17,
                ping: 21,
                top_color: 4,
                bottom_color: 4,
            }
        );

        Ok(())
    }

    #[test]
    fn test_get_majority_color() {
        let m = get_majority_color;
        assert_eq!(m(&[]), (0, 0));
        assert_eq!(m(&[(1, 1)]), (1, 1));
        assert_eq!(m(&[(1, 1), (0, 0)]), (1, 1));
        assert_eq!(m(&[(0, 0), (1, 1), (1, 1)]), (1, 1));
    }
}