Skip to main content

pfx_steam/
daily.rs

1use std::fmt;
2
3use crate::lockstep::checksum;
4use crate::{Board, Call, Display, Entry, Error, Range, Sort, SteamApi, Upload, User};
5
6const DAY: u64 = 86_400;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct Date {
10    year: u64,
11    month: u8,
12    day: u8,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum Clock {
17    Server,
18    System,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct Today {
23    pub date: Date,
24    pub clock: Clock,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum Repeat {
29    FirstOnly,
30    Best,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Daily {
35    pub salt: String,
36    pub prefix: String,
37    pub repeat: Repeat,
38    pub sort: Sort,
39    pub display: Display,
40}
41
42impl Date {
43    pub fn new(year: u64, month: u8, day: u8) -> Option<Self> {
44        if !(1..=12).contains(&month) || day == 0 || day > days_in(year, month) {
45            return None;
46        }
47        Some(Self { year, month, day })
48    }
49
50    pub fn from_unix(seconds: u64) -> Self {
51        let z = seconds / DAY + 719_468;
52        let era = z / 146_097;
53        let doe = z - era * 146_097;
54        let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
55        let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
56        let mp = (5 * doy + 2) / 153;
57        let day = doy - (153 * mp + 2) / 5 + 1;
58        let month = if mp < 10 { mp + 3 } else { mp - 9 };
59        let year = yoe + era * 400 + u64::from(month <= 2);
60        Self {
61            year,
62            month: month as u8,
63            day: day as u8,
64        }
65    }
66
67    pub fn year(self) -> u64 {
68        self.year
69    }
70
71    pub fn month(self) -> u8 {
72        self.month
73    }
74
75    pub fn day(self) -> u8 {
76        self.day
77    }
78}
79
80impl fmt::Display for Date {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
83    }
84}
85
86fn days_in(year: u64, month: u8) -> u8 {
87    match month {
88        2 if year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400)) => {
89            29
90        }
91        2 => 28,
92        4 | 6 | 9 | 11 => 30,
93        _ => 31,
94    }
95}
96
97pub fn daily_seed(salt: &str, date: Date) -> u64 {
98    let mut bytes = Vec::with_capacity(salt.len() + 11);
99    bytes.extend_from_slice(salt.as_bytes());
100    bytes.push(0);
101    bytes.extend_from_slice(date.to_string().as_bytes());
102    checksum(&bytes)
103}
104
105impl Today {
106    pub fn pick(server: Option<u64>, system: u64) -> Self {
107        match server {
108            Some(seconds) => Self {
109                date: Date::from_unix(seconds),
110                clock: Clock::Server,
111            },
112            None => Self {
113                date: Date::from_unix(system),
114                clock: Clock::System,
115            },
116        }
117    }
118}
119
120impl Daily {
121    pub fn new(salt: &str, prefix: &str, repeat: Repeat, sort: Sort, display: Display) -> Self {
122        Self {
123            salt: salt.to_string(),
124            prefix: prefix.to_string(),
125            repeat,
126            sort,
127            display,
128        }
129    }
130
131    pub fn today(&self, steam: &impl SteamApi, system: u64) -> Today {
132        Today::pick(steam.server_time().ok().flatten(), system)
133    }
134
135    pub fn seed(&self, date: Date) -> u64 {
136        daily_seed(&self.salt, date)
137    }
138
139    pub fn board_name(&self, date: Date) -> String {
140        format!("{}-{date}", self.prefix)
141    }
142
143    pub fn open(&self, steam: &mut impl SteamApi, date: Date) -> Result<Call, Error> {
144        steam.find_or_create_board(&self.board_name(date), self.sort, self.display)
145    }
146
147    pub fn marker(&self) -> i32 {
148        match self.sort {
149            Sort::Descending => 0,
150            Sort::Ascending => i32::MAX,
151        }
152    }
153
154    pub fn start(&self, steam: &mut impl SteamApi, board: Board) -> Result<Call, Error> {
155        steam.upload_score(board, self.marker(), &[], Upload::KeepBest)
156    }
157
158    pub fn check(&self, steam: &mut impl SteamApi, board: Board) -> Result<Call, Error> {
159        steam.download_scores(
160            board,
161            Range::AroundUser {
162                before: 0,
163                after: 0,
164            },
165            0,
166        )
167    }
168
169    pub fn used(entries: &[Entry], me: User) -> bool {
170        entries.iter().any(|entry| entry.user == me)
171    }
172
173    pub fn upload(&self, date: Date, last_counted: Option<Date>) -> Option<Upload> {
174        match self.repeat {
175            Repeat::FirstOnly if last_counted == Some(date) => None,
176            Repeat::FirstOnly | Repeat::Best => Some(Upload::KeepBest),
177        }
178    }
179
180    pub fn submit(
181        &self,
182        steam: &mut impl SteamApi,
183        board: Board,
184        date: Date,
185        last_counted: Option<Date>,
186        score: i32,
187        details: &[i32],
188    ) -> Result<Option<Call>, Error> {
189        match self.upload(date, last_counted) {
190            Some(upload) => steam.upload_score(board, score, details, upload).map(Some),
191            None => Ok(None),
192        }
193    }
194}