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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use crate::encode::Encode;
use alloc::boxed::Box;
use serde::{Deserialize, Serialize};
/// Player-specific app stats, like playtime.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Stats {
/// How many minutes the app was played for every player count from 1 to 4+.
///
/// Games with more than 4 players are recorded as 4 players.
/// So, the last index (3) is "4+ players".
///
/// The play time is calculated based on the number of the calls
/// to the `update` callback. So, the number is an approximation
/// and may drift a bit.
///
/// Plays shorter than 1 minute are not recorded.
pub minutes: [u32; 4],
/// How many minutes the app was played the longest for every player count from 1 to 4+.
///
/// Use this metric carefully. The runtime might or might not account for players
/// pausing the app for days instead of exiting it. Which means, multiple play
/// sessions may be counted as one.
pub longest_play: [u32; 4],
/// How many times the app was launched for every player count from 1 to 4+.
pub launches: [u32; 4],
/// The date when the app was installed.
///
/// The date is a tuple of year, month, and day of month.
pub installed_on: (u16, u8, u8),
/// The date when the app was updated.
///
/// The date is a tuple of year, month, and day of month.
pub updated_on: (u16, u8, u8),
/// The date when the app was launched last time.
///
/// The date is a tuple of year, month, and day of month.
pub launched_on: (u16, u8, u8),
/// How much XP the player has earned in the game.
///
/// Cannot be more than 1000.
pub xp: u16,
/// The progress of earning each badge.
///
/// The len is equal to the number of badges that the app has.
/// See [`crate::Badges`].
pub badges: Box<[BadgeProgress]>,
/// The high scores
pub scores: Box<[BoardScores]>,
}
impl Encode<'_> for Stats {}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct BadgeProgress {
/// If true, the earning of the badge hasn't been shown to the player yet.
pub new: bool,
/// How many points are already earned for the badge.
pub done: u16,
/// How many points needed to earn the badge.
pub goal: u16,
}
impl BadgeProgress {
/// If the badge has been earned by the player.
#[must_use]
#[inline]
pub const fn earned(&self) -> bool {
self.done >= self.goal
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct BoardScores {
/// Top scores of the local player.
pub me: Box<[i16; 8]>,
/// Top scores of friends.
pub friends: Box<[FriendScore; 8]>,
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
pub struct FriendScore {
pub index: u16,
pub score: i16,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_roundtrip() {
let given = Stats {
minutes: [11, 12, 13, 14],
longest_play: [21, 22, 23, 24],
launches: [31, 32, 33, 34],
installed_on: (2023, 12, 31),
updated_on: (2024, 1, 17),
launched_on: (2024, 2, 28),
xp: 32,
badges: Box::new([]),
scores: Box::new([]),
};
let mut buf = vec![0; given.size()];
let raw = given.encode_buf(&mut buf).unwrap();
let actual = Stats::decode(raw).unwrap();
assert_eq!(given, actual);
}
}