1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use std::collections::HashMap;
use getset::Getters;
use itertools::Itertools;
use serde::Serialize;
use crate::data::{
database::Linked,
leaderboard::{leaderboard, LeaderboardRun},
types::*,
};
#[derive(Debug, Clone, Getters, Serialize)]
#[get = "pub"]
pub struct ProgressionRun {
progress_ms: u64,
run: Linked<Run>,
leaderboard_run: Option<LeaderboardRun>,
}
pub fn progression(runs: &[Linked<Run>]) -> Vec<ProgressionRun> {
if runs.is_empty() {
return vec![]
}
let game_id = runs[0].game_id;
let category_id = runs[0].category_id;
assert!(
runs.iter()
.all(|r| r.game_id == game_id && r.category_id == category_id),
"runs must all be from same game and category"
);
let runs_by_level: HashMap<Option<u64>, Vec<Linked<Run>>> = runs
.iter()
.sorted_by(|a, b| {
a.date()
.cmp(&b.date())
.then(a.created().cmp(&b.created()))
.then(a.id().cmp(&b.id()))
})
.map(|run| (run.level_id, run.clone()))
.into_group_map();
let mut progression: Vec<ProgressionRun> = Vec::new();
for (_level_id, runs) in runs_by_level.iter().sorted_by(|a, b| a.0.cmp(b.0)) {
let mut best_ms: Option<u64> = None;
let mut leaderboard_runs_by_id: HashMap<u64, LeaderboardRun> = HashMap::new();
for leaderboard_run in leaderboard(&runs.to_vec(), false) {
let id = *leaderboard_run.run().id();
leaderboard_runs_by_id.insert(id, leaderboard_run);
}
for run in runs.iter() {
let is_progress;
let mut progress_ms = 0;
match best_ms {
None => {
is_progress = true;
}
Some(best_ms) => {
is_progress = run.time_ms() < best_ms;
if is_progress {
progress_ms = best_ms - run.time_ms();
}
}
}
if is_progress {
progression.push(ProgressionRun {
progress_ms,
run: run.clone(),
leaderboard_run: leaderboard_runs_by_id.remove(run.id()),
});
best_ms = Some(run.time_ms());
}
}
}
progression.sort_by(|a, b| {
b.run
.date()
.cmp(&a.run.date())
.then(b.run.created().cmp(&a.run.created()))
});
progression
}