1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum CalendarKind {
12 Canon,
14 RealWorld,
16}
17
18impl CalendarKind {
19 pub fn parse(s: &str) -> Option<CalendarKind> {
21 match s {
22 "canon" => Some(CalendarKind::Canon),
23 "realWorld" => Some(CalendarKind::RealWorld),
24 _ => None,
25 }
26 }
27
28 pub fn as_str(self) -> &'static str {
30 match self {
31 CalendarKind::Canon => "canon",
32 CalendarKind::RealWorld => "realWorld",
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug)]
39pub struct Calendar {
40 pub kind: CalendarKind,
42 pub week_days: i64,
44 pub weekend_days: i64,
46 pub quarter_days: i64,
48 pub year_days: i64,
50 pub maint_period_days: i64,
52}
53
54pub const CANON: Calendar = Calendar {
56 kind: CalendarKind::Canon,
57 week_days: 3,
58 weekend_days: 1,
59 quarter_days: 3,
60 year_days: 12,
61 maint_period_days: 3,
62};
63
64pub const REAL_WORLD: Calendar = Calendar {
66 kind: CalendarKind::RealWorld,
67 week_days: 7,
68 weekend_days: 2,
69 quarter_days: 90,
70 year_days: 360,
71 maint_period_days: 30,
72};
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum GameMode {
77 Classic,
79 Modern,
83}
84
85impl GameMode {
86 pub fn as_str(self) -> &'static str {
88 match self {
89 GameMode::Classic => "classic",
90 GameMode::Modern => "modern",
91 }
92 }
93
94 pub fn parse(s: &str) -> Option<GameMode> {
96 match s {
97 "classic" => Some(GameMode::Classic),
98 "modern" => Some(GameMode::Modern),
99 _ => None,
100 }
101 }
102}
103
104pub fn resolve_calendar(mode: GameMode, modern_calendar: CalendarKind) -> Calendar {
107 if mode == GameMode::Classic {
108 return CANON;
109 }
110 match modern_calendar {
111 CalendarKind::Canon => CANON,
112 CalendarKind::RealWorld => REAL_WORLD,
113 }
114}
115
116#[derive(Clone, Debug)]
118pub struct Clock {
119 pub minutes: f64,
121 pub calendar: Calendar,
123}
124
125impl Clock {
126 #[doc(hidden)]
128 pub fn new(minutes: f64, calendar: Calendar) -> Clock {
129 Clock {
130 minutes: if minutes == 0.0 { 7.0 * 60.0 } else { minutes },
131 calendar,
132 }
133 }
134
135 #[doc(hidden)]
136 pub fn advance(&mut self, min: f64) {
137 self.minutes += min;
138 }
139
140 pub fn minute_of_day(&self) -> f64 {
142 ((self.minutes % 1440.0) + 1440.0) % 1440.0
143 }
144
145 pub fn hour(&self) -> i64 {
147 (self.minute_of_day() / 60.0).floor() as i64
148 }
149
150 pub fn minute(&self) -> i64 {
152 (self.minute_of_day() % 60.0).floor() as i64
153 }
154
155 pub fn day(&self) -> i64 {
157 (self.minutes / 1440.0).floor() as i64
158 }
159
160 pub fn day_of_week(&self) -> i64 {
162 self.day() % self.calendar.week_days
163 }
164
165 pub fn is_weekend(&self) -> bool {
167 self.is_weekend_on(self.day())
168 }
169
170 pub fn is_weekend_on(&self, day: i64) -> bool {
172 day.rem_euclid(self.calendar.week_days)
173 >= self.calendar.week_days - self.calendar.weekend_days
174 }
175
176 pub fn quarter(&self) -> i64 {
178 ((self.day() % self.calendar.year_days) as f64 / self.calendar.quarter_days as f64).floor()
179 as i64
180 }
181
182 pub fn year(&self) -> i64 {
184 (self.day() as f64 / self.calendar.year_days as f64).floor() as i64
185 }
186}
187
188impl Clock {
189 pub fn is_morning(&self) -> bool {
191 let h = self.hour();
192 (7..10).contains(&h)
193 }
194
195 pub fn is_lunch(&self) -> bool {
197 let h = self.hour();
198 (11..14).contains(&h)
199 }
200
201 pub fn is_evening(&self) -> bool {
203 let h = self.hour();
204 (17..21).contains(&h)
205 }
206
207 pub fn is_night(&self) -> bool {
209 let h = self.hour();
210 !(6..21).contains(&h)
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 struct Row {
221 name: &'static str,
222 calendar: Calendar,
223 minutes: f64,
224 day: i64,
225 day_of_week: i64,
226 is_weekend: bool,
227 quarter: i64,
228 year: i64,
229 hour: i64,
230 minute: i64,
231 }
232
233 #[test]
234 fn readings_match_the_web_table() {
235 let rows = [
236 Row {
237 name: "canon, tower founded",
238 calendar: CANON,
239 minutes: 420.0,
240 day: 0,
241 day_of_week: 0,
242 is_weekend: false,
243 quarter: 0,
244 year: 0,
245 hour: 7,
246 minute: 0,
247 },
248 Row {
249 name: "canon, weekend slot",
250 calendar: CANON,
251 minutes: 3665.0,
252 day: 2,
253 day_of_week: 2,
254 is_weekend: true,
255 quarter: 0,
256 year: 0,
257 hour: 13,
258 minute: 5,
259 },
260 Row {
261 name: "canon, second quarter",
262 calendar: CANON,
263 minutes: 4380.0,
264 day: 3,
265 day_of_week: 0,
266 is_weekend: false,
267 quarter: 1,
268 year: 0,
269 hour: 1,
270 minute: 0,
271 },
272 Row {
273 name: "canon, year rollover",
274 calendar: CANON,
275 minutes: 17880.0,
276 day: 12,
277 day_of_week: 0,
278 is_weekend: false,
279 quarter: 0,
280 year: 1,
281 hour: 10,
282 minute: 0,
283 },
284 Row {
285 name: "canon, last day of the year",
286 calendar: CANON,
287 minutes: 17220.0,
288 day: 11,
289 day_of_week: 2,
290 is_weekend: true,
291 quarter: 3,
292 year: 0,
293 hour: 23,
294 minute: 0,
295 },
296 Row {
297 name: "real world, tower founded",
298 calendar: REAL_WORLD,
299 minutes: 420.0,
300 day: 0,
301 day_of_week: 0,
302 is_weekend: false,
303 quarter: 0,
304 year: 0,
305 hour: 7,
306 minute: 0,
307 },
308 Row {
309 name: "real world, first Saturday",
310 calendar: REAL_WORLD,
311 minutes: 7710.0,
312 day: 5,
313 day_of_week: 5,
314 is_weekend: true,
315 quarter: 0,
316 year: 0,
317 hour: 8,
318 minute: 30,
319 },
320 Row {
321 name: "real world, second quarter",
322 calendar: REAL_WORLD,
323 minutes: 129660.0,
324 day: 90,
325 day_of_week: 6,
326 is_weekend: true,
327 quarter: 1,
328 year: 0,
329 hour: 1,
330 minute: 0,
331 },
332 Row {
333 name: "real world, year rollover",
334 calendar: REAL_WORLD,
335 minutes: 519120.0,
336 day: 360,
337 day_of_week: 3,
338 is_weekend: false,
339 quarter: 0,
340 year: 1,
341 hour: 12,
342 minute: 0,
343 },
344 ];
345 for r in rows {
346 let c = Clock::new(r.minutes, r.calendar);
347 assert_eq!(c.day(), r.day, "{} day", r.name);
348 assert_eq!(c.day_of_week(), r.day_of_week, "{} day of week", r.name);
349 assert_eq!(c.is_weekend(), r.is_weekend, "{} weekend", r.name);
350 assert_eq!(c.quarter(), r.quarter, "{} quarter", r.name);
351 assert_eq!(c.year(), r.year, "{} year", r.name);
352 assert_eq!(c.hour(), r.hour, "{} hour", r.name);
353 assert_eq!(c.minute(), r.minute, "{} minute", r.name);
354 }
355 }
356
357 #[test]
358 fn calendar_spans_match_the_web() {
359 let spans = |c: Calendar| {
360 [
361 c.week_days,
362 c.weekend_days,
363 c.quarter_days,
364 c.year_days,
365 c.maint_period_days,
366 ]
367 };
368 assert_eq!(spans(CANON), [3, 1, 3, 12, 3]);
369 assert_eq!(spans(REAL_WORLD), [7, 2, 90, 360, 30]);
370 }
371}