1use core::cmp::Ordering;
19
20use shuyi_assets::keymap::{Button, ButtonChord, CrankDef};
21
22const MILLI: f32 = 1000.0;
25
26pub const STEPS_PER_TURN: i32 = 256;
28
29const UNITS_PER_DEGREE: i32 = 32_000;
32
33const UNITS_PER_MILLI: i32 = UNITS_PER_DEGREE / 1000;
35
36const UNITS_PER_STEP: i32 = UNITS_PER_DEGREE * 360 / STEPS_PER_TURN;
38
39const MOST_STEPS: i32 = 100 * STEPS_PER_TURN;
41
42const MOST_DEGREES: f32 = 36_000.0;
45
46pub type Notches = i32;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct Crank {
52 notch: i32,
54 wound: i32,
57}
58
59impl Crank {
60 pub fn new(def: CrankDef) -> Self {
62 Self {
63 notch: (i32::from(def.notch_degrees) * UNITS_PER_DEGREE).max(1),
64 wound: 0,
65 }
66 }
67
68 pub fn turn(&mut self, degrees: f32) -> Notches {
73 if !degrees.is_finite() {
74 return 0;
75 }
76 let milli = libm::roundf(degrees.clamp(-MOST_DEGREES, MOST_DEGREES) * MILLI);
77 #[allow(clippy::cast_possible_truncation)]
79 let milli = milli as i32;
80 self.wind(milli * UNITS_PER_MILLI)
81 }
82
83 pub fn turn_steps(&mut self, steps: i32) -> Notches {
86 self.wind(steps.clamp(-MOST_STEPS, MOST_STEPS) * UNITS_PER_STEP)
87 }
88
89 fn wind(&mut self, units: i32) -> Notches {
91 if units.signum() == -self.wound.signum() {
93 self.wound = 0;
94 }
95 let wound = self.wound + units;
96 let notches = wound / self.notch;
99 self.wound = wound % self.notch;
100 notches
101 }
102
103 pub fn dock(&mut self) {
106 self.wound = 0;
107 }
108
109 pub fn presses(notches: Notches) -> Option<(Button, u32)> {
113 let button = match notches.cmp(&0) {
114 Ordering::Equal => return None,
115 Ordering::Less => Button::CrankBack,
116 Ordering::Greater => Button::CrankForward,
117 };
118 Some((button, notches.unsigned_abs()))
119 }
120}
121
122pub fn only_on_the_crank(chords: &[ButtonChord]) -> bool {
125 let wound = |button| matches!(button, Button::CrankBack | Button::CrankForward);
126 !chords.is_empty()
127 && chords.iter().all(|chord| match chord.buttons() {
128 Some((a, b)) => wound(a) || wound(b),
129 None => wound(chord.first()),
130 })
131}
132
133#[cfg(test)]
134mod tests {
135 use proptest::prelude::*;
136
137 use super::*;
138
139 use crate::prelude::*;
140
141 fn crank(notch_degrees: u16) -> Crank {
142 Crank::new(CrankDef { notch_degrees })
143 }
144
145 #[test]
146 fn a_notch_is_forty_five_degrees_by_default() {
147 assert_eq!(CrankDef::default().notch_degrees, 45);
148 let mut c = Crank::new(CrankDef::default());
149 assert_eq!(c.turn(44.0), 0);
150 assert_eq!(c.turn(1.0), 1);
151 assert_eq!(c.turn(-45.0), -1);
152 }
153
154 #[test]
156 fn steps_of_a_turn_wind_as_degrees_do() {
157 let mut c = Crank::new(CrankDef::default());
158 assert_eq!(c.turn_steps(31), 0);
159 assert_eq!(c.turn_steps(1), 1, "32 steps are 45 degrees");
160 assert_eq!(c.turn_steps(-32), -1);
161 assert_eq!(c.turn_steps(64), 2, "a quarter turn is two notches");
162 let mut c = crank(30);
163 assert_eq!(c.turn_steps(21), 0, "21 steps are 29.53 degrees");
164 assert_eq!(c.turn_steps(1), 1, "and 22 are 30.94");
165 assert_eq!(c.turn(29.0), 0);
166 assert_eq!(
167 c.turn_steps(1),
168 1,
169 "what was left counts: 0.94, 29 and 1.41"
170 );
171 assert_eq!(c.turn_steps(i32::MAX), 1200, "a hundred turns at most");
172 }
173
174 #[test]
175 fn what_is_left_of_a_notch_carries_over_one_way() {
176 let mut c = crank(30);
177 assert_eq!(c.turn(-20.0), 0);
178 assert_eq!(c.turn(-20.0), -1);
179 assert_eq!(c.turn(-20.0), -1);
181 assert_eq!(c.turn(30.0), 1);
182 }
183
184 #[test]
187 fn turning_the_other_way_starts_a_fresh_notch() {
188 let mut c = crank(30);
189 assert_eq!(c.turn(-40.0), -1);
190 assert_eq!(c.turn(29.0), 0);
191 assert_eq!(c.turn(1.0), 1);
192 assert_eq!(c.turn(-20.0), 0);
193 assert_eq!(c.turn(5.0), 0);
194 assert_eq!(c.turn(-29.0), 0);
195 assert_eq!(c.turn(-1.0), -1);
196 }
197
198 #[test]
199 fn a_big_turn_is_several_notches_at_once() {
200 let mut c = crank(30);
201 assert_eq!(c.turn(-95.0), -3);
202 assert_eq!(c.turn(65.0), 2);
203 assert_eq!(c.turn(0.0), 0);
204 assert_eq!(c.turn(25.0), 1, "the 5 left of 65, still forwards");
205 }
206
207 #[test]
208 fn docking_forgets_a_part_notch() {
209 let mut c = crank(30);
210 assert_eq!(c.turn(-25.0), 0);
211 c.dock();
212 assert_eq!(c.turn(-25.0), 0);
213 assert_eq!(c.turn(-5.0), -1);
214 }
215
216 #[test]
217 fn a_broken_reading_is_no_turn() {
218 let mut c = crank(30);
219 assert_eq!(c.turn(f32::NAN), 0);
220 assert_eq!(c.turn(f32::INFINITY), 0);
221 assert_eq!(c.turn(1.0e30), 1200, "a hundred turns at most");
222 }
223
224 #[test]
225 fn an_action_is_only_on_the_crank_when_every_chord_needs_it() {
226 let wound = ButtonChord::single(Button::CrankForward);
227 let back = ButtonChord::single(Button::CrankBack);
228 let plain = ButtonChord::single(Button::East);
229 let chord = |a, b| ButtonChord::pair(a, b).unwrap_or_else(|| panic!());
230 assert!(
231 !only_on_the_crank(&[]),
232 "no button at all isn't the crank's"
233 );
234 assert!(only_on_the_crank(&[wound]));
235 assert!(only_on_the_crank(&[back, wound]));
236 assert!(!only_on_the_crank(&[plain]));
237 assert!(!only_on_the_crank(&[wound, plain]), "a button still works");
238 assert!(only_on_the_crank(&[chord(Button::East, Button::CrankBack)]));
239 assert!(only_on_the_crank(&[chord(Button::CrankBack, Button::East)]));
240 assert!(!only_on_the_crank(&[chord(Button::East, Button::West)]));
241 }
242
243 #[test]
244 fn notches_press_the_crank_buttons() {
245 assert_eq!(Crank::presses(0), None);
246 assert_eq!(Crank::presses(-3), Some((Button::CrankBack, 3)));
247 assert_eq!(Crank::presses(2), Some((Button::CrankForward, 2)));
248 }
249
250 fn total(parts: &[i32]) -> i32 {
252 parts.iter().sum()
253 }
254
255 fn wind(c: &mut Crank, parts: &[i32]) -> Vec<Notches> {
257 #[allow(clippy::cast_precision_loss)] parts.iter().map(|&p| c.turn(p as f32 / MILLI)).collect()
259 }
260
261 proptest! {
262 #[test]
265 fn any_split_of_a_one_way_turn_gives_the_same_notches(
266 notch in 1u16..=360,
267 parts in prop::collection::vec(0i32..100_000, 0..40),
268 back in any::<bool>(),
269 ) {
270 let parts: Vec<i32> = parts.iter().map(|&p| if back { -p } else { p }).collect();
271 let split: Notches = wind(&mut crank(notch), &parts).iter().sum();
272 let whole = wind(&mut crank(notch), &[total(&parts)]);
273 prop_assert_eq!(split, whole[0]);
274 for n in wind(&mut crank(notch), &parts) {
276 let one_way = if back { n <= 0 } else { n >= 0 };
277 prop_assert!(one_way, "{} notches", n);
278 }
279 }
280
281 #[test]
284 fn wobbling_by_less_than_a_notch_never_gives_one(
285 notch in 1u16..=360,
286 parts in prop::collection::vec(1i32..1000, 0..40),
287 ) {
288 let notch_milli = i32::from(notch) * 1000;
289 let wobble: Vec<i32> = parts
291 .iter()
292 .enumerate()
293 .map(|(i, &p)| {
294 let p = p * notch_milli / 1000;
295 let p = p.clamp(1, notch_milli - 1);
296 if i % 2 == 0 { -p } else { p }
297 })
298 .collect();
299 let notches = wind(&mut crank(notch), &wobble);
300 prop_assert!(notches.iter().all(|&n| n == 0), "{:?}", notches);
301 }
302 }
303}