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

Commit 6d9bdd2

Browse files
committed
🍻 Implemented sorting by column, closes #33
1 parent 1170ec3 commit 6d9bdd2

File tree

3 files changed

+115
-39
lines changed

3 files changed

+115
-39
lines changed

src/models.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ pub struct Vehicle {
106106
pub imported_at: DateTime<Utc>,
107107
}
108108

109-
#[derive(Deserialize, Serialize, Clone, Debug, Copy)]
109+
#[derive(Deserialize, Serialize, Clone, Debug, Copy, Ord, Eq, PartialEq, PartialOrd)]
110110
pub enum Nation {
111111
#[serde(rename = "ussr")]
112112
Ussr,
@@ -136,7 +136,7 @@ pub enum Nation {
136136
Other,
137137
}
138138

139-
#[derive(Deserialize, Serialize, Clone, Debug, Copy)]
139+
#[derive(Deserialize, Serialize, Clone, Debug, Copy, Ord, Eq, PartialEq, PartialOrd)]
140140
pub enum TankType {
141141
#[serde(rename = "lightTank")]
142142
Light,

src/web/player/models.rs

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ use lru_time_cache::LruCache;
1111
use serde::{Deserialize, Serialize};
1212
use tide::Request;
1313

14-
use crate::models::{AccountInfo, AllStatistics, TankSnapshot};
14+
use crate::models::{AccountInfo, AllStatistics, TankSnapshot, Vehicle};
15+
use crate::statistics::wilson_score_interval_90;
1516
use crate::wargaming::WargamingApi;
1617
use crate::web::state::State;
18+
use std::cmp::Ordering;
1719

1820
lazy_static! {
1921
static ref ACCOUNT_INFO_CACHE: Arc<Mutex<LruCache<i32, Arc<AccountInfo>>>> =
@@ -40,7 +42,7 @@ pub struct PlayerViewModel {
4042
pub query: Query,
4143
pub warn_no_previous_account_info: bool,
4244
pub statistics: AllStatistics,
43-
pub tank_snapshots: Vec<TankSnapshot>,
45+
pub rows: Vec<TankRow>,
4446
}
4547

4648
impl PlayerViewModel {
@@ -84,9 +86,27 @@ impl PlayerViewModel {
8486
} else {
8587
Vec::new()
8688
};
87-
let mut tank_snapshots =
88-
Self::subtract_tank_snapshots(actual_tanks.to_vec(), previous_tanks);
89-
tank_snapshots.sort_by_key(|snapshot| -snapshot.all_statistics.battles);
89+
let tank_snapshots = Self::subtract_tank_snapshots(actual_tanks.to_vec(), previous_tanks);
90+
91+
let mut rows: Vec<TankRow> = Vec::new();
92+
for snapshot in tank_snapshots.into_iter() {
93+
let stats = &snapshot.all_statistics;
94+
let vehicle = state.get_vehicle(snapshot.tank_id).await?;
95+
let win_rate = stats.wins as f64 / stats.battles as f64;
96+
let true_win_rate = wilson_score_interval_90(stats.battles, stats.wins);
97+
rows.push(TankRow {
98+
win_rate,
99+
true_win_rate,
100+
damage_per_battle: stats.damage_dealt as f64 / stats.battles as f64,
101+
survival_rate: stats.survived_battles as f64 / stats.battles as f64,
102+
all_statistics: snapshot.all_statistics,
103+
gold_per_battle: 10.0 + vehicle.tier as f64 * win_rate,
104+
true_gold_per_battle: 10.0 + vehicle.tier as f64 * true_win_rate.0,
105+
vehicle,
106+
});
107+
}
108+
Self::sort_vehicles(&mut rows, query.sort_by);
109+
90110
let warn_no_previous_account_info = previous_account_info.is_none();
91111
let statistics = actual_statistics
92112
.sub(&previous_account_info.map_or_else(Default::default, |info| info.statistics.all));
@@ -103,7 +123,7 @@ impl PlayerViewModel {
103123
query,
104124
warn_no_previous_account_info,
105125
statistics,
106-
tank_snapshots,
126+
rows,
107127
total_tanks,
108128
})
109129
}
@@ -180,6 +200,59 @@ impl PlayerViewModel {
180200
.collect::<Vec<TankSnapshot>>()
181201
}
182202

203+
fn sort_vehicles(rows: &mut Vec<TankRow>, sort_by: SortBy) {
204+
match sort_by {
205+
SortBy::Battles => rows.sort_unstable_by_key(|row| -row.all_statistics.battles),
206+
SortBy::Wins => rows.sort_unstable_by_key(|row| -row.all_statistics.wins),
207+
SortBy::Nation => rows.sort_unstable_by_key(|row| row.vehicle.nation),
208+
SortBy::DamageDealt => {
209+
rows.sort_unstable_by_key(|row| -row.all_statistics.damage_dealt)
210+
}
211+
SortBy::DamagePerBattle => rows.sort_unstable_by(|left, right| {
212+
right
213+
.damage_per_battle
214+
.partial_cmp(&left.damage_per_battle)
215+
.unwrap_or(Ordering::Equal)
216+
}),
217+
SortBy::Tier => rows.sort_unstable_by_key(|row| -row.vehicle.tier),
218+
SortBy::VehicleType => rows.sort_unstable_by_key(|row| row.vehicle.type_),
219+
SortBy::WinRate => rows.sort_unstable_by(|left, right| {
220+
right
221+
.win_rate
222+
.partial_cmp(&left.win_rate)
223+
.unwrap_or(Ordering::Equal)
224+
}),
225+
SortBy::TrueWinRate => rows.sort_unstable_by(|left, right| {
226+
right
227+
.true_win_rate
228+
.0
229+
.partial_cmp(&left.true_win_rate.0)
230+
.unwrap_or(Ordering::Equal)
231+
}),
232+
SortBy::Gold => rows.sort_unstable_by(|left, right| {
233+
right
234+
.gold_per_battle
235+
.partial_cmp(&left.gold_per_battle)
236+
.unwrap_or(Ordering::Equal)
237+
}),
238+
SortBy::TrueGold => rows.sort_unstable_by(|left, right| {
239+
right
240+
.true_gold_per_battle
241+
.partial_cmp(&left.true_gold_per_battle)
242+
.unwrap_or(Ordering::Equal)
243+
}),
244+
SortBy::SurvivedBattles => {
245+
rows.sort_unstable_by_key(|row| -row.all_statistics.survived_battles)
246+
}
247+
SortBy::SurvivalRate => rows.sort_unstable_by(|left, right| {
248+
right
249+
.survival_rate
250+
.partial_cmp(&left.survival_rate)
251+
.unwrap_or(Ordering::Equal)
252+
}),
253+
}
254+
}
255+
183256
/// Inserts account if it doesn't exist. The rest is updated by [`crate::crawler`].
184257
async fn insert_account_or_ignore(
185258
state: &State,
@@ -208,6 +281,17 @@ impl PlayerViewModel {
208281
}
209282
}
210283

284+
pub struct TankRow {
285+
pub vehicle: Arc<Vehicle>,
286+
pub all_statistics: AllStatistics,
287+
pub win_rate: f64,
288+
pub true_win_rate: (f64, f64),
289+
pub damage_per_battle: f64,
290+
pub survival_rate: f64,
291+
pub gold_per_battle: f64,
292+
pub true_gold_per_battle: f64,
293+
}
294+
211295
#[derive(Deserialize, Serialize, Clone, Copy)]
212296
pub struct Query {
213297
#[serde(default = "default_period", with = "humantime_serde")]
@@ -244,17 +328,16 @@ impl Query {
244328
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq)]
245329
#[serde(rename_all = "kebab-case")]
246330
pub enum SortBy {
247-
Vehicle,
248331
Battles,
249332
Tier,
250333
Nation,
251334
VehicleType,
252-
WonBattles,
335+
Wins,
253336
WinRate,
254337
TrueWinRate,
255338
Gold,
256339
TrueGold,
257-
Damage,
340+
DamageDealt,
258341
DamagePerBattle,
259342
SurvivedBattles,
260343
SurvivalRate,

src/web/player/view.rs

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -250,73 +250,66 @@ pub async fn get(request: tide::Request<State>) -> tide::Result {
250250
}
251251
}
252252

253-
@if !model.tank_snapshots.is_empty() {
253+
@if !model.rows.is_empty() {
254254
div.box {
255255
div.table-container {
256256
table#vehicles.table.is-hoverable.is-striped.is-fullwidth {
257257
thead {
258258
tr {
259-
(render_vehicles_th(&model.query, SortBy::Vehicle, html! { "Техника" })?)
259+
th { "Техника" }
260260
(render_vehicles_th(&model.query, SortBy::Tier, html! { "Уровень" })?)
261261
(render_vehicles_th(&model.query, SortBy::Nation, html! { "Нация" })?)
262262
(render_vehicles_th(&model.query, SortBy::VehicleType, html! { "Тип" })?)
263263
(render_vehicles_th(&model.query, SortBy::Battles, html! { "Бои" })?)
264-
(render_vehicles_th(&model.query, SortBy::WonBattles, html! { "Победы" })?)
264+
(render_vehicles_th(&model.query, SortBy::Wins, html! { "Победы" })?)
265265
(render_vehicles_th(&model.query, SortBy::WinRate, html! { "Текущий процент побед" })?)
266266
(render_vehicles_th(&model.query, SortBy::TrueWinRate, html! { "Ожидаемый процент побед" })?)
267267
(render_vehicles_th(&model.query, SortBy::Gold, html! { abbr title="Текущий доход от золотых бустеров за бой, если они были установлены" { "Заработанное золото" } })?)
268268
(render_vehicles_th(&model.query, SortBy::TrueGold, html! { abbr title="Средняя ожидаемая доходность золотого бустера за бой" { "Ожидаемое золото" } })?)
269-
(render_vehicles_th(&model.query, SortBy::Damage, html! { "Ущерб" })?)
269+
(render_vehicles_th(&model.query, SortBy::DamageDealt, html! { "Ущерб" })?)
270270
(render_vehicles_th(&model.query, SortBy::DamagePerBattle, html! { "Ущерб за бой" })?)
271271
(render_vehicles_th(&model.query, SortBy::SurvivedBattles, html! { "Выжил" })?)
272272
(render_vehicles_th(&model.query, SortBy::SurvivalRate, html! { "Выживаемость" })?)
273273
}
274274
}
275275
tbody {
276-
@for snapshot in &model.tank_snapshots {
277-
@let vehicle = state.get_vehicle(snapshot.tank_id).await?;
278-
@let statistics = &snapshot.all_statistics;
279-
@let win_rate = statistics.wins as f64 / statistics.battles as f64;
280-
@let (estimated_win_rate, win_rate_margin) = wilson_score_interval_90(snapshot.all_statistics.battles, snapshot.all_statistics.wins);
281-
@let survival_percentage = 100.0 * (statistics.survived_battles as f64) / (statistics.battles as f64);
282-
@let mean_damage_dealt = statistics.damage_dealt as f64 / statistics.battles as f64;
283-
276+
@for row in &model.rows {
284277
tr {
285-
th.is-white-space-nowrap { (render_vehicle_name(&vehicle)) }
286-
td.has-text-centered { strong { (render_tier(vehicle.tier)) } }
287-
td.has-text-centered { (render_nation(&vehicle.nation)) }
288-
td { (format!("{:?}", vehicle.type_)) }
289-
td { (snapshot.all_statistics.battles) }
290-
td { (snapshot.all_statistics.wins) }
291-
td.has-text-info { strong { (render_f64(100.0 * win_rate, 1)) "%" } }
278+
th.is-white-space-nowrap { (render_vehicle_name(&row.vehicle)) }
279+
td.has-text-centered { strong { (render_tier(row.vehicle.tier)) } }
280+
td.has-text-centered { (render_nation(&row.vehicle.nation)) }
281+
td { (format!("{:?}", row.vehicle.type_)) }
282+
td { (row.all_statistics.battles) }
283+
td { (row.all_statistics.wins) }
284+
td.has-text-info { strong { (render_f64(100.0 * row.win_rate, 1)) "%" } }
292285
td.has-text-centered.is-white-space-nowrap {
293-
strong { (render_f64(100.0 * estimated_win_rate, 1)) "%" }
286+
strong { (render_f64(100.0 * row.true_win_rate.0, 1)) "%" }
294287
" ±"
295-
(render_f64(win_rate_margin * 100.0, 1))
288+
(render_f64(row.true_win_rate.1 * 100.0, 1))
296289
}
297290
td {
298291
span.icon-text.is-flex-wrap-nowrap {
299292
span.icon.has-text-warning-dark { i.fas.fa-coins {} }
300-
span { strong { (render_f64(10.0 + vehicle.tier as f64 * win_rate, 1)) } }
293+
span { strong { (render_f64(row.gold_per_battle, 1)) } }
301294
}
302295
}
303296
td.is-white-space-nowrap {
304297
span.icon-text.is-flex-wrap-nowrap {
305298
span.icon.has-text-warning-dark { i.fas.fa-coins {} }
306299
span {
307-
strong { (render_f64(10.0 + vehicle.tier as f64 * estimated_win_rate, 1)) }
300+
strong { (render_f64(row.true_gold_per_battle, 1)) }
308301
" ±"
309-
(render_f64(vehicle.tier as f64 * win_rate_margin, 1))
302+
(render_f64(row.vehicle.tier as f64 * row.true_win_rate.1, 1))
310303
}
311304
}
312305
}
313-
td { (snapshot.all_statistics.damage_dealt) }
314-
td { (render_f64(mean_damage_dealt, 0)) }
315-
td { (snapshot.all_statistics.survived_battles) }
306+
td { (row.all_statistics.damage_dealt) }
307+
td { (render_f64(row.damage_per_battle, 0)) }
308+
td { (row.all_statistics.survived_battles) }
316309
td {
317310
span.icon-text.is-flex-wrap-nowrap {
318311
span.icon { i.fas.fa-heart.has-text-danger {} }
319-
span { (render_f64(survival_percentage, 0)) "%" }
312+
span { (render_f64(100.0 * row.survival_rate, 0)) "%" }
320313
}
321314
}
322315
}

0 commit comments

Comments
 (0)