Skip to content
This repository has been archived by the owner on Mar 18, 2023. It is now read-only.

Commit

Permalink
🍻 Implemented sorting by column, closes #33
Browse files Browse the repository at this point in the history
  • Loading branch information
eigenein committed Jun 30, 2021
1 parent 1170ec3 commit 6d9bdd2
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 39 deletions.
4 changes: 2 additions & 2 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub struct Vehicle {
pub imported_at: DateTime<Utc>,
}

#[derive(Deserialize, Serialize, Clone, Debug, Copy)]
#[derive(Deserialize, Serialize, Clone, Debug, Copy, Ord, Eq, PartialEq, PartialOrd)]
pub enum Nation {
#[serde(rename = "ussr")]
Ussr,
Expand Down Expand Up @@ -136,7 +136,7 @@ pub enum Nation {
Other,
}

#[derive(Deserialize, Serialize, Clone, Debug, Copy)]
#[derive(Deserialize, Serialize, Clone, Debug, Copy, Ord, Eq, PartialEq, PartialOrd)]
pub enum TankType {
#[serde(rename = "lightTank")]
Light,
Expand Down
101 changes: 92 additions & 9 deletions src/web/player/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ use lru_time_cache::LruCache;
use serde::{Deserialize, Serialize};
use tide::Request;

use crate::models::{AccountInfo, AllStatistics, TankSnapshot};
use crate::models::{AccountInfo, AllStatistics, TankSnapshot, Vehicle};
use crate::statistics::wilson_score_interval_90;
use crate::wargaming::WargamingApi;
use crate::web::state::State;
use std::cmp::Ordering;

lazy_static! {
static ref ACCOUNT_INFO_CACHE: Arc<Mutex<LruCache<i32, Arc<AccountInfo>>>> =
Expand All @@ -40,7 +42,7 @@ pub struct PlayerViewModel {
pub query: Query,
pub warn_no_previous_account_info: bool,
pub statistics: AllStatistics,
pub tank_snapshots: Vec<TankSnapshot>,
pub rows: Vec<TankRow>,
}

impl PlayerViewModel {
Expand Down Expand Up @@ -84,9 +86,27 @@ impl PlayerViewModel {
} else {
Vec::new()
};
let mut tank_snapshots =
Self::subtract_tank_snapshots(actual_tanks.to_vec(), previous_tanks);
tank_snapshots.sort_by_key(|snapshot| -snapshot.all_statistics.battles);
let tank_snapshots = Self::subtract_tank_snapshots(actual_tanks.to_vec(), previous_tanks);

let mut rows: Vec<TankRow> = Vec::new();
for snapshot in tank_snapshots.into_iter() {
let stats = &snapshot.all_statistics;
let vehicle = state.get_vehicle(snapshot.tank_id).await?;
let win_rate = stats.wins as f64 / stats.battles as f64;
let true_win_rate = wilson_score_interval_90(stats.battles, stats.wins);
rows.push(TankRow {
win_rate,
true_win_rate,
damage_per_battle: stats.damage_dealt as f64 / stats.battles as f64,
survival_rate: stats.survived_battles as f64 / stats.battles as f64,
all_statistics: snapshot.all_statistics,
gold_per_battle: 10.0 + vehicle.tier as f64 * win_rate,
true_gold_per_battle: 10.0 + vehicle.tier as f64 * true_win_rate.0,
vehicle,
});
}
Self::sort_vehicles(&mut rows, query.sort_by);

let warn_no_previous_account_info = previous_account_info.is_none();
let statistics = actual_statistics
.sub(&previous_account_info.map_or_else(Default::default, |info| info.statistics.all));
Expand All @@ -103,7 +123,7 @@ impl PlayerViewModel {
query,
warn_no_previous_account_info,
statistics,
tank_snapshots,
rows,
total_tanks,
})
}
Expand Down Expand Up @@ -180,6 +200,59 @@ impl PlayerViewModel {
.collect::<Vec<TankSnapshot>>()
}

fn sort_vehicles(rows: &mut Vec<TankRow>, sort_by: SortBy) {
match sort_by {
SortBy::Battles => rows.sort_unstable_by_key(|row| -row.all_statistics.battles),
SortBy::Wins => rows.sort_unstable_by_key(|row| -row.all_statistics.wins),
SortBy::Nation => rows.sort_unstable_by_key(|row| row.vehicle.nation),
SortBy::DamageDealt => {
rows.sort_unstable_by_key(|row| -row.all_statistics.damage_dealt)
}
SortBy::DamagePerBattle => rows.sort_unstable_by(|left, right| {
right
.damage_per_battle
.partial_cmp(&left.damage_per_battle)
.unwrap_or(Ordering::Equal)
}),
SortBy::Tier => rows.sort_unstable_by_key(|row| -row.vehicle.tier),
SortBy::VehicleType => rows.sort_unstable_by_key(|row| row.vehicle.type_),
SortBy::WinRate => rows.sort_unstable_by(|left, right| {
right
.win_rate
.partial_cmp(&left.win_rate)
.unwrap_or(Ordering::Equal)
}),
SortBy::TrueWinRate => rows.sort_unstable_by(|left, right| {
right
.true_win_rate
.0
.partial_cmp(&left.true_win_rate.0)
.unwrap_or(Ordering::Equal)
}),
SortBy::Gold => rows.sort_unstable_by(|left, right| {
right
.gold_per_battle
.partial_cmp(&left.gold_per_battle)
.unwrap_or(Ordering::Equal)
}),
SortBy::TrueGold => rows.sort_unstable_by(|left, right| {
right
.true_gold_per_battle
.partial_cmp(&left.true_gold_per_battle)
.unwrap_or(Ordering::Equal)
}),
SortBy::SurvivedBattles => {
rows.sort_unstable_by_key(|row| -row.all_statistics.survived_battles)
}
SortBy::SurvivalRate => rows.sort_unstable_by(|left, right| {
right
.survival_rate
.partial_cmp(&left.survival_rate)
.unwrap_or(Ordering::Equal)
}),
}
}

/// Inserts account if it doesn't exist. The rest is updated by [`crate::crawler`].
async fn insert_account_or_ignore(
state: &State,
Expand Down Expand Up @@ -208,6 +281,17 @@ impl PlayerViewModel {
}
}

pub struct TankRow {
pub vehicle: Arc<Vehicle>,
pub all_statistics: AllStatistics,
pub win_rate: f64,
pub true_win_rate: (f64, f64),
pub damage_per_battle: f64,
pub survival_rate: f64,
pub gold_per_battle: f64,
pub true_gold_per_battle: f64,
}

#[derive(Deserialize, Serialize, Clone, Copy)]
pub struct Query {
#[serde(default = "default_period", with = "humantime_serde")]
Expand Down Expand Up @@ -244,17 +328,16 @@ impl Query {
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum SortBy {
Vehicle,
Battles,
Tier,
Nation,
VehicleType,
WonBattles,
Wins,
WinRate,
TrueWinRate,
Gold,
TrueGold,
Damage,
DamageDealt,
DamagePerBattle,
SurvivedBattles,
SurvivalRate,
Expand Down
49 changes: 21 additions & 28 deletions src/web/player/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,73 +250,66 @@ pub async fn get(request: tide::Request<State>) -> tide::Result {
}
}

@if !model.tank_snapshots.is_empty() {
@if !model.rows.is_empty() {
div.box {
div.table-container {
table#vehicles.table.is-hoverable.is-striped.is-fullwidth {
thead {
tr {
(render_vehicles_th(&model.query, SortBy::Vehicle, html! { "Техника" })?)
th { "Техника" }
(render_vehicles_th(&model.query, SortBy::Tier, html! { "Уровень" })?)
(render_vehicles_th(&model.query, SortBy::Nation, html! { "Нация" })?)
(render_vehicles_th(&model.query, SortBy::VehicleType, html! { "Тип" })?)
(render_vehicles_th(&model.query, SortBy::Battles, html! { "Бои" })?)
(render_vehicles_th(&model.query, SortBy::WonBattles, html! { "Победы" })?)
(render_vehicles_th(&model.query, SortBy::Wins, html! { "Победы" })?)
(render_vehicles_th(&model.query, SortBy::WinRate, html! { "Текущий процент побед" })?)
(render_vehicles_th(&model.query, SortBy::TrueWinRate, html! { "Ожидаемый процент побед" })?)
(render_vehicles_th(&model.query, SortBy::Gold, html! { abbr title="Текущий доход от золотых бустеров за бой, если они были установлены" { "Заработанное золото" } })?)
(render_vehicles_th(&model.query, SortBy::TrueGold, html! { abbr title="Средняя ожидаемая доходность золотого бустера за бой" { "Ожидаемое золото" } })?)
(render_vehicles_th(&model.query, SortBy::Damage, html! { "Ущерб" })?)
(render_vehicles_th(&model.query, SortBy::DamageDealt, html! { "Ущерб" })?)
(render_vehicles_th(&model.query, SortBy::DamagePerBattle, html! { "Ущерб за бой" })?)
(render_vehicles_th(&model.query, SortBy::SurvivedBattles, html! { "Выжил" })?)
(render_vehicles_th(&model.query, SortBy::SurvivalRate, html! { "Выживаемость" })?)
}
}
tbody {
@for snapshot in &model.tank_snapshots {
@let vehicle = state.get_vehicle(snapshot.tank_id).await?;
@let statistics = &snapshot.all_statistics;
@let win_rate = statistics.wins as f64 / statistics.battles as f64;
@let (estimated_win_rate, win_rate_margin) = wilson_score_interval_90(snapshot.all_statistics.battles, snapshot.all_statistics.wins);
@let survival_percentage = 100.0 * (statistics.survived_battles as f64) / (statistics.battles as f64);
@let mean_damage_dealt = statistics.damage_dealt as f64 / statistics.battles as f64;

@for row in &model.rows {
tr {
th.is-white-space-nowrap { (render_vehicle_name(&vehicle)) }
td.has-text-centered { strong { (render_tier(vehicle.tier)) } }
td.has-text-centered { (render_nation(&vehicle.nation)) }
td { (format!("{:?}", vehicle.type_)) }
td { (snapshot.all_statistics.battles) }
td { (snapshot.all_statistics.wins) }
td.has-text-info { strong { (render_f64(100.0 * win_rate, 1)) "%" } }
th.is-white-space-nowrap { (render_vehicle_name(&row.vehicle)) }
td.has-text-centered { strong { (render_tier(row.vehicle.tier)) } }
td.has-text-centered { (render_nation(&row.vehicle.nation)) }
td { (format!("{:?}", row.vehicle.type_)) }
td { (row.all_statistics.battles) }
td { (row.all_statistics.wins) }
td.has-text-info { strong { (render_f64(100.0 * row.win_rate, 1)) "%" } }
td.has-text-centered.is-white-space-nowrap {
strong { (render_f64(100.0 * estimated_win_rate, 1)) "%" }
strong { (render_f64(100.0 * row.true_win_rate.0, 1)) "%" }
" ±"
(render_f64(win_rate_margin * 100.0, 1))
(render_f64(row.true_win_rate.1 * 100.0, 1))
}
td {
span.icon-text.is-flex-wrap-nowrap {
span.icon.has-text-warning-dark { i.fas.fa-coins {} }
span { strong { (render_f64(10.0 + vehicle.tier as f64 * win_rate, 1)) } }
span { strong { (render_f64(row.gold_per_battle, 1)) } }
}
}
td.is-white-space-nowrap {
span.icon-text.is-flex-wrap-nowrap {
span.icon.has-text-warning-dark { i.fas.fa-coins {} }
span {
strong { (render_f64(10.0 + vehicle.tier as f64 * estimated_win_rate, 1)) }
strong { (render_f64(row.true_gold_per_battle, 1)) }
" ±"
(render_f64(vehicle.tier as f64 * win_rate_margin, 1))
(render_f64(row.vehicle.tier as f64 * row.true_win_rate.1, 1))
}
}
}
td { (snapshot.all_statistics.damage_dealt) }
td { (render_f64(mean_damage_dealt, 0)) }
td { (snapshot.all_statistics.survived_battles) }
td { (row.all_statistics.damage_dealt) }
td { (render_f64(row.damage_per_battle, 0)) }
td { (row.all_statistics.survived_battles) }
td {
span.icon-text.is-flex-wrap-nowrap {
span.icon { i.fas.fa-heart.has-text-danger {} }
span { (render_f64(survival_percentage, 0)) "%" }
span { (render_f64(100.0 * row.survival_rate, 0)) "%" }
}
}
}
Expand Down

0 comments on commit 6d9bdd2

Please sign in to comment.