Skip to main content

emom/
lib.rs

1pub mod countdown_timer;
2pub mod emomtimer {
3    pub enum Msg {
4        Start,
5        Stop,
6        Reset,
7        IncrementSecond,
8        DecrementSecond,
9        IncrementQuarter,
10        DecrementQuarter,
11        IncrementRound,
12        DecrementRound,
13        Tick,
14    }
15
16    pub const DEFAULT_MINUTES: usize = 1;
17    pub const DEFAULT_SECONDS: usize = 0;
18    pub const DEFAULT_ROUNDS: usize = 5;
19
20    #[derive(Debug, Clone, Copy, PartialEq)]
21    pub struct Time {
22        pub seconds: usize,
23        pub minutes: usize,
24        pub tenths: usize,
25    }
26
27    impl Time {
28        pub fn reset(&mut self) {
29            self.seconds = DEFAULT_SECONDS;
30            self.minutes = DEFAULT_MINUTES;
31            self.tenths = 0;
32        }
33
34        pub fn is_zero(&self) -> bool {
35            self.seconds == 0 && self.minutes == 0 && self.tenths == 0
36        }
37
38        pub fn tick(&mut self, max_seconds: usize) {
39            if self.is_zero() {
40                return;
41            }
42            // Decrement tenths first
43            if self.tenths > 0 {
44                self.tenths -= 1;
45            } else {
46                // tenths is 0, so we need to decrement seconds and wrap tenths to 9
47                self.decrement_seconds(max_seconds);
48                // Only set tenths to 9 if we didn't hit zero
49                if !self.is_zero() {
50                    self.tenths = 9;
51                }
52            }
53        }
54
55        pub fn increment_seconds(&mut self) {
56            self.seconds += 1;
57            if self.seconds == 60 {
58                self.seconds = 0;
59                self.increment_minutes()
60            }
61        }
62
63        pub fn decrement_seconds(&mut self, max_seconds: usize) {
64            if self.is_zero() {
65                self.seconds = 0;
66                self.minutes = 0;
67                self.tenths = 0;
68            } else if self.seconds == 0 {
69                self.seconds = max_seconds - 1;
70                self.decrement_minutes()
71            } else {
72                self.seconds -= 1;
73            }
74        }
75
76        pub fn increment_quarter(&mut self) {
77            self.seconds += 15;
78            if self.seconds >= 60 {
79                self.seconds -= 60;
80                self.increment_minutes();
81            }
82        }
83
84        pub fn decrement_quarter(&mut self) {
85            if self.minutes == 0 && self.seconds < 15 {
86                self.seconds = 0;
87                self.minutes = 0;
88                self.tenths = 0;
89            } else if self.seconds < 15 {
90                self.seconds += 45;
91                self.decrement_minutes();
92            } else {
93                self.seconds -= 15;
94            }
95        }
96
97        pub fn increment_minutes(&mut self) {
98            self.minutes += 1;
99        }
100
101        pub fn decrement_minutes(&mut self) {
102            if self.minutes > 0 {
103                self.minutes -= 1;
104            }
105        }
106
107        // ignoring tenths
108        pub fn total_seconds(&self) -> usize {
109            self.seconds + self.minutes * 60
110        }
111    }
112
113    pub struct Timer {
114        pub current_time: Time,
115        pub rounds: usize,
116        pub current_round: usize,
117        pub running: bool,
118    }
119
120    impl Timer {
121        pub fn reset(&mut self) {
122            self.current_time.reset();
123            self.rounds = DEFAULT_ROUNDS;
124            self.current_round = 1;
125            self.running = false;
126        }
127
128        pub fn increment_rounds(&mut self) {
129            self.rounds += 1;
130        }
131
132        pub fn decrement_rounds(&mut self) {
133            if self.rounds > 1 {
134                self.rounds -= 1;
135            }
136            if self.current_round > self.rounds {
137                self.current_round = self.rounds;
138            }
139        }
140    }
141
142    pub fn distance<T>(a: T, b: T) -> T
143    where
144        T: std::ops::Sub<Output = T> + std::cmp::PartialOrd + Copy,
145    {
146        if a > b { a - b } else { b - a }
147    }
148
149    #[cfg(test)]
150    mod tests {
151        use super::*;
152
153        #[test]
154        fn test_timer_reset() {
155            let mut timer = Timer {
156                current_time: Time {
157                    seconds: 30,
158                    minutes: 2,
159                    tenths: 1,
160                },
161                rounds: 10,
162                current_round: 5,
163                running: true,
164            };
165            timer.reset();
166            assert_eq!(timer.current_time.seconds, DEFAULT_SECONDS);
167            assert_eq!(timer.current_time.minutes, DEFAULT_MINUTES);
168            assert_eq!(timer.current_time.tenths, 0);
169            assert_eq!(timer.rounds, DEFAULT_ROUNDS);
170            assert_eq!(timer.current_round, 1);
171            assert!(!timer.running);
172        }
173
174        #[test]
175        fn test_timer_increment_rounds() {
176            let mut timer = Timer {
177                current_time: Time {
178                    seconds: 0,
179                    minutes: 1,
180                    tenths: 0,
181                },
182                rounds: 15,
183                current_round: 1,
184                running: true,
185            };
186            timer.increment_rounds();
187            assert_eq!(timer.rounds, 16);
188        }
189
190        #[test]
191        fn test_decrement_rounds() {
192            let mut timer = Timer {
193                current_time: Time {
194                    seconds: 0,
195                    minutes: 1,
196                    tenths: 0,
197                },
198                rounds: 15,
199                current_round: 1,
200                running: true,
201            };
202            timer.decrement_rounds();
203            assert_eq!(timer.rounds, 14);
204            timer.rounds = 1;
205            timer.decrement_rounds();
206            assert_eq!(timer.rounds, 1);
207        }
208
209        #[test]
210        fn test_decrement_rounds_clamps_current_round() {
211            let mut timer = Timer {
212                current_time: Time {
213                    seconds: 0,
214                    minutes: 1,
215                    tenths: 0,
216                },
217                rounds: 4,
218                current_round: 4,
219                running: false,
220            };
221            timer.decrement_rounds();
222            assert_eq!(timer.rounds, 3);
223            assert_eq!(timer.current_round, 3);
224        }
225
226        #[test]
227        fn test_time_increment_seconds() {
228            let mut time = Time {
229                seconds: 0,
230                minutes: 1,
231                tenths: 0,
232            };
233            time.increment_seconds();
234            assert_eq!(time.seconds, 1);
235            assert_eq!(time.minutes, 1);
236            time.seconds = 59;
237            time.increment_seconds();
238            assert_eq!(time.seconds, 0);
239            assert_eq!(time.minutes, 2);
240        }
241
242        #[test]
243        fn test_time_decrement_seconds() {
244            let mut time = Time {
245                seconds: 0,
246                minutes: 0,
247                tenths: 0,
248            };
249            time.decrement_seconds(60);
250            assert_eq!(time.seconds, 0);
251            assert_eq!(time.minutes, 0);
252            assert_eq!(time.tenths, 0);
253            time.seconds = 1;
254            time.decrement_seconds(60);
255            assert_eq!(time.seconds, 0);
256            assert_eq!(time.minutes, 0);
257            assert_eq!(time.tenths, 0);
258            time.seconds = 0;
259            time.minutes = 1;
260            time.decrement_seconds(60);
261            assert_eq!(time.seconds, 59);
262            assert_eq!(time.minutes, 0);
263            assert_eq!(time.tenths, 0);
264        }
265
266        #[test]
267        fn test_increment_quarter() {
268            let mut time = Time {
269                seconds: 0,
270                minutes: 0,
271                tenths: 0,
272            };
273            time.increment_quarter();
274            assert_eq!(time.seconds, 15);
275            assert_eq!(time.minutes, 0);
276            assert_eq!(time.tenths, 0);
277            time.seconds = 45;
278            time.increment_quarter();
279            assert_eq!(time.seconds, 0);
280            assert_eq!(time.minutes, 1);
281            assert_eq!(time.tenths, 0);
282        }
283
284        #[test]
285        fn test_decrement_quarter() {
286            let mut time = Time {
287                seconds: 0,
288                minutes: 0,
289                tenths: 0,
290            };
291            time.decrement_quarter();
292            assert_eq!(time.seconds, 0);
293            assert_eq!(time.minutes, 0);
294            assert_eq!(time.tenths, 0);
295            time.seconds = 15;
296            time.decrement_quarter();
297            assert_eq!(time.seconds, 0);
298            assert_eq!(time.minutes, 0);
299            assert_eq!(time.tenths, 0);
300            time.seconds = 0;
301            time.minutes = 1;
302            time.decrement_quarter();
303            assert_eq!(time.seconds, 45);
304            assert_eq!(time.minutes, 0);
305            assert_eq!(time.tenths, 0);
306            time.seconds = 12;
307            time.decrement_quarter();
308            assert_eq!(time.seconds, 0);
309            assert_eq!(time.minutes, 0);
310            assert_eq!(time.tenths, 0);
311        }
312
313        #[test]
314        fn test_time_reset() {
315            let mut time = Time {
316                seconds: 30,
317                minutes: 2,
318                tenths: 3,
319            };
320            time.reset();
321            assert_eq!(time.seconds, DEFAULT_SECONDS);
322            assert_eq!(time.minutes, DEFAULT_MINUTES);
323            assert_eq!(time.tenths, 0);
324        }
325
326        #[test]
327        fn test_time_increment_minutes() {
328            let mut time = Time {
329                seconds: 0,
330                minutes: 1,
331                tenths: 0,
332            };
333            time.increment_minutes();
334            assert_eq!(time.seconds, 0);
335            assert_eq!(time.minutes, 2);
336            assert_eq!(time.tenths, 0);
337        }
338
339        #[test]
340        fn test_time_decrement_minutes() {
341            let mut time = Time {
342                seconds: 0,
343                minutes: 1,
344                tenths: 0,
345            };
346            time.decrement_minutes();
347            assert_eq!(time.seconds, 0);
348            assert_eq!(time.minutes, 0);
349            assert_eq!(time.tenths, 0);
350            time.decrement_minutes();
351            assert_eq!(time.seconds, 0);
352            assert_eq!(time.minutes, 0);
353            assert_eq!(time.tenths, 0);
354        }
355
356        #[test]
357        fn test_time_tick() {
358            let mut time = Time {
359                seconds: 0,
360                minutes: 1,
361                tenths: 0,
362            };
363            time.tick(60);
364            assert_eq!(time.seconds, 59);
365            assert_eq!(time.minutes, 0);
366            assert_eq!(time.tenths, 9);
367            time.tenths = 0;
368            time.tick(60);
369            assert_eq!(time.seconds, 58);
370            assert_eq!(time.minutes, 0);
371            assert_eq!(time.tenths, 9);
372            time.seconds = 0;
373            time.minutes = 0;
374            time.tenths = 1;
375            time.tick(60);
376            assert_eq!(time.seconds, 0);
377            assert_eq!(time.minutes, 0);
378            assert_eq!(time.tenths, 0);
379        }
380
381        #[test]
382        fn test_time_double_tick() {
383            let mut time = Time {
384                seconds: 0,
385                minutes: 0,
386                tenths: 0,
387            };
388            time.tick(60);
389            assert_eq!(time.seconds, 0);
390            assert_eq!(time.minutes, 0);
391            assert_eq!(time.tenths, 0);
392        }
393
394        #[test]
395        fn test_time_zero() {
396            let mut time = Time {
397                seconds: 0,
398                minutes: 0,
399                tenths: 0,
400            };
401            assert!(time.is_zero());
402            time.seconds = 1;
403            assert!(!time.is_zero());
404            time.seconds = 0;
405            time.minutes = 1;
406            assert!(!time.is_zero());
407            time.minutes = 0;
408            time.tenths = 1;
409            assert!(!time.is_zero());
410        }
411
412        #[test]
413        fn test_distance() {
414            assert_eq!(distance(1, 2), 1);
415            assert_eq!(distance(2, 1), 1);
416            assert_eq!(distance(1, 1), 0);
417        }
418
419        #[test]
420        fn test_tick_countdown_sequence() {
421            // Test that ticking properly counts down through all tenths
422            // This test ensures we don't skip the second-to-last second
423            let mut time = Time {
424                seconds: 14,
425                minutes: 0,
426                tenths: 2,
427            };
428            // Tick from 14.2 -> 14.1
429            time.tick(60);
430            assert_eq!(time.seconds, 14);
431            assert_eq!(time.tenths, 1);
432            // Tick from 14.1 -> 14.0
433            time.tick(60);
434            assert_eq!(time.seconds, 14);
435            assert_eq!(time.tenths, 0);
436            // Tick from 14.0 -> 13.9 (this is where the bug was)
437            time.tick(60);
438            assert_eq!(time.seconds, 13);
439            assert_eq!(time.tenths, 9);
440            // Continue ticking
441            time.tick(60);
442            assert_eq!(time.seconds, 13);
443            assert_eq!(time.tenths, 8);
444        }
445
446        #[test]
447        fn test_15_second_timer_countdown() {
448            // Test a 15 second timer counting down
449            // Timer starts at 15.0 and should count: 15.0, 14.9, 14.8, ..., 14.1, 14.0, 13.9, ...
450            let mut time = Time {
451                seconds: 15,
452                minutes: 0,
453                tenths: 0,
454            };
455
456            // First tick from 15.0 should go to 14.9 (decrement second, set tenths to 9)
457            time.tick(60);
458            assert_eq!(time.seconds, 14, "After first tick from 15.0");
459            assert_eq!(time.tenths, 9, "After first tick from 15.0");
460
461            // Continue counting down second 14: 14.9 -> 14.8 -> ... -> 14.0
462            for expected_tenth in (0..=8).rev() {
463                time.tick(60);
464                assert_eq!(
465                    time.seconds, 14,
466                    "Second mismatch at tenth {}",
467                    expected_tenth
468                );
469                assert_eq!(
470                    time.tenths, expected_tenth,
471                    "Tenth mismatch at {}",
472                    expected_tenth
473                );
474            }
475
476            // Next tick from 14.0 should go to 13.9
477            time.tick(60);
478            assert_eq!(time.seconds, 13);
479            assert_eq!(time.tenths, 9);
480        }
481
482        #[test]
483        fn test_full_15_second_countdown() {
484            // Simulate complete 15 second countdown to catch any skips
485            let mut time = Time {
486                seconds: 15,
487                minutes: 0,
488                tenths: 0,
489            };
490
491            let mut history = Vec::new();
492
493            // Count down all the way to zero
494            while !time.is_zero() {
495                history.push((time.minutes, time.seconds, time.tenths));
496                time.tick(60);
497            }
498
499            // Debug: print the history around 14
500            println!("History around 14:");
501            for (i, (m, s, t)) in history.iter().enumerate() {
502                if *s >= 13 && *s <= 15 {
503                    println!("  [{}]: {}:{}.{}", i, m, s, t);
504                }
505            }
506
507            println!("Total history length: {}", history.len());
508
509            // Should start at 15.0
510            assert_eq!(history[0], (0, 15, 0));
511            // First tick to 14.9
512            assert_eq!(history[1], (0, 14, 9));
513        }
514
515        #[test]
516        fn test_total_seconds() {
517            let mut time = Time {
518                seconds: 0,
519                minutes: 0,
520                tenths: 0,
521            };
522            assert_eq!(time.total_seconds(), 0);
523            time.seconds = 1;
524            assert_eq!(time.total_seconds(), 1);
525            time.seconds = 0;
526            time.minutes = 1;
527            assert_eq!(time.total_seconds(), 60);
528            time.seconds = 1;
529            assert_eq!(time.total_seconds(), 61);
530        }
531    }
532}