what the hell, now i can mix stuff, and it seems to work
This commit is contained in:
@@ -12,6 +12,10 @@ use serenity::model::id::{ChannelId, RoleId, UserId};
|
|||||||
use serenity::model::Permissions;
|
use serenity::model::Permissions;
|
||||||
use crate::bot::commands::MixerCommand;
|
use crate::bot::commands::MixerCommand;
|
||||||
use crate::database::DatabaseContainer;
|
use crate::database::DatabaseContainer;
|
||||||
|
use crate::mixer::mixer;
|
||||||
|
use crate::mixer::player::Player;
|
||||||
|
use crate::mixer::role::Role;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LobbyCommand;
|
pub struct LobbyCommand;
|
||||||
@@ -146,11 +150,28 @@ impl LobbyCommand {
|
|||||||
member.move_to_voice_channel(ctx.http(), main_channel.id).await?;
|
member.move_to_voice_channel(ctx.http(), main_channel.id).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let members = main_channel.members(ctx.cache().unwrap()).await?;
|
// TODO: uncomment this
|
||||||
let users = members.iter().map(|m| m.user.id).collect::<Vec<UserId>>();
|
// let members = main_channel.members(ctx.cache().unwrap()).await?;
|
||||||
|
// let users = members.iter().map(|m| m.user.id).collect::<Vec<UserId>>();
|
||||||
|
let users = (0..10).map(|id| UserId::from(id)).collect::<Vec<UserId>>();
|
||||||
let players = db.get_players(users).await;
|
let players = db.get_players(users).await;
|
||||||
|
let players = players.into_iter().map(|p| Player::new(p)).collect::<Vec<Player>>();
|
||||||
println!("{:?}", players);
|
let slots = vec![Role::Tank, Role::Dps, Role::Dps, Role::Support, Role::Support];
|
||||||
|
if let Some((team1, team2)) = mixer::mix_players(&players, slots) {
|
||||||
|
println!("Average rank {}", team1.average_rank());
|
||||||
|
println!("Average rank tank {}", team1.average_rank_role(&Role::Tank));
|
||||||
|
println!("Average rank dps {}", team1.average_rank_role(&Role::Dps));
|
||||||
|
println!("Average rank support {}", team1.average_rank_role(&Role::Support));
|
||||||
|
println!("Average rank {}", team2.average_rank());
|
||||||
|
println!("Average rank tank {}", team2.average_rank_role(&Role::Tank));
|
||||||
|
println!("Average rank dps {}", team2.average_rank_role(&Role::Dps));
|
||||||
|
println!("Average rank support {}\n", team2.average_rank_role(&Role::Support));
|
||||||
|
println!("Team 1: {:?}\n\n", team1.players.iter().map(|p| (p.0.clone().0, p.1.clone().unwrap().name.clone())).collect::<Vec<(Role, String)>>());
|
||||||
|
println!("Team 2: {:?}", team2.players.iter().map(|p| (p.0.clone().0, p.1.clone().unwrap().name.clone())).collect::<Vec<(Role, String)>>());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
println!("Fair lobby could not be mixed")
|
||||||
|
}
|
||||||
|
|
||||||
interaction.create_interaction_response(ctx.http(), |response| {
|
interaction.create_interaction_response(ctx.http(), |response| {
|
||||||
response.kind(InteractionResponseType::ChannelMessageWithSource)
|
response.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use serenity::model::application::interaction::{
|
|||||||
InteractionResponseType
|
InteractionResponseType
|
||||||
};
|
};
|
||||||
use serenity::async_trait;
|
use serenity::async_trait;
|
||||||
use serenity::http::CacheHttp;
|
|
||||||
use serenity::model::Permissions;
|
use serenity::model::Permissions;
|
||||||
use serenity::model::prelude::command::CommandOptionType;
|
use serenity::model::prelude::command::CommandOptionType;
|
||||||
use serenity::model::prelude::interaction::application_command::CommandDataOptionValue::User;
|
use serenity::model::prelude::interaction::application_command::CommandDataOptionValue::User;
|
||||||
@@ -99,12 +98,12 @@ impl MixerCommand for PreferenceCommand {
|
|||||||
|
|
||||||
match interaction.data.options.get(0).unwrap().options.get(0).unwrap().name.as_str() {
|
match interaction.data.options.get(0).unwrap().options.get(0).unwrap().name.as_str() {
|
||||||
"flex" => {
|
"flex" => {
|
||||||
db.update_player_preference(user.id, true, Role::None, Role::None, Role::None).await;
|
db.update_player_preference(user.id, true, None, None, None).await;
|
||||||
},
|
},
|
||||||
"complex" => {
|
"complex" => {
|
||||||
let role1 = Role::from_str(interaction.data.options.get(0).unwrap().options.get(0).unwrap().options.get(1).unwrap().value.as_ref().unwrap().as_str().unwrap()).unwrap();
|
let role1 = Role::from_str(interaction.data.options.get(0).unwrap().options.get(0).unwrap().options.get(1).unwrap().value.as_ref().unwrap().as_str().unwrap()).ok();
|
||||||
let role2 = Role::from_str(interaction.data.options.get(0).unwrap().options.get(0).unwrap().options.get(2).unwrap().value.as_ref().unwrap().as_str().unwrap()).unwrap();
|
let role2 = Role::from_str(interaction.data.options.get(0).unwrap().options.get(0).unwrap().options.get(2).unwrap().value.as_ref().unwrap().as_str().unwrap()).ok();
|
||||||
let role3 = Role::from_str(interaction.data.options.get(0).unwrap().options.get(0).unwrap().options.get(3).unwrap().value.as_ref().unwrap().as_str().unwrap()).unwrap();
|
let role3 = Role::from_str(interaction.data.options.get(0).unwrap().options.get(0).unwrap().options.get(3).unwrap().value.as_ref().unwrap().as_str().unwrap()).ok();
|
||||||
|
|
||||||
db.update_player_preference(user.id, false, role1, role2, role3).await;
|
db.update_player_preference(user.id, false, role1, role2, role3).await;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ impl MixerCommand for RankCommand {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let role = Role::from_str(interaction.data.options.get(0).unwrap().options.get(1).unwrap().value.as_ref().unwrap().as_str().unwrap()).unwrap();
|
let role = Role::from_str(interaction.data.options.get(0).unwrap().options.get(1).unwrap().value.as_ref().unwrap().as_str().unwrap()).ok();
|
||||||
let rank = interaction.data.options.get(0).unwrap().options.get(2).unwrap().value.as_ref().unwrap().as_u64().unwrap();
|
let rank = interaction.data.options.get(0).unwrap().options.get(2).unwrap().value.as_ref().unwrap().as_u64().unwrap();
|
||||||
|
|
||||||
if rank < 1 || rank > 5000 {
|
if rank < 1 || rank > 5000 {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ impl MixerDatabase {
|
|||||||
.expect("Could not insert player into database");
|
.expect("Could not insert player into database");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_player_rank(&self, id: UserId, role: Role, rank: f32) {
|
pub async fn update_player_rank(&self, id: UserId, role: Option<Role>, rank: f32) {
|
||||||
if !self.has_player(id).await {
|
if !self.has_player(id).await {
|
||||||
self.insert_player(id).await;
|
self.insert_player(id).await;
|
||||||
}
|
}
|
||||||
@@ -62,10 +62,10 @@ impl MixerDatabase {
|
|||||||
let mut player = self.get_player(id).await.unwrap().into_active_model();
|
let mut player = self.get_player(id).await.unwrap().into_active_model();
|
||||||
|
|
||||||
match role {
|
match role {
|
||||||
Role::Tank => player.tank = Set(rank),
|
Some(Role::Tank) => player.tank = Set(rank),
|
||||||
Role::Dps => player.dps = Set(rank),
|
Some(Role::Dps) => player.dps = Set(rank),
|
||||||
Role::Support => player.support = Set(rank),
|
Some(Role::Support) => player.support = Set(rank),
|
||||||
Role::None => return
|
None => return
|
||||||
}
|
}
|
||||||
|
|
||||||
player.update(&self.connection)
|
player.update(&self.connection)
|
||||||
@@ -73,7 +73,7 @@ impl MixerDatabase {
|
|||||||
.expect("Could not update player rank in database");
|
.expect("Could not update player rank in database");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_player_preference(&self, id: UserId, flex: bool, primary: Role, secondary: Role, tertiary: Role) {
|
pub async fn update_player_preference(&self, id: UserId, flex: bool, primary: Option<Role>, secondary: Option<Role>, tertiary: Option<Role>) {
|
||||||
if !self.has_player(id).await {
|
if !self.has_player(id).await {
|
||||||
self.insert_player(id).await;
|
self.insert_player(id).await;
|
||||||
}
|
}
|
||||||
@@ -81,9 +81,9 @@ impl MixerDatabase {
|
|||||||
let mut player = self.get_player(id).await.unwrap().into_active_model();
|
let mut player = self.get_player(id).await.unwrap().into_active_model();
|
||||||
|
|
||||||
player.flex = Set(flex);
|
player.flex = Set(flex);
|
||||||
player.primary_role = Set(primary.into());
|
player.primary_role = Set(Role::option_to_i32(primary));
|
||||||
player.secondary_role = Set(secondary.into());
|
player.secondary_role = Set(Role::option_to_i32(secondary));
|
||||||
player.tertiary_role = Set(tertiary.into());
|
player.tertiary_role = Set(Role::option_to_i32(tertiary));
|
||||||
|
|
||||||
player.update(&self.connection)
|
player.update(&self.connection)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,206 +1,76 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use crate::mixer::role::Role;
|
use crate::mixer::role::Role;
|
||||||
use crate::database::models::player::{Model as Player, Model};
|
use crate::mixer::player::Player;
|
||||||
use crate::mixer::role;
|
use crate::mixer::team::Team;
|
||||||
|
|
||||||
pub struct Mixer {
|
|
||||||
players: Vec<Player>,
|
pub fn mix_players(players: &Vec<Player>, slots: Vec<Role>) -> Option<(Team, Team)> {
|
||||||
|
let mut players = players.clone();
|
||||||
|
let mut team1 = Team::new(slots.clone());
|
||||||
|
let mut team2 = Team::new(slots.clone());
|
||||||
|
|
||||||
|
for _ in 0..(slots.len()*2) {
|
||||||
|
let mut priorities = calculate_priorities(&players, &team1, &team2);
|
||||||
|
priorities.sort_by(|(_, _, p1), (_, _, p2)| p2.partial_cmp(p1).unwrap());
|
||||||
|
|
||||||
|
for (player, role, _) in priorities {
|
||||||
|
if team1.full_rank() > team2.full_rank() {
|
||||||
|
if team2.has_slot(&role) {
|
||||||
|
team2.add_player(&player, &role);
|
||||||
|
players.retain(|p| p.id != player.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if team1.has_slot(&role) {
|
||||||
|
team1.add_player(&player, &role);
|
||||||
|
players.retain(|p| p.id != player.id);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct MixedPlayer {
|
|
||||||
pub role: Role,
|
|
||||||
pub player: Player,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mixer {
|
|
||||||
pub fn new(players: Vec<Player>) -> Self {
|
|
||||||
Self {
|
|
||||||
players
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_teams(self) -> Option<(Vec<MixedPlayer>, Vec<MixedPlayer>)> {
|
if team1.count() < slots.len() || team2.count() < slots.len() {
|
||||||
let mut players = self.players.clone();
|
|
||||||
|
|
||||||
// TODO! pass in the original priorities
|
|
||||||
// let original_priorities = players.iter().cloned().map(|player| (player.id, 1)).collect::<HashMap<i32, i32>>();
|
|
||||||
|
|
||||||
let mut team1 = Vec::new();
|
|
||||||
let mut team2 = Vec::new();
|
|
||||||
|
|
||||||
for i in 0..10 {
|
|
||||||
let priorities = self.get_players_priority(&players, &team1, &team2);
|
|
||||||
|
|
||||||
if let Some(player) = self.get_highest_priority_player(&players, &priorities) {
|
|
||||||
if i % 2 == 0 {
|
|
||||||
team1.push(player.clone());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
team2.push(player.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
players.retain(|p| p.id != player.player.id);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Some((team1, team2))
|
Some((team1, team2))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_players_priority(&self, players: &Vec<Player>, team1: &Vec<MixedPlayer>, team2: &Vec<MixedPlayer>) -> HashMap<(Role, i32), f32> {
|
|
||||||
let mut priorities = HashMap::new();
|
|
||||||
|
|
||||||
if Self::calculate_priorities(players, team1, team2, Role::Tank, &mut priorities) {
|
fn calculate_priorities(players: &Vec<Player>, team1: &Team, team2: &Team) -> Vec<(Player, Role, f32)> {
|
||||||
return priorities;
|
let mut priorities = Vec::new();
|
||||||
|
|
||||||
|
for player in players {
|
||||||
|
for (role, priority) in player.base_priority() {
|
||||||
|
priorities.push((player.clone(), role, priority));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if Self::calculate_priorities(players, team1, team2, Role::Dps, &mut priorities) {
|
for item in &mut priorities {
|
||||||
return priorities;
|
let (player, role, _) = item;
|
||||||
}
|
let empty_teams = team1.count_role(role) == 0 && team2.count_role(role) == 0;
|
||||||
|
|
||||||
if Self::calculate_priorities(players, team1, team2, Role::Support, &mut priorities) {
|
let role_diff_rank = 1.0 + {
|
||||||
return priorities;
|
if empty_teams {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
(player.ranks[role] - (team1.full_rank_role(role) - team2.full_rank_role(role)).abs()).abs()
|
||||||
|
}
|
||||||
|
} as f32;
|
||||||
|
|
||||||
|
let sum_average_rank = team1.average_rank_role(role) + team2.average_rank_role(role);
|
||||||
|
let role_diff_avg_rank = 1.0 + {
|
||||||
|
if empty_teams {
|
||||||
|
let filtered_players = players.iter().filter(|player| player.priority_roles.contains(&Some(role.clone())) || player.flex).collect::<Vec<&Player>>();
|
||||||
|
(player.ranks[role] - filtered_players.iter().map(|player| player.ranks[role]).sum::<f32>() / filtered_players.len() as f32).abs()
|
||||||
|
} else {
|
||||||
|
(player.ranks[role] - sum_average_rank).abs()
|
||||||
|
}
|
||||||
|
} as f32;
|
||||||
|
|
||||||
|
let complex_coefficient = role_diff_rank * role_diff_avg_rank;
|
||||||
|
item.2 *= player.ranks[role] / complex_coefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
priorities
|
priorities
|
||||||
}
|
}
|
||||||
|
|
||||||
fn average_rank(team1: &Vec<MixedPlayer>, team2: &Vec<MixedPlayer>, role: Role) -> f32 {
|
|
||||||
let selected_players = team1.iter().filter(|player| player.role == role)
|
|
||||||
.chain(team2.iter().filter(|player| player.role == role)).clone().collect::<Vec<&MixedPlayer>>();
|
|
||||||
|
|
||||||
selected_players.iter().map(|player| {
|
|
||||||
match player.role {
|
|
||||||
Role::Tank => player.player.tank,
|
|
||||||
Role::Dps => player.player.dps,
|
|
||||||
Role::Support => player.player.support,
|
|
||||||
_ => 0.0
|
|
||||||
}
|
|
||||||
}).sum::<f32>() / selected_players.len() as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_priorities(players: &Vec<Model>, team1: &Vec<MixedPlayer>, team2: &Vec<MixedPlayer>, expected: Role, priorities: &mut HashMap<(Role, i32), f32>) -> bool {
|
|
||||||
let group_coefficients = vec![10.0, 50.0, 100.0, 150.0, 125.0];
|
|
||||||
|
|
||||||
let team1_roles = team1.iter().map(|player| player.role).collect::<Vec<Role>>();
|
|
||||||
let team2_roles = team2.iter().map(|player| player.role).collect::<Vec<Role>>();
|
|
||||||
|
|
||||||
let team1_role_count = team1_roles.iter().filter(|role| **role == expected).count();
|
|
||||||
let team2_role_count = team2_roles.iter().filter(|role| **role == expected).count();
|
|
||||||
let prioritize_role = team1_role_count == 0 || team2_role_count == 0;
|
|
||||||
|
|
||||||
if !prioritize_role {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let otp = players.iter().filter(|player|
|
|
||||||
player.primary_role == expected && player.secondary_role == Role::None && player.tertiary_role == Role::None
|
|
||||||
).cloned().collect::<Vec<Player>>();
|
|
||||||
|
|
||||||
let primary = players.iter().filter(|player|
|
|
||||||
player.primary_role == expected && (player.secondary_role != Role::None || player.tertiary_role == Role::None)
|
|
||||||
).cloned().collect::<Vec<Player>>();
|
|
||||||
|
|
||||||
let secondary = players.iter().filter(|player|
|
|
||||||
player.secondary_role == expected
|
|
||||||
).cloned().collect::<Vec<Player>>();
|
|
||||||
|
|
||||||
let flex = players.iter().filter(|player|
|
|
||||||
player.flex
|
|
||||||
).cloned().collect::<Vec<Player>>();
|
|
||||||
|
|
||||||
let tertiary = players.iter().filter(|player|
|
|
||||||
player.tertiary_role == expected
|
|
||||||
).cloned().collect::<Vec<Player>>();
|
|
||||||
|
|
||||||
|
|
||||||
let average_tank_skill = Self::average_rank(team1, team2, expected);
|
|
||||||
|
|
||||||
for player in otp {
|
|
||||||
let rank = match expected {
|
|
||||||
Role::Tank => player.tank,
|
|
||||||
Role::Dps => player.dps,
|
|
||||||
Role::Support => player.support,
|
|
||||||
_ => 0.0
|
|
||||||
};
|
|
||||||
let skill_difference = (rank - average_tank_skill).abs();
|
|
||||||
|
|
||||||
priorities.insert((expected, player.id), rank / group_coefficients[0] / (skill_difference + 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
for player in primary {
|
|
||||||
let rank = match expected {
|
|
||||||
Role::Tank => player.tank,
|
|
||||||
Role::Dps => player.dps,
|
|
||||||
Role::Support => player.support,
|
|
||||||
_ => 0.0
|
|
||||||
};
|
|
||||||
let skill_difference = (rank - average_tank_skill).abs();
|
|
||||||
|
|
||||||
priorities.insert((expected, player.id), rank / group_coefficients[1] / (skill_difference + 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
for player in secondary {
|
|
||||||
let rank = match expected {
|
|
||||||
Role::Tank => player.tank,
|
|
||||||
Role::Dps => player.dps,
|
|
||||||
Role::Support => player.support,
|
|
||||||
_ => 0.0
|
|
||||||
};
|
|
||||||
let skill_difference = (rank - average_tank_skill).abs();
|
|
||||||
|
|
||||||
priorities.insert((expected, player.id), rank / group_coefficients[2] / (skill_difference + 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
for player in tertiary {
|
|
||||||
let rank = match expected {
|
|
||||||
Role::Tank => player.tank,
|
|
||||||
Role::Dps => player.dps,
|
|
||||||
Role::Support => player.support,
|
|
||||||
_ => 0.0
|
|
||||||
};
|
|
||||||
let skill_difference = (rank - average_tank_skill).abs();
|
|
||||||
|
|
||||||
priorities.insert((expected, player.id), rank / group_coefficients[3] / (skill_difference + 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
for player in flex {
|
|
||||||
let rank = match expected {
|
|
||||||
Role::Tank => player.tank,
|
|
||||||
Role::Dps => player.dps,
|
|
||||||
Role::Support => player.support,
|
|
||||||
_ => 0.0
|
|
||||||
};
|
|
||||||
let skill_difference = (rank - average_tank_skill).abs();
|
|
||||||
|
|
||||||
priorities.insert((expected, player.id), rank / group_coefficients[4] / (skill_difference + 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_highest_priority_player(&self, players: &Vec<Model>, priorities: &HashMap<(Role, i32), f32>) -> Option<MixedPlayer> {
|
|
||||||
let mut highest_priority = 0.0;
|
|
||||||
let mut highest_priority_player = None;
|
|
||||||
|
|
||||||
for role in vec![Role::Tank, Role::Dps, Role::Support] {
|
|
||||||
for player in players {
|
|
||||||
if let Some(priority) = priorities.get(&(role, player.id)) {
|
|
||||||
if *priority > highest_priority {
|
|
||||||
highest_priority = *priority;
|
|
||||||
highest_priority_player = Some(MixedPlayer {
|
|
||||||
role,
|
|
||||||
player: player.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
highest_priority_player
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
pub mod mixer;
|
pub mod mixer;
|
||||||
pub mod role;
|
pub mod role;
|
||||||
|
pub mod player;
|
||||||
|
pub mod team;
|
||||||
|
|||||||
55
src/mixer/player.rs
Normal file
55
src/mixer/player.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::database::models::player::Model;
|
||||||
|
use crate::mixer::role::Role;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Player {
|
||||||
|
pub(crate) id: i32,
|
||||||
|
pub(crate) name: String,
|
||||||
|
|
||||||
|
pub(crate) ranks: HashMap<Role, f32>,
|
||||||
|
pub(crate) flex: bool,
|
||||||
|
pub(crate) priority_roles: Vec<Option<Role>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
pub fn new(model: Model) -> Self {
|
||||||
|
Self {
|
||||||
|
id: model.id,
|
||||||
|
name: model.bn_name,
|
||||||
|
ranks: vec![(Role::Tank, model.tank), (Role::Dps, model.dps), (Role::Support, model.support)].into_iter().collect(),
|
||||||
|
flex: model.flex,
|
||||||
|
priority_roles: vec![model.primary_role, model.secondary_role, model.tertiary_role].into_iter().map(|role| {
|
||||||
|
match role {
|
||||||
|
-1 => None,
|
||||||
|
_ => Some(Role::from(role))
|
||||||
|
}
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_priority(&self) -> HashMap<Role, f32> {
|
||||||
|
let mut priorities = HashMap::new();
|
||||||
|
|
||||||
|
if self.flex {
|
||||||
|
for role in Role::iter() {
|
||||||
|
priorities.insert(role, (self.priority_roles.len() / 2) as f32);
|
||||||
|
}
|
||||||
|
|
||||||
|
return priorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = self.priority_roles.iter().filter(|role| role.is_some()).count() as f32;
|
||||||
|
let denominator = count * (count + 1.0) * (2.0 * count + 1.0) / 6.0;
|
||||||
|
let priority_points = 100.0;
|
||||||
|
|
||||||
|
for (i, role) in self.priority_roles.iter().enumerate() {
|
||||||
|
if let Some(role) = role {
|
||||||
|
priorities.insert(role.clone(), priority_points * (count - i as f32)*(count - i as f32) / denominator as f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
priorities
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,67 +1,11 @@
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::Hash;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub enum Role {
|
pub enum Role {
|
||||||
Tank,
|
Tank,
|
||||||
Dps,
|
Dps,
|
||||||
Support,
|
Support
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<i32> for Role {
|
|
||||||
fn eq(&self, other: &i32) -> bool {
|
|
||||||
match self {
|
|
||||||
Role::Tank => *other == 0,
|
|
||||||
Role::Dps => *other == 1,
|
|
||||||
Role::Support => *other == 2,
|
|
||||||
Role::None => *other == -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<Role> for i32 {
|
|
||||||
fn eq(&self, other: &Role) -> bool {
|
|
||||||
match other {
|
|
||||||
Role::Tank => *self == 0,
|
|
||||||
Role::Dps => *self == 1,
|
|
||||||
Role::Support => *self == 2,
|
|
||||||
Role::None => *self == -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Role> for i32 {
|
|
||||||
fn from(role: &Role) -> Self {
|
|
||||||
match role {
|
|
||||||
Role::Tank => 0,
|
|
||||||
Role::Dps => 1,
|
|
||||||
Role::Support => 2,
|
|
||||||
Role::None => -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for Role {
|
|
||||||
fn from(i: i32) -> Self {
|
|
||||||
match i {
|
|
||||||
0 => Role::Tank,
|
|
||||||
1 => Role::Dps,
|
|
||||||
2 => Role::Support,
|
|
||||||
_ => Role::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<i32> for Role {
|
|
||||||
fn into(self) -> i32 {
|
|
||||||
match self {
|
|
||||||
Role::Tank => 0,
|
|
||||||
Role::Dps => 1,
|
|
||||||
Role::Support => 2,
|
|
||||||
Role::None => -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Role {
|
impl FromStr for Role {
|
||||||
@@ -72,9 +16,41 @@ impl FromStr for Role {
|
|||||||
"tank" => Ok(Role::Tank),
|
"tank" => Ok(Role::Tank),
|
||||||
"dps" => Ok(Role::Dps),
|
"dps" => Ok(Role::Dps),
|
||||||
"support" => Ok(Role::Support),
|
"support" => Ok(Role::Support),
|
||||||
"none" => Ok(Role::None),
|
_ => Err(())
|
||||||
_ => Ok(Role::None)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<i32> for Role {
|
||||||
|
fn from(i: i32) -> Self {
|
||||||
|
match i {
|
||||||
|
0 => Role::Tank,
|
||||||
|
1 => Role::Dps,
|
||||||
|
2 => Role::Support,
|
||||||
|
_ => panic!("Invalid role number")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<i32> for Role {
|
||||||
|
fn eq(&self, other: &i32) -> bool {
|
||||||
|
match self {
|
||||||
|
Role::Tank => *other == 0,
|
||||||
|
Role::Dps => *other == 1,
|
||||||
|
Role::Support => *other == 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Role {
|
||||||
|
pub fn iter() -> impl Iterator<Item = Role> {
|
||||||
|
vec![Role::Tank, Role::Dps, Role::Support].into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn option_to_i32(role: Option<Role>) -> i32 {
|
||||||
|
match role {
|
||||||
|
Some(role) => role as i32,
|
||||||
|
None => -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/mixer/team.rs
Normal file
79
src/mixer/team.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::mixer::player::Player;
|
||||||
|
use crate::mixer::role::Role;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Team {
|
||||||
|
pub players: HashMap<(Role, i32), Option<Player>>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Team {
|
||||||
|
pub fn new(slots: Vec<Role>) -> Self {
|
||||||
|
Self {
|
||||||
|
players: {
|
||||||
|
let mut players = HashMap::new();
|
||||||
|
for role in Role::iter() {
|
||||||
|
for i in 0..slots.iter().filter(|slot| **slot == role).count() {
|
||||||
|
players.insert((role, i as i32), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
players
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
self.players.iter().filter(|(_, player)| player.is_some()).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count_role(&self, role: &Role) -> usize {
|
||||||
|
self.players.iter().filter(|((r, _), player)| r == role && player.is_some()).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn full_rank(&self) -> f32 {
|
||||||
|
self.players.iter().map(|((role, _), player)| {
|
||||||
|
if let Some(player) = player {
|
||||||
|
player.ranks.get(role).unwrap().clone()
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}).sum::<f32>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn average_rank(&self) -> f32 {
|
||||||
|
if self.players.len() == 0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.full_rank() / self.players.len() as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn full_rank_role(&self, role: &Role) -> f32 {
|
||||||
|
self.players.iter().filter(|((r, _), _)| r == role).map(|((_, _), player)| {
|
||||||
|
if let Some(player) = player {
|
||||||
|
player.ranks.get(&role).unwrap().clone()
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}).sum::<f32>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn average_rank_role(&self, role: &Role) -> f32 {
|
||||||
|
let count = self.players.iter().filter(|((r, _), _)| r == role).count();
|
||||||
|
if count == 0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.full_rank_role(role) / count as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_slot(&self, role: &Role) -> bool {
|
||||||
|
self.players.iter().filter(|((r, _), _)| r == role).any(|(_, player)| player.is_none())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_player(&mut self, player: &Player, role: &Role) {
|
||||||
|
let slot = self.players.iter().filter(|((r, _), _)| r == role).find(|(_, player)| player.is_none()).unwrap().0.clone();
|
||||||
|
self.players.insert(slot, Some(player.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user