Skip to main content

dancepredictor/
feet.rs

1use crate::extensions::HasPressRequirement;
2use crate::stage::DanceStage;
3use danceparser::Row;
4use std::fmt::{Debug, Display, Formatter};
5
6#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq, Hash)]
7pub enum Side {
8    Left,
9    Right,
10}
11
12/// Possible foot placement states for a column.
13#[derive(Clone, Copy, PartialOrd, PartialEq, Eq, Hash)]
14pub enum FootPart {
15    None,
16    LeftHeel,
17    LeftToe,
18    RightHeel,
19    RightToe,
20}
21
22impl FootPart {
23    pub(crate) fn all_except_none() -> [Self; 4] {
24        [
25            Self::LeftHeel,
26            Self::LeftToe,
27            Self::RightHeel,
28            Self::RightToe,
29        ]
30    }
31
32    pub const fn parse(c: char) -> Option<Self> {
33        match c {
34            'L' => Some(Self::LeftHeel),
35            'l' => Some(Self::LeftToe),
36            'R' => Some(Self::RightHeel),
37            'r' => Some(Self::RightToe),
38            '-' => Some(Self::None),
39            _ => None,
40        }
41    }
42
43    pub(crate) fn is_toe(&self) -> bool {
44        matches!(self, FootPart::LeftToe | FootPart::RightToe)
45    }
46
47    pub(crate) fn is_heel(&self) -> bool {
48        matches!(self, FootPart::LeftHeel | FootPart::RightHeel)
49    }
50
51    pub(crate) fn side(&self) -> Option<Side> {
52        match self {
53            FootPart::None => None,
54            FootPart::LeftHeel => Some(Side::Left),
55            FootPart::LeftToe => Some(Side::Left),
56            FootPart::RightHeel => Some(Side::Right),
57            FootPart::RightToe => Some(Side::Right),
58        }
59    }
60
61    pub(crate) fn heel(side: Side) -> FootPart {
62        match side {
63            Side::Left => FootPart::LeftHeel,
64            Side::Right => FootPart::RightHeel,
65        }
66    }
67
68    pub(crate) fn toe(side: Side) -> FootPart {
69        match side {
70            Side::Left => FootPart::LeftToe,
71            Side::Right => FootPart::RightToe,
72        }
73    }
74
75    pub(crate) fn other_part(&self) -> FootPart {
76        if self.is_toe() {
77            FootPart::heel(self.side().unwrap())
78        } else if self.is_heel() {
79            FootPart::toe(self.side().unwrap())
80        } else {
81            FootPart::None
82        }
83    }
84}
85
86impl Debug for FootPart {
87    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88        <Self as Display>::fmt(self, f)
89    }
90}
91
92impl Display for FootPart {
93    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
94        match self {
95            FootPart::None => write!(f, "-"),
96            FootPart::LeftHeel => write!(f, "L"),
97            FootPart::LeftToe => write!(f, "l"),
98            FootPart::RightHeel => write!(f, "R"),
99            FootPart::RightToe => write!(f, "r"),
100        }
101    }
102}
103
104/// Represents what foot part is on a column.
105#[derive(Clone, PartialEq, Eq, Hash)]
106pub struct FootPlacement(pub Vec<FootPart>);
107
108impl FootPlacement {
109    pub fn new(columns: usize) -> Self {
110        FootPlacement(vec![FootPart::None; columns])
111    }
112
113    pub fn from_ddr_solo(left: FootPart, down: FootPart, up: FootPart, right: FootPart) -> Self {
114        FootPlacement(vec![left, down, up, right])
115    }
116
117    pub fn parse(s: &str) -> Option<Self> {
118        s.chars()
119            .map(|c| FootPart::parse(c))
120            .collect::<Option<_>>()
121            .map(|v| FootPlacement(v))
122    }
123
124    pub(crate) fn get_foot_part_index(&self, part: FootPart) -> Option<usize> {
125        self.0.iter().position(|&x| x == part)
126    }
127
128    pub(crate) fn get_foot_part_indices(&self) -> FootPartIndices {
129        FootPartIndices {
130            left_heel: self.get_foot_part_index(FootPart::LeftHeel),
131            left_toe: self.get_foot_part_index(FootPart::LeftToe),
132            right_heel: self.get_foot_part_index(FootPart::RightHeel),
133            right_toe: self.get_foot_part_index(FootPart::RightToe),
134        }
135    }
136
137    pub(crate) fn contains(&self, part: FootPart) -> bool {
138        self.0.iter().any(|&x| x == part)
139    }
140
141    pub(crate) fn is_bracketing(&self, side: Side) -> bool {
142        match side {
143            Side::Left => self.contains(FootPart::LeftToe) && self.contains(FootPart::LeftHeel),
144            Side::Right => self.contains(FootPart::RightToe) && self.contains(FootPart::RightHeel),
145        }
146    }
147
148    pub(crate) fn at(&self, column_idx: usize) -> FootPart {
149        self.0[column_idx]
150    }
151
152    pub(crate) fn at_mut(&mut self, column_idx: usize) -> &mut FootPart {
153        &mut self.0[column_idx]
154    }
155}
156
157impl Debug for FootPlacement {
158    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
159        <Self as Display>::fmt(self, f)
160    }
161}
162
163impl Display for FootPlacement {
164    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
165        for part in &self.0 {
166            <FootPart as Display>::fmt(&part, f)?;
167        }
168        Ok(())
169    }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub struct FootPartIndices {
174    pub left_heel: Option<usize>,
175    pub left_toe: Option<usize>,
176    pub right_heel: Option<usize>,
177    pub right_toe: Option<usize>,
178}
179
180pub(crate) fn foot_placement_permutations(stage: &DanceStage, row: &Row) -> Vec<FootPlacement> {
181    let mut permutations = Vec::new();
182
183    permute_foot_placement(
184        &mut permutations,
185        stage,
186        row,
187        &FootPlacement::new(row.columns.len()),
188        0,
189    );
190
191    permutations
192}
193
194fn permute_foot_placement(
195    permutations: &mut Vec<FootPlacement>,
196    stage: &DanceStage,
197    row: &Row,
198    current_placement: &FootPlacement,
199    column: usize,
200) {
201    if column >= row.columns.len() {
202        let indices = current_placement.get_foot_part_indices();
203
204        // TODO This algorithm assumes you will always use the heel at least,
205        // TODO and never exclusively the toes (unlike what a player should do)
206        let invalid_left_toe = matches!((indices.left_heel, indices.left_toe), (None, Some(_)));
207        let invalid_right_toe = matches!((indices.right_heel, indices.right_toe), (None, Some(_)));
208        if invalid_left_toe || invalid_right_toe {
209            return;
210        }
211
212        // Check for impossible brackets (e.g UP + DOWN, LEFT + RIGHT)
213        let valid_left_bracket = !current_placement.is_bracketing(Side::Left)
214            || stage.is_valid_bracket(indices.left_heel.unwrap(), indices.left_toe.unwrap());
215        let valid_right_bracket = !current_placement.is_bracketing(Side::Right)
216            || stage.is_valid_bracket(indices.right_heel.unwrap(), indices.right_toe.unwrap());
217        if !valid_left_bracket || !valid_right_bracket {
218            return;
219        }
220
221        permutations.push(current_placement.clone());
222        return;
223    }
224
225    if let Some(note) = row.columns.get(column)
226        && note.require_press()
227    {
228        let mut new_placement = current_placement.clone();
229        for foot_part in FootPart::all_except_none() {
230            if current_placement.contains(foot_part) {
231                continue;
232            }
233
234            new_placement.0[column] = foot_part;
235            permute_foot_placement(permutations, stage, row, &new_placement, column + 1);
236        }
237
238        return;
239    }
240
241    permute_foot_placement(permutations, stage, row, &current_placement, column + 1);
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use danceparser::NoteKind;
248
249    #[test]
250    fn test_tap_permutations() {
251        let permutations = foot_placement_permutations(
252            &DanceStage::ddr_solo(),
253            &Row {
254                columns: vec![
255                    NoteKind::Tap,
256                    NoteKind::Empty,
257                    NoteKind::Empty,
258                    NoteKind::Empty,
259                ],
260            },
261        );
262
263        assert_eq!(
264            permutations,
265            vec![
266                FootPlacement::from_ddr_solo(
267                    FootPart::LeftHeel,
268                    FootPart::None,
269                    FootPart::None,
270                    FootPart::None,
271                ),
272                FootPlacement::from_ddr_solo(
273                    FootPart::RightHeel,
274                    FootPart::None,
275                    FootPart::None,
276                    FootPart::None,
277                )
278            ]
279        );
280    }
281
282    #[test]
283    fn test_jump_permutations() {
284        let permutations = foot_placement_permutations(
285            &DanceStage::ddr_solo(),
286            &Row {
287                columns: vec![
288                    NoteKind::Tap,
289                    NoteKind::Empty,
290                    NoteKind::Empty,
291                    NoteKind::Tap,
292                ],
293            },
294        );
295
296        assert_eq!(
297            permutations,
298            vec![
299                FootPlacement::from_ddr_solo(
300                    FootPart::LeftHeel,
301                    FootPart::None,
302                    FootPart::None,
303                    FootPart::RightHeel,
304                ),
305                FootPlacement::from_ddr_solo(
306                    FootPart::RightHeel,
307                    FootPart::None,
308                    FootPart::None,
309                    FootPart::LeftHeel,
310                )
311            ]
312        );
313    }
314
315    #[test]
316    fn test_bracket_permutations() {
317        let permutations = foot_placement_permutations(
318            &DanceStage::ddr_solo(),
319            &Row {
320                columns: vec![
321                    NoteKind::Tap,
322                    NoteKind::Tap,
323                    NoteKind::Empty,
324                    NoteKind::Empty,
325                ],
326            },
327        );
328
329        assert_eq!(
330            permutations,
331            vec![
332                FootPlacement::from_ddr_solo(
333                    FootPart::LeftHeel,
334                    FootPart::LeftToe,
335                    FootPart::None,
336                    FootPart::None
337                ),
338                FootPlacement::from_ddr_solo(
339                    FootPart::LeftHeel,
340                    FootPart::RightHeel,
341                    FootPart::None,
342                    FootPart::None
343                ),
344                FootPlacement::from_ddr_solo(
345                    FootPart::LeftToe,
346                    FootPart::LeftHeel,
347                    FootPart::None,
348                    FootPart::None
349                ),
350                FootPlacement::from_ddr_solo(
351                    FootPart::RightHeel,
352                    FootPart::LeftHeel,
353                    FootPart::None,
354                    FootPart::None
355                ),
356                FootPlacement::from_ddr_solo(
357                    FootPart::RightHeel,
358                    FootPart::RightToe,
359                    FootPart::None,
360                    FootPart::None
361                ),
362                FootPlacement::from_ddr_solo(
363                    FootPart::RightToe,
364                    FootPart::RightHeel,
365                    FootPart::None,
366                    FootPart::None
367                )
368            ]
369        );
370    }
371}