1use gilrs::{Event as GamepadEvent, EventType as GamepadEventType, Gilrs};
2#[cfg(not(target_arch = "wasm32"))]
3use glutin::event::{ElementState, MouseScrollDelta, WindowEvent};
4use std::{
5 borrow::Cow,
6 cmp::Ordering,
7 collections::HashMap,
8 sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
9};
10use typid::ID;
11#[cfg(target_arch = "wasm32")]
12use winit::event::{ElementState, MouseScrollDelta, WindowEvent};
13
14pub use gilrs::{Axis as GamepadAxis, Button as GamepadButton, GamepadId};
15#[cfg(not(target_arch = "wasm32"))]
16pub use glutin::event::{MouseButton, VirtualKeyCode};
17#[cfg(target_arch = "wasm32")]
18pub use winit::event::{MouseButton, VirtualKeyCode};
19
20#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
21pub enum InputConsume {
22 #[default]
23 None,
24 Hit,
25 All,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum VirtualAction {
30 KeyButton(VirtualKeyCode),
31 MouseButton(MouseButton),
32 Axis(u32),
33 GamepadButton(GamepadButton),
34 GamepadAxis(GamepadAxis),
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum VirtualAxis {
39 KeyButton(VirtualKeyCode),
40 MousePositionX,
41 MousePositionY,
42 MouseWheelX,
43 MouseWheelY,
44 MouseButton(MouseButton),
45 Axis(u32),
46 GamepadButton(GamepadButton),
47 GamepadAxis(GamepadAxis),
48}
49
50#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
51pub enum InputAction {
52 #[default]
53 Idle,
54 Pressed,
55 Hold,
56 Released,
57}
58
59impl InputAction {
60 pub fn change(self, hold: bool) -> Self {
61 match (self, hold) {
62 (Self::Idle, true) | (Self::Released, true) => Self::Pressed,
63 (Self::Pressed, true) => Self::Hold,
64 (Self::Pressed, false) | (Self::Hold, false) => Self::Released,
65 (Self::Released, false) => Self::Idle,
66 _ => self,
67 }
68 }
69
70 pub fn update(self) -> Self {
71 match self {
72 Self::Pressed => Self::Hold,
73 Self::Released => Self::Idle,
74 _ => self,
75 }
76 }
77
78 pub fn is_idle(self) -> bool {
79 matches!(self, Self::Idle)
80 }
81
82 pub fn is_pressed(self) -> bool {
83 matches!(self, Self::Pressed)
84 }
85
86 pub fn is_hold(self) -> bool {
87 matches!(self, Self::Hold)
88 }
89
90 pub fn is_released(self) -> bool {
91 matches!(self, Self::Released)
92 }
93
94 pub fn is_up(self) -> bool {
95 matches!(self, Self::Idle | Self::Released)
96 }
97
98 pub fn is_down(self) -> bool {
99 matches!(self, Self::Pressed | Self::Hold)
100 }
101
102 pub fn is_changing(self) -> bool {
103 matches!(self, Self::Pressed | Self::Released)
104 }
105
106 pub fn is_continuing(self) -> bool {
107 matches!(self, Self::Idle | Self::Hold)
108 }
109
110 pub fn to_scalar(self, falsy: f32, truthy: f32) -> f32 {
111 if self.is_down() { truthy } else { falsy }
112 }
113}
114
115#[derive(Debug, Default, Clone, Copy, PartialEq)]
116pub struct InputAxis(pub f32);
117
118impl InputAxis {
119 pub fn threshold(self, value: f32) -> bool {
120 self.0 >= value
121 }
122}
123
124#[derive(Debug, Default, Clone)]
125pub struct InputRef<T: Default>(Arc<RwLock<T>>);
126
127impl<T: Default> InputRef<T> {
128 pub fn new(data: T) -> Self {
129 Self(Arc::new(RwLock::new(data)))
130 }
131
132 pub fn read(&self) -> Option<RwLockReadGuard<T>> {
133 self.0.read().ok()
134 }
135
136 pub fn write(&self) -> Option<RwLockWriteGuard<T>> {
137 self.0.write().ok()
138 }
139
140 pub fn get(&self) -> T
141 where
142 T: Clone,
143 {
144 self.read().map(|value| value.clone()).unwrap_or_default()
145 }
146
147 pub fn set(&self, value: T) {
148 if let Some(mut data) = self.write() {
149 *data = value;
150 }
151 }
152}
153
154pub type InputActionRef = InputRef<InputAction>;
155pub type InputAxisRef = InputRef<InputAxis>;
156pub type InputCharactersRef = InputRef<InputCharacters>;
157pub type InputMappingRef = InputRef<InputMapping>;
158
159#[derive(Debug, Default, Clone)]
160pub enum InputActionOrAxisRef {
161 #[default]
162 None,
163 Action(InputActionRef),
164 Axis(InputAxisRef),
165}
166
167impl InputActionOrAxisRef {
168 pub fn is_none(&self) -> bool {
169 matches!(self, Self::None)
170 }
171
172 pub fn is_some(&self) -> bool {
173 !self.is_none()
174 }
175
176 pub fn get_scalar(&self, falsy: f32, truthy: f32) -> f32 {
177 match self {
178 Self::None => falsy,
179 Self::Action(action) => action.get().to_scalar(falsy, truthy),
180 Self::Axis(axis) => axis.get().0,
181 }
182 }
183
184 pub fn threshold(&self, value: f32) -> bool {
185 match self {
186 Self::None => false,
187 Self::Action(action) => action.get().is_down(),
188 Self::Axis(axis) => axis.get().threshold(value),
189 }
190 }
191}
192
193impl From<InputActionRef> for InputActionOrAxisRef {
194 fn from(value: InputActionRef) -> Self {
195 Self::Action(value)
196 }
197}
198
199impl From<InputAxisRef> for InputActionOrAxisRef {
200 fn from(value: InputAxisRef) -> Self {
201 Self::Axis(value)
202 }
203}
204
205pub struct InputCombinator<T> {
206 mapper: Box<dyn Fn() -> T + Send + Sync>,
207}
208
209impl<T: Default> Default for InputCombinator<T> {
210 fn default() -> Self {
211 Self::new(|| T::default())
212 }
213}
214
215impl<T> InputCombinator<T> {
216 pub fn new(mapper: impl Fn() -> T + Send + Sync + 'static) -> Self {
217 Self {
218 mapper: Box::new(mapper),
219 }
220 }
221
222 pub fn get(&self) -> T {
223 (self.mapper)()
224 }
225}
226
227#[derive(Default)]
228pub struct CardinalInputCombinator(InputCombinator<[f32; 2]>);
229
230impl CardinalInputCombinator {
231 pub fn new(
232 left: impl Into<InputActionOrAxisRef>,
233 right: impl Into<InputActionOrAxisRef>,
234 up: impl Into<InputActionOrAxisRef>,
235 down: impl Into<InputActionOrAxisRef>,
236 ) -> Self {
237 let left = left.into();
238 let right = right.into();
239 let up = up.into();
240 let down = down.into();
241 Self(InputCombinator::new(move || {
242 let left = left.get_scalar(0.0, -1.0);
243 let right = right.get_scalar(0.0, 1.0);
244 let up = up.get_scalar(0.0, -1.0);
245 let down = down.get_scalar(0.0, 1.0);
246 [left + right, up + down]
247 }))
248 }
249
250 pub fn get(&self) -> [f32; 2] {
251 self.0.get()
252 }
253}
254
255#[derive(Default)]
256pub struct DualInputCombinator(InputCombinator<f32>);
257
258impl DualInputCombinator {
259 pub fn new(
260 negative: impl Into<InputActionOrAxisRef>,
261 positive: impl Into<InputActionOrAxisRef>,
262 ) -> Self {
263 let negative = negative.into();
264 let positive = positive.into();
265 Self(InputCombinator::new(move || {
266 let negative = negative.get_scalar(0.0, -1.0);
267 let positive = positive.get_scalar(0.0, 1.0);
268 negative + positive
269 }))
270 }
271
272 pub fn get(&self) -> f32 {
273 self.0.get()
274 }
275}
276
277pub struct ArrayInputCombinator<const N: usize>(InputCombinator<[f32; N]>);
278
279impl<const N: usize> Default for ArrayInputCombinator<N> {
280 fn default() -> Self {
281 Self(InputCombinator::new(|| [0.0; N]))
282 }
283}
284
285impl<const N: usize> ArrayInputCombinator<N> {
286 pub fn new(inputs: [impl Into<InputActionOrAxisRef>; N]) -> Self {
287 let items: [InputActionOrAxisRef; N] = inputs.map(|input| input.into());
288 Self(InputCombinator::new(move || {
289 std::array::from_fn(|index| items[index].get_scalar(0.0, 1.0))
290 }))
291 }
292
293 pub fn get(&self) -> [f32; N] {
294 self.0.get()
295 }
296}
297
298#[derive(Debug, Default, Clone)]
299pub struct InputCharacters {
300 characters: String,
301}
302
303impl InputCharacters {
304 pub fn read(&self) -> &str {
305 &self.characters
306 }
307
308 pub fn write(&mut self) -> &mut String {
309 &mut self.characters
310 }
311
312 pub fn take(&mut self) -> String {
313 std::mem::take(&mut self.characters)
314 }
315}
316
317#[derive(Debug, Default, Clone)]
318pub struct InputMapping {
319 pub actions: HashMap<VirtualAction, InputActionRef>,
320 pub axes: HashMap<VirtualAxis, InputAxisRef>,
321 pub consume: InputConsume,
322 pub layer: isize,
323 pub name: Cow<'static, str>,
324 pub gamepad: Option<GamepadId>,
325}
326
327impl InputMapping {
328 pub fn action(mut self, id: VirtualAction, action: InputActionRef) -> Self {
329 self.actions.insert(id, action);
330 self
331 }
332
333 pub fn axis(mut self, id: VirtualAxis, axis: InputAxisRef) -> Self {
334 self.axes.insert(id, axis);
335 self
336 }
337
338 pub fn consume(mut self, consume: InputConsume) -> Self {
339 self.consume = consume;
340 self
341 }
342
343 pub fn layer(mut self, value: isize) -> Self {
344 self.layer = value;
345 self
346 }
347
348 pub fn name(mut self, value: impl Into<Cow<'static, str>>) -> Self {
349 self.name = value.into();
350 self
351 }
352
353 pub fn gamepad(mut self, gamepad: GamepadId) -> Self {
354 self.gamepad = Some(gamepad);
355 self
356 }
357}
358
359impl From<InputMapping> for InputMappingRef {
360 fn from(value: InputMapping) -> Self {
361 Self::new(value)
362 }
363}
364
365#[derive(Debug)]
366pub struct InputContext {
367 pub mouse_wheel_line_scale: f32,
368 mappings_stack: Vec<(ID<InputMapping>, InputMappingRef)>,
370 characters: InputCharactersRef,
371 gamepads: Option<Gilrs>,
372}
373
374impl Default for InputContext {
375 fn default() -> Self {
376 Self {
377 mouse_wheel_line_scale: Self::default_mouse_wheel_line_scale(),
378 mappings_stack: Default::default(),
379 characters: Default::default(),
380 gamepads: None,
381 }
382 }
383}
384
385impl Clone for InputContext {
386 fn clone(&self) -> Self {
387 Self {
388 mouse_wheel_line_scale: self.mouse_wheel_line_scale,
389 mappings_stack: self.mappings_stack.clone(),
390 characters: self.characters.clone(),
391 gamepads: None,
392 }
393 }
394}
395
396impl InputContext {
397 fn default_mouse_wheel_line_scale() -> f32 {
398 10.0
399 }
400
401 pub fn with_gamepads(mut self) -> Self {
402 self.gamepads = Gilrs::new().ok();
403 self
404 }
405
406 pub fn with_gamepads_custom(mut self, gamepads: Gilrs) -> Self {
407 self.gamepads = Some(gamepads);
408 self
409 }
410
411 pub fn gamepads(&self) -> Option<&Gilrs> {
412 self.gamepads.as_ref()
413 }
414
415 pub fn gamepads_mut(&mut self) -> Option<&mut Gilrs> {
416 self.gamepads.as_mut()
417 }
418
419 pub fn push_mapping(&mut self, mapping: impl Into<InputMappingRef>) -> ID<InputMapping> {
420 let mapping = mapping.into();
421 let id = ID::default();
422 let layer = mapping.read().unwrap().layer;
423 let index = self
424 .mappings_stack
425 .binary_search_by(|(_, mapping)| {
426 mapping
427 .read()
428 .unwrap()
429 .layer
430 .cmp(&layer)
431 .then(Ordering::Less)
432 })
433 .unwrap_or_else(|index| index);
434 self.mappings_stack.insert(index, (id, mapping));
435 id
436 }
437
438 pub fn pop_mapping(&mut self) -> Option<InputMappingRef> {
439 self.mappings_stack.pop().map(|(_, mapping)| mapping)
440 }
441
442 pub fn top_mapping(&self) -> Option<&InputMappingRef> {
443 self.mappings_stack.last().map(|(_, mapping)| mapping)
444 }
445
446 pub fn remove_mapping(&mut self, id: ID<InputMapping>) -> Option<InputMappingRef> {
447 self.mappings_stack
448 .iter()
449 .position(|(mid, _)| mid == &id)
450 .map(|index| self.mappings_stack.remove(index).1)
451 }
452
453 pub fn mapping(&self, id: ID<InputMapping>) -> Option<RwLockReadGuard<InputMapping>> {
454 self.mappings_stack
455 .iter()
456 .find(|(mid, _)| mid == &id)
457 .and_then(|(_, mapping)| mapping.read())
458 }
459
460 pub fn stack(&self) -> impl Iterator<Item = &InputMappingRef> {
461 self.mappings_stack.iter().map(|(_, mapping)| mapping)
462 }
463
464 pub fn characters(&self) -> InputCharactersRef {
465 self.characters.clone()
466 }
467
468 pub fn maintain(&mut self) {
469 if let Some(gamepads) = self.gamepads.as_mut() {
470 while let Some(GamepadEvent { id, event, .. }) = gamepads.next_event() {
471 match event {
472 GamepadEventType::ButtonPressed(info, ..) => {
473 for (_, mapping) in self.mappings_stack.iter().rev() {
474 if let Some(mapping) = mapping.read() {
475 if !mapping.gamepad.map(|gamepad| gamepad == id).unwrap_or(true) {
476 continue;
477 }
478 let mut consume = mapping.consume == InputConsume::All;
479 for (id, data) in &mapping.actions {
480 if let VirtualAction::GamepadButton(button) = id {
481 if *button == info {
482 if let Some(mut data) = data.write() {
483 *data = data.change(true);
484 if mapping.consume == InputConsume::Hit {
485 consume = true;
486 }
487 }
488 }
489 }
490 }
491 for (id, data) in &mapping.axes {
492 if let VirtualAxis::GamepadButton(button) = id {
493 if *button == info {
494 if let Some(mut data) = data.write() {
495 data.0 = 1.0;
496 if mapping.consume == InputConsume::Hit {
497 consume = true;
498 }
499 }
500 }
501 }
502 }
503 if consume {
504 break;
505 }
506 }
507 }
508 }
509 GamepadEventType::ButtonReleased(info, ..) => {
510 for (_, mapping) in self.mappings_stack.iter().rev() {
511 if let Some(mapping) = mapping.read() {
512 if !mapping.gamepad.map(|gamepad| gamepad == id).unwrap_or(true) {
513 continue;
514 }
515 let mut consume = mapping.consume == InputConsume::All;
516 for (id, data) in &mapping.actions {
517 if let VirtualAction::GamepadButton(button) = id {
518 if *button == info {
519 if let Some(mut data) = data.write() {
520 *data = data.change(false);
521 if mapping.consume == InputConsume::Hit {
522 consume = true;
523 }
524 }
525 }
526 }
527 }
528 for (id, data) in &mapping.axes {
529 if let VirtualAxis::GamepadButton(button) = id {
530 if *button == info {
531 if let Some(mut data) = data.write() {
532 data.0 = 0.0;
533 if mapping.consume == InputConsume::Hit {
534 consume = true;
535 }
536 }
537 }
538 }
539 }
540 if consume {
541 break;
542 }
543 }
544 }
545 }
546 GamepadEventType::AxisChanged(info, value, ..) => {
547 for (_, mapping) in self.mappings_stack.iter().rev() {
548 if let Some(mapping) = mapping.read() {
549 let mut consume = mapping.consume == InputConsume::All;
550 for (id, data) in &mapping.actions {
551 if let VirtualAction::GamepadAxis(axis) = id {
552 if *axis == info {
553 if let Some(mut data) = data.write() {
554 *data = data.change(value > 0.5);
555 if mapping.consume == InputConsume::Hit {
556 consume = true;
557 }
558 }
559 }
560 }
561 }
562 for (id, data) in &mapping.axes {
563 if let VirtualAxis::GamepadAxis(axis) = id {
564 if *axis == info {
565 if let Some(mut data) = data.write() {
566 data.0 = value;
567 if mapping.consume == InputConsume::Hit {
568 consume = true;
569 }
570 }
571 }
572 }
573 }
574 if consume {
575 break;
576 }
577 }
578 }
579 }
580 _ => {}
581 }
582 }
583 gamepads.inc();
584 }
585 for (_, mapping) in &mut self.mappings_stack {
586 if let Some(mut mapping) = mapping.write() {
587 for action in mapping.actions.values_mut() {
588 if let Some(mut action) = action.write() {
589 *action = action.update();
590 }
591 }
592 for (id, axis) in &mut mapping.axes {
593 if let VirtualAxis::MouseWheelX | VirtualAxis::MouseWheelY = id {
594 if let Some(mut axis) = axis.write() {
595 axis.0 = 0.0;
596 }
597 }
598 }
599 }
600 }
601 }
602
603 pub fn on_event(&mut self, event: &WindowEvent) {
604 match event {
605 WindowEvent::ReceivedCharacter(character) => {
606 if let Some(mut characters) = self.characters.write() {
607 characters.characters.push(*character);
608 }
609 }
610 WindowEvent::KeyboardInput { input, .. } => {
611 if let Some(key) = input.virtual_keycode {
612 for (_, mapping) in self.mappings_stack.iter().rev() {
613 if let Some(mapping) = mapping.read() {
614 let mut consume = mapping.consume == InputConsume::All;
615 for (id, data) in &mapping.actions {
616 if let VirtualAction::KeyButton(button) = id {
617 if *button == key {
618 if let Some(mut data) = data.write() {
619 *data =
620 data.change(input.state == ElementState::Pressed);
621 if mapping.consume == InputConsume::Hit {
622 consume = true;
623 }
624 }
625 }
626 }
627 }
628 for (id, data) in &mapping.axes {
629 if let VirtualAxis::KeyButton(button) = id {
630 if *button == key {
631 if let Some(mut data) = data.write() {
632 data.0 = if input.state == ElementState::Pressed {
633 1.0
634 } else {
635 0.0
636 };
637 if mapping.consume == InputConsume::Hit {
638 consume = true;
639 }
640 }
641 }
642 }
643 }
644 if consume {
645 break;
646 }
647 }
648 }
649 }
650 }
651 WindowEvent::CursorMoved { position, .. } => {
652 for (_, mapping) in self.mappings_stack.iter().rev() {
653 if let Some(mapping) = mapping.read() {
654 let mut consume = mapping.consume == InputConsume::All;
655 for (id, data) in &mapping.axes {
656 match id {
657 VirtualAxis::MousePositionX => {
658 if let Some(mut data) = data.write() {
659 data.0 = position.x as _;
660 if mapping.consume == InputConsume::Hit {
661 consume = true;
662 }
663 }
664 }
665 VirtualAxis::MousePositionY => {
666 if let Some(mut data) = data.write() {
667 data.0 = position.y as _;
668 if mapping.consume == InputConsume::Hit {
669 consume = true;
670 }
671 }
672 }
673 _ => {}
674 }
675 }
676 if consume {
677 break;
678 }
679 }
680 }
681 }
682 WindowEvent::MouseWheel { delta, .. } => {
683 for (_, mapping) in self.mappings_stack.iter().rev() {
684 if let Some(mapping) = mapping.read() {
685 let mut consume = mapping.consume == InputConsume::All;
686 for (id, data) in &mapping.axes {
687 match id {
688 VirtualAxis::MouseWheelX => {
689 if let Some(mut data) = data.write() {
690 data.0 = match delta {
691 MouseScrollDelta::LineDelta(x, _) => *x,
692 MouseScrollDelta::PixelDelta(pos) => pos.x as _,
693 };
694 if mapping.consume == InputConsume::Hit {
695 consume = true;
696 }
697 }
698 }
699 VirtualAxis::MouseWheelY => {
700 if let Some(mut data) = data.write() {
701 data.0 = match delta {
702 MouseScrollDelta::LineDelta(_, y) => *y,
703 MouseScrollDelta::PixelDelta(pos) => pos.y as _,
704 };
705 if mapping.consume == InputConsume::Hit {
706 consume = true;
707 }
708 }
709 }
710 _ => {}
711 }
712 }
713 if consume {
714 break;
715 }
716 }
717 }
718 }
719 WindowEvent::MouseInput { state, button, .. } => {
720 for (_, mapping) in self.mappings_stack.iter().rev() {
721 if let Some(mapping) = mapping.read() {
722 let mut consume = mapping.consume == InputConsume::All;
723 for (id, data) in &mapping.actions {
724 if let VirtualAction::MouseButton(btn) = id {
725 if button == btn {
726 if let Some(mut data) = data.write() {
727 *data = data.change(*state == ElementState::Pressed);
728 if mapping.consume == InputConsume::Hit {
729 consume = true;
730 }
731 }
732 }
733 }
734 }
735 for (id, data) in &mapping.axes {
736 if let VirtualAxis::MouseButton(btn) = id {
737 if button == btn {
738 if let Some(mut data) = data.write() {
739 data.0 = if *state == ElementState::Pressed {
740 1.0
741 } else {
742 0.0
743 };
744 if mapping.consume == InputConsume::Hit {
745 consume = true;
746 }
747 }
748 }
749 }
750 }
751 if consume {
752 break;
753 }
754 }
755 }
756 }
757 WindowEvent::AxisMotion { axis, value, .. } => {
758 for (_, mapping) in self.mappings_stack.iter().rev() {
759 if let Some(mapping) = mapping.read() {
760 let mut consume = mapping.consume == InputConsume::All;
761 for (id, data) in &mapping.actions {
762 if let VirtualAction::Axis(index) = id {
763 if axis == index {
764 if let Some(mut data) = data.write() {
765 *data = data.change(value.abs() > 0.5);
766 if mapping.consume == InputConsume::Hit {
767 consume = true;
768 }
769 }
770 }
771 }
772 }
773 for (id, data) in &mapping.axes {
774 if let VirtualAxis::Axis(index) = id {
775 if axis == index {
776 if let Some(mut data) = data.write() {
777 data.0 = *value as _;
778 if mapping.consume == InputConsume::Hit {
779 consume = true;
780 }
781 }
782 }
783 }
784 }
785 if consume {
786 break;
787 }
788 }
789 }
790 }
791 _ => {}
792 }
793 }
794}
795
796#[cfg(test)]
797mod tests {
798 use super::*;
799
800 #[test]
801 fn test_stack() {
802 let mut context = InputContext::default();
803 context.push_mapping(InputMapping::default().name("a").layer(0));
804 context.push_mapping(InputMapping::default().name("b").layer(0));
805 context.push_mapping(InputMapping::default().name("c").layer(0));
806 context.push_mapping(InputMapping::default().name("d").layer(-1));
807 context.push_mapping(InputMapping::default().name("e").layer(1));
808 context.push_mapping(InputMapping::default().name("f").layer(-1));
809 context.push_mapping(InputMapping::default().name("g").layer(1));
810 context.push_mapping(InputMapping::default().name("h").layer(-2));
811 context.push_mapping(InputMapping::default().name("i").layer(-2));
812 context.push_mapping(InputMapping::default().name("j").layer(2));
813 context.push_mapping(InputMapping::default().name("k").layer(2));
814
815 let provided = context
816 .stack()
817 .map(|mapping| {
818 let mapping = mapping.read().unwrap();
819 (mapping.name.as_ref().to_owned(), mapping.layer)
820 })
821 .collect::<Vec<_>>();
822 assert_eq!(
823 provided,
824 vec![
825 ("h".to_owned(), -2),
826 ("i".to_owned(), -2),
827 ("d".to_owned(), -1),
828 ("f".to_owned(), -1),
829 ("a".to_owned(), 0),
830 ("b".to_owned(), 0),
831 ("c".to_owned(), 0),
832 ("e".to_owned(), 1),
833 ("g".to_owned(), 1),
834 ("j".to_owned(), 2),
835 ("k".to_owned(), 2),
836 ]
837 );
838 }
839}