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            } else {
136                self.rounds -= 1;
137            }
138        }
139    }
140
141    pub fn distance<T>(a: T, b: T) -> T
142    where
143        T: std::ops::Sub<Output = T> + std::cmp::PartialOrd + Copy,
144    {
145        if a > b { a - b } else { b - a }
146    }
147
148    #[cfg(test)]
149    mod tests {
150        use super::*;
151
152        #[test]
153        fn test_timer_reset() {
154            let mut timer = Timer {
155                current_time: Time {
156                    seconds: 30,
157                    minutes: 2,
158                    tenths: 1,
159                },
160                rounds: 10,
161                current_round: 5,
162                running: true,
163            };
164            timer.reset();
165            assert_eq!(timer.current_time.seconds, DEFAULT_SECONDS);
166            assert_eq!(timer.current_time.minutes, DEFAULT_MINUTES);
167            assert_eq!(timer.current_time.tenths, 0);
168            assert_eq!(timer.rounds, DEFAULT_ROUNDS);
169            assert_eq!(timer.current_round, 1);
170            assert!(!timer.running);
171        }
172
173        #[test]
174        fn test_timer_increment_rounds() {
175            let mut timer = Timer {
176                current_time: Time {
177                    seconds: 0,
178                    minutes: 1,
179                    tenths: 0,
180                },
181                rounds: 15,
182                current_round: 1,
183                running: true,
184            };
185            timer.increment_rounds();
186            assert_eq!(timer.rounds, 16);
187        }
188
189        #[test]
190        fn test_decrement_rounds() {
191            let mut timer = Timer {
192                current_time: Time {
193                    seconds: 0,
194                    minutes: 1,
195                    tenths: 0,
196                },
197                rounds: 15,
198                current_round: 1,
199                running: true,
200            };
201            timer.decrement_rounds();
202            assert_eq!(timer.rounds, 14);
203            timer.rounds = 1;
204            timer.decrement_rounds();
205            assert_eq!(timer.rounds, 1);
206        }
207
208        #[test]
209        fn test_time_increment_seconds() {
210            let mut time = Time {
211                seconds: 0,
212                minutes: 1,
213                tenths: 0,
214            };
215            time.increment_seconds();
216            assert_eq!(time.seconds, 1);
217            assert_eq!(time.minutes, 1);
218            time.seconds = 59;
219            time.increment_seconds();
220            assert_eq!(time.seconds, 0);
221            assert_eq!(time.minutes, 2);
222        }
223
224        #[test]
225        fn test_time_decrement_seconds() {
226            let mut time = Time {
227                seconds: 0,
228                minutes: 0,
229                tenths: 0,
230            };
231            time.decrement_seconds(60);
232            assert_eq!(time.seconds, 0);
233            assert_eq!(time.minutes, 0);
234            assert_eq!(time.tenths, 0);
235            time.seconds = 1;
236            time.decrement_seconds(60);
237            assert_eq!(time.seconds, 0);
238            assert_eq!(time.minutes, 0);
239            assert_eq!(time.tenths, 0);
240            time.seconds = 0;
241            time.minutes = 1;
242            time.decrement_seconds(60);
243            assert_eq!(time.seconds, 59);
244            assert_eq!(time.minutes, 0);
245            assert_eq!(time.tenths, 0);
246        }
247
248        #[test]
249        fn test_increment_quarter() {
250            let mut time = Time {
251                seconds: 0,
252                minutes: 0,
253                tenths: 0,
254            };
255            time.increment_quarter();
256            assert_eq!(time.seconds, 15);
257            assert_eq!(time.minutes, 0);
258            assert_eq!(time.tenths, 0);
259            time.seconds = 45;
260            time.increment_quarter();
261            assert_eq!(time.seconds, 0);
262            assert_eq!(time.minutes, 1);
263            assert_eq!(time.tenths, 0);
264        }
265
266        #[test]
267        fn test_decrement_quarter() {
268            let mut time = Time {
269                seconds: 0,
270                minutes: 0,
271                tenths: 0,
272            };
273            time.decrement_quarter();
274            assert_eq!(time.seconds, 0);
275            assert_eq!(time.minutes, 0);
276            assert_eq!(time.tenths, 0);
277            time.seconds = 15;
278            time.decrement_quarter();
279            assert_eq!(time.seconds, 0);
280            assert_eq!(time.minutes, 0);
281            assert_eq!(time.tenths, 0);
282            time.seconds = 0;
283            time.minutes = 1;
284            time.decrement_quarter();
285            assert_eq!(time.seconds, 45);
286            assert_eq!(time.minutes, 0);
287            assert_eq!(time.tenths, 0);
288            time.seconds = 12;
289            time.decrement_quarter();
290            assert_eq!(time.seconds, 0);
291            assert_eq!(time.minutes, 0);
292            assert_eq!(time.tenths, 0);
293        }
294
295        #[test]
296        fn test_time_reset() {
297            let mut time = Time {
298                seconds: 30,
299                minutes: 2,
300                tenths: 3,
301            };
302            time.reset();
303            assert_eq!(time.seconds, DEFAULT_SECONDS);
304            assert_eq!(time.minutes, DEFAULT_MINUTES);
305            assert_eq!(time.tenths, 0);
306        }
307
308        #[test]
309        fn test_time_increment_minutes() {
310            let mut time = Time {
311                seconds: 0,
312                minutes: 1,
313                tenths: 0,
314            };
315            time.increment_minutes();
316            assert_eq!(time.seconds, 0);
317            assert_eq!(time.minutes, 2);
318            assert_eq!(time.tenths, 0);
319        }
320
321        #[test]
322        fn test_time_decrement_minutes() {
323            let mut time = Time {
324                seconds: 0,
325                minutes: 1,
326                tenths: 0,
327            };
328            time.decrement_minutes();
329            assert_eq!(time.seconds, 0);
330            assert_eq!(time.minutes, 0);
331            assert_eq!(time.tenths, 0);
332            time.decrement_minutes();
333            assert_eq!(time.seconds, 0);
334            assert_eq!(time.minutes, 0);
335            assert_eq!(time.tenths, 0);
336        }
337
338        #[test]
339        fn test_time_tick() {
340            let mut time = Time {
341                seconds: 0,
342                minutes: 1,
343                tenths: 0,
344            };
345            time.tick(60);
346            assert_eq!(time.seconds, 59);
347            assert_eq!(time.minutes, 0);
348            assert_eq!(time.tenths, 9);
349            time.tenths = 0;
350            time.tick(60);
351            assert_eq!(time.seconds, 58);
352            assert_eq!(time.minutes, 0);
353            assert_eq!(time.tenths, 9);
354            time.seconds = 0;
355            time.minutes = 0;
356            time.tenths = 1;
357            time.tick(60);
358            assert_eq!(time.seconds, 0);
359            assert_eq!(time.minutes, 0);
360            assert_eq!(time.tenths, 0);
361        }
362
363        #[test]
364        fn test_time_double_tick() {
365            let mut time = Time {
366                seconds: 0,
367                minutes: 0,
368                tenths: 0,
369            };
370            time.tick(60);
371            assert_eq!(time.seconds, 0);
372            assert_eq!(time.minutes, 0);
373            assert_eq!(time.tenths, 0);
374        }
375
376        #[test]
377        fn test_time_zero() {
378            let mut time = Time {
379                seconds: 0,
380                minutes: 0,
381                tenths: 0,
382            };
383            assert!(time.is_zero());
384            time.seconds = 1;
385            assert!(!time.is_zero());
386            time.seconds = 0;
387            time.minutes = 1;
388            assert!(!time.is_zero());
389            time.minutes = 0;
390            time.tenths = 1;
391            assert!(!time.is_zero());
392        }
393
394        #[test]
395        fn test_distance() {
396            assert_eq!(distance(1, 2), 1);
397            assert_eq!(distance(2, 1), 1);
398            assert_eq!(distance(1, 1), 0);
399        }
400
401        #[test]
402        fn test_tick_countdown_sequence() {
403            // Test that ticking properly counts down through all tenths
404            // This test ensures we don't skip the second-to-last second
405            let mut time = Time {
406                seconds: 14,
407                minutes: 0,
408                tenths: 2,
409            };
410            // Tick from 14.2 -> 14.1
411            time.tick(60);
412            assert_eq!(time.seconds, 14);
413            assert_eq!(time.tenths, 1);
414            // Tick from 14.1 -> 14.0
415            time.tick(60);
416            assert_eq!(time.seconds, 14);
417            assert_eq!(time.tenths, 0);
418            // Tick from 14.0 -> 13.9 (this is where the bug was)
419            time.tick(60);
420            assert_eq!(time.seconds, 13);
421            assert_eq!(time.tenths, 9);
422            // Continue ticking
423            time.tick(60);
424            assert_eq!(time.seconds, 13);
425            assert_eq!(time.tenths, 8);
426        }
427
428        #[test]
429        fn test_15_second_timer_countdown() {
430            // Test a 15 second timer counting down
431            // Timer starts at 15.0 and should count: 15.0, 14.9, 14.8, ..., 14.1, 14.0, 13.9, ...
432            let mut time = Time {
433                seconds: 15,
434                minutes: 0,
435                tenths: 0,
436            };
437
438            // First tick from 15.0 should go to 14.9 (decrement second, set tenths to 9)
439            time.tick(60);
440            assert_eq!(time.seconds, 14, "After first tick from 15.0");
441            assert_eq!(time.tenths, 9, "After first tick from 15.0");
442
443            // Continue counting down second 14: 14.9 -> 14.8 -> ... -> 14.0
444            for expected_tenth in (0..=8).rev() {
445                time.tick(60);
446                assert_eq!(
447                    time.seconds, 14,
448                    "Second mismatch at tenth {}",
449                    expected_tenth
450                );
451                assert_eq!(
452                    time.tenths, expected_tenth,
453                    "Tenth mismatch at {}",
454                    expected_tenth
455                );
456            }
457
458            // Next tick from 14.0 should go to 13.9
459            time.tick(60);
460            assert_eq!(time.seconds, 13);
461            assert_eq!(time.tenths, 9);
462        }
463
464        #[test]
465        fn test_full_15_second_countdown() {
466            // Simulate complete 15 second countdown to catch any skips
467            let mut time = Time {
468                seconds: 15,
469                minutes: 0,
470                tenths: 0,
471            };
472
473            let mut history = Vec::new();
474
475            // Count down all the way to zero
476            while !time.is_zero() {
477                history.push((time.minutes, time.seconds, time.tenths));
478                time.tick(60);
479            }
480
481            // Debug: print the history around 14
482            println!("History around 14:");
483            for (i, (m, s, t)) in history.iter().enumerate() {
484                if *s >= 13 && *s <= 15 {
485                    println!("  [{}]: {}:{}.{}", i, m, s, t);
486                }
487            }
488
489            println!("Total history length: {}", history.len());
490
491            // Should start at 15.0
492            assert_eq!(history[0], (0, 15, 0));
493            // First tick to 14.9
494            assert_eq!(history[1], (0, 14, 9));
495        }
496
497        #[test]
498        fn test_total_seconds() {
499            let mut time = Time {
500                seconds: 0,
501                minutes: 0,
502                tenths: 0,
503            };
504            assert_eq!(time.total_seconds(), 0);
505            time.seconds = 1;
506            assert_eq!(time.total_seconds(), 1);
507            time.seconds = 0;
508            time.minutes = 1;
509            assert_eq!(time.total_seconds(), 60);
510            time.seconds = 1;
511            assert_eq!(time.total_seconds(), 61);
512        }
513    }
514}