rotchess_core/
piece.rs

1use std::{collections::HashSet, f32::consts::PI, hash::Hash};
2
3use crate::{emulator::TravelKind, turn::Score};
4
5/// An iterable over the distances of a [`DistancesAngle`].
6///
7/// There was an intentional choice to make this iterable just the distances,
8/// and not repeatly return the angle. We are guaranteed to maintain the same
9/// angle as the `DistancesAngle` this iterable was generated from, so just get
10/// the angle from there.
11struct IterableDA {
12    curr: f32,
13    step: f32,
14    inclusive_upper_bound: f32,
15}
16
17impl Iterator for IterableDA {
18    type Item = f32;
19
20    fn next(&mut self) -> Option<Self::Item> {
21        if self.curr > self.inclusive_upper_bound {
22            return None;
23        }
24        let ans = self.curr;
25        self.curr += self.step;
26        Some(ans)
27    }
28}
29
30/// An iterator of (x, y) distances and an angle with which to advance them.
31#[derive(Debug, Clone, Copy)]
32struct DistancesAngle {
33    start: f32,
34    step: f32,
35    inclusive_upper_bound: f32,
36    /// Used as a piece's angle *offset*.
37    ///
38    /// An angle of 0 means the piece is facing "forward." This means it has an actual
39    /// angle of who knows what.
40    angle: f32,
41}
42
43impl IntoIterator for DistancesAngle {
44    type Item = f32;
45
46    type IntoIter = IterableDA;
47
48    fn into_iter(self) -> Self::IntoIter {
49        IterableDA {
50            curr: self.start,
51            step: self.step,
52            inclusive_upper_bound: self.inclusive_upper_bound,
53        }
54    }
55}
56
57impl DistancesAngle {
58    const fn singleton(distance: f32, angle: f32) -> Self {
59        Self {
60            start: distance,
61            step: 1.,
62            inclusive_upper_bound: distance,
63            angle,
64        }
65    }
66
67    const fn repeated(start: f32, step: f32, n: i32, angle: f32) -> Self {
68        Self {
69            start,
70            step,
71            inclusive_upper_bound: start + step * (n - 1) as f32,
72            angle,
73        }
74    }
75
76    const fn range(start: f32, step: f32, inclusive_upper_bound: f32, angle: f32) -> Self {
77        Self {
78            start,
79            step,
80            inclusive_upper_bound,
81            angle,
82        }
83    }
84}
85
86impl DistancesAngle {
87    /// Get the point offsets when a piece rotated by angle applies this DistancesAngle.
88    fn get_offsets(&self, angle: f32) -> impl Iterator<Item = (f32, f32)> {
89        self.clone()
90            .into_iter()
91            .map(move |d| self.get_point(d, self.angle, angle))
92    }
93
94    /// .
95    ///
96    /// Angle in radians.
97    fn get_point(&self, distance: f32, base_angle: f32, offset_angle: f32) -> (f32, f32) {
98        let angle = base_angle - offset_angle;
99        crate::floating_drift::floating_drift_adjust!(
100            distance * f32::cos(angle),
101            distance * f32::sin(angle),
102        )
103    }
104}
105
106#[cfg(test)]
107mod da_tests {
108    use super::DistancesAngle;
109
110    #[test]
111    fn rep() {
112        let n = 5;
113        let da = DistancesAngle::repeated(1., 2., n, 5.);
114        assert_eq!(n as usize, da.into_iter().collect::<Vec<f32>>().len());
115    }
116
117    #[test]
118    fn rep_45deg() {
119        let (start, step, n, angle) = (0., f32::sqrt(2.), 4, 45.0_f32.to_radians());
120        let da = DistancesAngle::repeated(start, step, n, angle);
121        let offset_angle = 0.;
122        assert_eq![
123            vec![(0., 0.), (1., 1.), (2., 2.), (3., 3.)],
124            da.get_offsets(offset_angle).collect::<Vec<(f32, f32)>>()
125        ];
126    }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq)]
130pub enum Side {
131    Black,
132    White,
133}
134
135impl Side {
136    pub fn to_file_desc(&self) -> &str {
137        match self {
138            Side::Black => "B",
139            Side::White => "W",
140        }
141    }
142
143    pub fn toggle(&self) -> Self {
144        match self {
145            Side::Black => Side::White,
146            Side::White => Side::Black,
147        }
148    }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq)]
152pub enum PieceKind {
153    Pawn,
154    Rook,
155    Knight,
156    Bishop,
157    Queen,
158    King,
159}
160
161impl PieceKind {
162    pub fn to_file_desc(&self) -> &str {
163        match self {
164            PieceKind::Pawn => "pawn",
165            PieceKind::Rook => "rook",
166            PieceKind::Knight => "knight",
167            PieceKind::Bishop => "bishop",
168            PieceKind::Queen => "queen",
169            PieceKind::King => "king",
170        }
171    }
172
173    pub fn value(&self) -> Score {
174        match self {
175            PieceKind::Pawn => 1.0,
176            PieceKind::Rook => 5.0,
177            PieceKind::Knight => 3.0,
178            PieceKind::Bishop => 3.0,
179            PieceKind::Queen => 9.0,
180            PieceKind::King => 1000.0,
181        }
182    }
183
184    pub fn can_jump(&self) -> bool {
185        match self {
186            PieceKind::Pawn => true,
187            PieceKind::King => true,
188            PieceKind::Knight => true,
189            PieceKind::Rook => false,
190            PieceKind::Bishop => false,
191            PieceKind::Queen => false,
192        }
193    }
194
195    pub fn can_promote(&self) -> bool {
196        *self == PieceKind::Pawn
197    }
198
199    /// Add the DAs of a rook to `v`.
200    fn add_level_das(v: &mut Vec<DistancesAngle>) {
201        for i in 0..4 {
202            v.push(DistancesAngle::range(
203                1.,
204                1.,
205                f32::INFINITY,
206                i as f32 * PI / 2.,
207            ))
208        }
209    }
210
211    /// From distance formula with 1 and 2.
212    const KNIGHT_DISTANCE: f32 = 2.23606797749979;
213
214    const KNIGHT_ANGLES: [f32; 8] = [
215        0.4636476090008061,
216        -0.4636476090008061,
217        -1.1071487177940904,
218        -2.0344439357957027,
219        -2.677945044588987,
220        2.677945044588987,
221        2.0344439357957027,
222        1.1071487177940904,
223    ];
224
225    /// Add the DAs of a bishop to `v`.
226    fn add_diag_das(v: &mut Vec<DistancesAngle>) {
227        for i in 0..4 {
228            v.push(DistancesAngle::range(
229                f32::sqrt(2.),
230                f32::sqrt(2.),
231                f32::INFINITY,
232                (i as f32 * PI / 2.) + (PI / 4.),
233            ))
234        }
235    }
236
237    fn get_capture_das(&self) -> Vec<DistancesAngle> {
238        let mut ans = vec![];
239        match self {
240            PieceKind::Pawn => {
241                ans.push(DistancesAngle::singleton(f32::sqrt(2.), PI / 4.));
242                ans.push(DistancesAngle::singleton(f32::sqrt(2.), -PI / 4.));
243            }
244            PieceKind::King => {
245                for i in 0..8 {
246                    ans.push(DistancesAngle::singleton(
247                        if i % 2 == 0 { 1. } else { f32::sqrt(2.) },
248                        i as f32 * PI / 4.,
249                    ))
250                }
251            }
252            PieceKind::Knight => {
253                for rad in PieceKind::KNIGHT_ANGLES {
254                    ans.push(DistancesAngle::singleton(PieceKind::KNIGHT_DISTANCE, rad));
255                }
256            }
257            PieceKind::Rook => PieceKind::add_level_das(&mut ans),
258            PieceKind::Bishop => PieceKind::add_diag_das(&mut ans),
259            PieceKind::Queen => {
260                PieceKind::add_level_das(&mut ans);
261                PieceKind::add_diag_das(&mut ans);
262            }
263        };
264        ans
265    }
266
267    fn get_move_das(&self) -> Vec<DistancesAngle> {
268        let mut ans = vec![];
269        match self {
270            PieceKind::Pawn => ans.push(DistancesAngle::repeated(1., 1., 2, 0.)),
271            PieceKind::King => {
272                for i in 0..8 {
273                    ans.push(DistancesAngle::singleton(
274                        if i % 2 == 0 { 1. } else { f32::sqrt(2.) },
275                        i as f32 * PI / 4.,
276                    ))
277                }
278            }
279            PieceKind::Knight => {
280                for rad in PieceKind::KNIGHT_ANGLES {
281                    ans.push(DistancesAngle::singleton(PieceKind::KNIGHT_DISTANCE, rad));
282                }
283            }
284            PieceKind::Rook => PieceKind::add_level_das(&mut ans),
285            PieceKind::Bishop => PieceKind::add_diag_das(&mut ans),
286            PieceKind::Queen => {
287                PieceKind::add_level_das(&mut ans);
288                PieceKind::add_diag_das(&mut ans);
289            }
290        };
291        ans
292    }
293}
294
295/// Radius of a piece in rotchess-units.
296///
297/// 17/50 is parity from rotchess-python, where a tile was 50 pixels wide and
298/// a piece had a radius of 17 pixels.
299pub const PIECE_RADIUS: f32 = 17.0 / 50.0;
300
301/// The data about a piece that matters.
302///
303/// Everything else (i.e. delayable piece data) can be derived from this.
304/// That is, this is the minimum of what needs to be serde'd for a game save
305/// to understand what defines a piece.
306#[derive(Debug, Clone)]
307struct CorePieceData {
308    center: (f32, f32),
309    angle: f32,
310    side: Side,
311    kind: PieceKind,
312}
313
314impl Hash for CorePieceData {
315    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
316        self.center.0.to_be_bytes().hash(state);
317        self.center.1.to_be_bytes().hash(state);
318    }
319}
320
321impl PartialEq for CorePieceData {
322    fn eq(&self, other: &Self) -> bool {
323        self.center == other.center
324            && self.angle == other.angle
325            && self.side == other.side
326            && self.kind == other.kind
327    }
328}
329impl Eq for CorePieceData {}
330
331/// Delayable piece data.
332///
333/// Some piece data will only be created (un-None'd) when the piece is first moved.
334/// this speeds up loading a game save LOTS.
335/// so, we require as an invariant that self.init() is called sometime before any
336/// forbidden methods are called. this should be enforced with assertions.
337/// this can (and probably is) done when a piece is clicked in normal game code,
338/// but for test code we need to hack it in somewhere else that's intuitive.
339#[derive(Debug, Clone)]
340struct SecondaryPieceData {
341    /// set by init_movement
342    capture_das: Vec<DistancesAngle>,
343    /// set by init_movement
344    move_das: Vec<DistancesAngle>,
345}
346
347impl From<&CorePieceData> for SecondaryPieceData {
348    fn from(core: &CorePieceData) -> Self {
349        Self {
350            capture_das: core.kind.get_capture_das(),
351            move_das: core.kind.get_move_das(),
352        }
353    }
354}
355
356#[derive(Clone)]
357struct TertiaryPieceData {
358    /// set by init_capture_points
359    capture_points: Vec<(f32, f32)>,
360    /// set by init_move_points
361    move_points: Vec<(f32, f32)>,
362}
363
364impl From<(&CorePieceData, &SecondaryPieceData)> for TertiaryPieceData {
365    fn from((core, sec): (&CorePieceData, &SecondaryPieceData)) -> Self {
366        let mut capture_points = vec![];
367        let mut move_points = vec![];
368        Piece::extend_with_drawable_points(core, &mut capture_points, sec.capture_das.iter());
369        Piece::extend_with_drawable_points(core, &mut move_points, sec.move_das.iter());
370        Self {
371            capture_points,
372            move_points,
373        }
374    }
375}
376
377#[derive(Clone)]
378pub struct Piece {
379    core: CorePieceData,
380    secondary: Option<SecondaryPieceData>,
381    tertiary: Option<TertiaryPieceData>,
382}
383
384impl std::fmt::Display for Piece {
385    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
386        write!(
387            f,
388            "Piece(x={}, y={}, side={:?}), kind={:?}",
389            self.core.center.0, self.core.center.1, self.core.side, self.core.kind
390        )
391    }
392}
393
394impl Hash for Piece {
395    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
396        self.core.hash(state);
397    }
398}
399
400impl PartialEq for Piece {
401    fn eq(&self, other: &Self) -> bool {
402        self.core == other.core
403    }
404}
405impl Eq for Piece {}
406
407/// Instantiation.
408impl Piece {
409    pub fn new(center: (f32, f32), angle: f32, side: Side, kind: PieceKind) -> Self {
410        Self {
411            core: CorePieceData {
412                center,
413                angle,
414                side,
415                kind,
416            },
417            secondary: None,
418            tertiary: None,
419        }
420    }
421
422    /// From tile indices. i.e. tile (0,0) is center (0.5, 0.5).
423    pub fn from_tile(tile: (u8, u8), angle: f32, side: Side, kind: PieceKind) -> Self {
424        let (x, y) = tile;
425        Self {
426            core: CorePieceData {
427                center: (x as f32 + 0.5, y as f32 + 0.5),
428                angle,
429                side,
430                kind,
431            },
432            secondary: None,
433            tertiary: None,
434        }
435    }
436}
437
438/// Trivial getters and setters.
439impl Piece {
440    pub fn center(&self) -> (f32, f32) {
441        self.core.center
442    }
443
444    pub fn x(&self) -> f32 {
445        self.core.center.0
446    }
447
448    pub fn set_x(&mut self, x: f32) {
449        self.core.center.0 = x;
450    }
451
452    pub fn y(&self) -> f32 {
453        self.core.center.1
454    }
455
456    pub fn set_y(&mut self, y: f32) {
457        self.core.center.1 = y;
458    }
459
460    pub fn set_pos(&mut self, x: f32, y: f32) {
461        self.core.center.0 = x;
462        self.core.center.1 = y;
463    }
464
465    pub fn angle(&self) -> f32 {
466        self.core.angle
467    }
468
469    pub fn set_angle(&mut self, angle: f32) {
470        self.core.angle = angle;
471    }
472
473    pub fn side(&self) -> Side {
474        self.core.side
475    }
476
477    pub fn kind(&self) -> PieceKind {
478        self.core.kind
479    }
480
481    pub fn set_kind(&mut self, kind: PieceKind) {
482        self.core.kind = kind;
483    }
484
485    pub fn needs_init(&self) -> bool {
486        self.secondary.is_none() || self.tertiary.is_none()
487    }
488}
489
490/// Nontrivial piece stuff.
491impl Piece {
492    /// The distance from a side's send in rotchess units.
493    pub fn forward_distance(&self) -> f32 {
494        match self.side() {
495            Side::Black => self.y(),
496            Side::White => 8.0 - self.y(),
497        }
498    }
499
500    pub fn collidepoint_generic(x1: f32, y1: f32, x2: f32, y2: f32) -> bool {
501        (x1 - x2).powi(2) + (y1 - y2).powi(2) < PIECE_RADIUS.powi(2)
502    }
503
504    pub fn collidepoint(&self, x: f32, y: f32) -> bool {
505        Piece::collidepoint_generic(x, y, self.core.center.0, self.core.center.1)
506    }
507
508    /// Whether a piece with center (x, y) collides with self.
509    pub fn collidepiece(&self, x: f32, y: f32) -> bool {
510        ((x - self.core.center.0).powi(2) + (y - self.core.center.1).powi(2))
511            < (PIECE_RADIUS * 2.).powi(2)
512    }
513
514    /// Whether a piece with center (x, y) is on the board.
515    ///
516    /// TODO: this probably should be in a board struct. we might want to move Pieces and Board into the same struct?
517    pub fn on_board(x: f32, y: f32) -> bool {
518        const BOARD_SIZE: f32 = 8.;
519        const MARGIN: f32 = PIECE_RADIUS;
520        !(x < 0. - MARGIN || x > BOARD_SIZE + MARGIN || y < 0. - MARGIN || y > BOARD_SIZE + MARGIN)
521    }
522
523    /// Whether a piece with given characteristics should promote.
524    ///
525    /// TODO: like on_board, this should probably be in a board struct.
526    pub fn should_promote(kind: PieceKind, side: Side, y: f32) -> bool {
527        if !kind.can_promote() {
528            return false;
529        }
530
531        match side {
532            Side::Black => y + PIECE_RADIUS > 7.,
533            Side::White => y - PIECE_RADIUS < 1.,
534        }
535    }
536
537    pub fn capture_points_unchecked(&self) -> impl Iterator<Item = &(f32, f32)> {
538        let tertiary = self
539            .tertiary
540            .as_ref()
541            .expect("Invariant was that delayed is Some.");
542
543        tertiary.capture_points.iter()
544    }
545
546    pub fn move_points_unchecked(&self) -> impl Iterator<Item = &(f32, f32)> {
547        let tertiary = self
548            .tertiary
549            .as_ref()
550            .expect("Invariant was that delayed is Some.");
551
552        tertiary.move_points.iter()
553    }
554
555    pub fn init_auxiliary_data(&mut self) {
556        self.secondary = Some(SecondaryPieceData::from(&self.core));
557        self.tertiary = Some(TertiaryPieceData::from((
558            &self.core,
559            self.secondary.as_ref().expect("We just created this."),
560        )));
561    }
562
563    pub fn update_capmove_points_unchecked(&mut self) {
564        self.update_capture_points_unchecked();
565        self.update_move_points_unchecked();
566    }
567
568    /// Update self's capture points with the drawable DistancesAngles.
569    pub fn update_capture_points_unchecked(&mut self) {
570        let capture_points: &mut Vec<(f32, f32)> =
571            &mut self.tertiary.as_mut().expect("Invariant.").capture_points;
572        let capture_das: &Vec<DistancesAngle> =
573            &self.secondary.as_ref().expect("Invariant.").capture_das;
574
575        capture_points.clear();
576        Piece::extend_with_drawable_points(&self.core, capture_points, capture_das.iter());
577    }
578
579    /// Update self's move points with the drawable DistancesAngles.
580    pub fn update_move_points_unchecked(&mut self) {
581        let move_points: &mut Vec<(f32, f32)> =
582            &mut self.tertiary.as_mut().expect("Invariant.").move_points;
583        let move_das: &Vec<DistancesAngle> = &self.secondary.as_ref().expect("Invariant.").move_das;
584
585        move_points.clear();
586        Piece::extend_with_drawable_points(&self.core, move_points, move_das.iter());
587    }
588
589    /// Extend points with the drawable points from each DA in das.
590    ///
591    /// Necessary metadata like offset angle and piece center is retrieved from self.
592    ///
593    /// This function blocks, and will become an infinite loop if some idiot (me) manages to
594    /// define a chess piece that moves infinitely without leaving the board. An error
595    /// arising from this should become pretty obvious. "Oh, hey, I just added Mr. moves
596    /// around in circles, and for some reason my game freezes whenever I try to use him."
597    fn extend_with_drawable_points<'a>(
598        core: &CorePieceData,
599        points: &mut Vec<(f32, f32)>,
600        das: impl Iterator<Item = &'a DistancesAngle>,
601    ) {
602        for da in das {
603            for (x, y) in da.get_offsets(core.angle + PI / 2.) {
604                let point = (x + core.center.0, y + core.center.1);
605                if !Piece::on_board(point.0, point.1) {
606                    break;
607                }
608                points.push(point);
609            }
610        }
611    }
612}
613
614/// The scalar composition of vectors point in the direction of dir where the vectors have starting point start
615fn scalar_comp(
616    start_x: f32,
617    start_y: f32,
618    point_x: f32,
619    point_y: f32,
620    dir_x: f32,
621    dir_y: f32,
622) -> f32 {
623    // scalar comp of v in the direction of u: we find u dot v / magn(u)
624    let u = (dir_x - start_x, dir_y - start_y);
625    let v = (point_x - start_x, point_y - start_y);
626
627    (u.0 * v.0 + u.1 * v.1) / f32::sqrt(u.0.powi(2) + u.1.powi(2))
628}
629
630/// simple distance formula + hitcirclerad
631///
632/// might be made into Piece const? this is legacy code.
633fn max_hit_distance(start_x: f32, start_y: f32, end_x: f32, end_y: f32) -> f32 {
634    f32::sqrt((start_x - end_x).powi(2) + (start_y - end_y).powi(2)) + PIECE_RADIUS
635}
636
637/// distance from a point to a line, where the line is given by two points
638fn point_to_line_dist(
639    start_x: f32,
640    start_y: f32,
641    end_x: f32,
642    end_y: f32,
643    point_x: f32,
644    point_y: f32,
645) -> f32 {
646    f32::abs((end_x - start_x) * (point_y - start_y) - (point_x - start_x) * (end_y - start_y))
647        / f32::sqrt((end_x - start_x).powi(2) + (end_y - start_y).powi(2))
648}
649
650/// A vector of pieces.
651///
652/// # Invariants
653///
654/// We maintain that
655/// - pieces will not overlap
656#[derive(Clone)]
657pub struct Pieces {
658    inner: Vec<Piece>,
659}
660
661impl Pieces {
662    /// Create a board with standard piece positions.
663    pub fn standard_board() -> Self {
664        let mut inner = vec![];
665
666        const ORDER: [PieceKind; 8] = [
667            PieceKind::Rook,
668            PieceKind::Knight,
669            PieceKind::Bishop,
670            PieceKind::Queen,
671            PieceKind::King,
672            PieceKind::Bishop,
673            PieceKind::Knight,
674            PieceKind::Rook,
675        ];
676
677        for i in 0..8 {
678            inner.push(Piece::from_tile((i, 1), -PI, Side::Black, PieceKind::Pawn));
679            inner.push(Piece::from_tile((i, 6), 0., Side::White, PieceKind::Pawn));
680        }
681
682        for (i, kind) in ORDER.iter().enumerate() {
683            inner.push(Piece::from_tile((i as u8, 0), -PI, Side::Black, *kind));
684            inner.push(Piece::from_tile((i as u8, 7), 0., Side::White, *kind));
685        }
686
687        Self { inner }
688    }
689
690    /// Create a board with a randomized back row.
691    ///
692    /// This is known as the 960, or Fischer, variant setup.
693    pub fn chess960_board(idx_ordering: impl FnOnce() -> [usize; 8]) -> Self {
694        let mut inner = vec![];
695
696        let pieces: [PieceKind; 8] = [
697            PieceKind::Rook,
698            PieceKind::Knight,
699            PieceKind::Bishop,
700            PieceKind::Queen,
701            PieceKind::King,
702            PieceKind::Bishop,
703            PieceKind::Knight,
704            PieceKind::Rook,
705        ];
706
707        let ordering = idx_ordering();
708        debug_assert!({
709            let mut ordering2 = ordering.to_vec();
710            ordering2.sort();
711            ordering2 == (0..8).collect::<Vec<_>>()
712        });
713        let order = ordering.map(|i| pieces[i]);
714
715        for i in 0..8 {
716            inner.push(Piece::from_tile((i, 1), -PI, Side::Black, PieceKind::Pawn));
717            inner.push(Piece::from_tile((i, 6), 0., Side::White, PieceKind::Pawn));
718        }
719
720        for (i, kind) in order.iter().enumerate() {
721            inner.push(Piece::from_tile((i as u8, 0), -PI, Side::Black, *kind));
722            inner.push(Piece::from_tile((i as u8, 7), 0., Side::White, *kind));
723        }
724
725        Self { inner }
726    }
727
728    /// Get a piece's index within inner, if it exists.
729    pub fn get_piece(&self, x: f32, y: f32) -> Option<usize> {
730        self.inner.iter().position(|piece| piece.collidepoint(x, y))
731    }
732
733    pub fn get(&self, index: usize) -> Option<&Piece> {
734        self.inner.get(index)
735    }
736
737    pub fn get_mut(&mut self, index: usize) -> Option<&mut Piece> {
738        self.inner.get_mut(index)
739    }
740
741    pub fn inner_ref(&self) -> &[Piece] {
742        &self.inner
743    }
744
745    pub fn inner_mut(&mut self) -> &mut Vec<Piece> {
746        &mut self.inner
747    }
748
749    /// Move the piece at idx to x, y.
750    ///
751    /// # Warnings
752    ///
753    /// This may shuffle piece indices! Returns the piece's new index.
754    pub fn travel(&mut self, idx: usize, x: f32, y: f32) -> usize {
755        let orig_piece_center = self.inner[idx].center();
756        self.inner.retain(|piece| !piece.collidepiece(x, y));
757        let new_idx = self
758            .inner
759            .iter()
760            .position(|p| p.center() == orig_piece_center)
761            .expect("Should still exist.");
762
763        let piece = &mut self.inner[new_idx];
764        piece.set_x(x);
765        piece.set_y(y);
766        if Piece::should_promote(piece.kind(), piece.side(), y) {
767            piece.set_kind(PieceKind::Queen);
768            piece.init_auxiliary_data();
769        }
770
771        new_idx
772    }
773
774    pub fn travelable(&self, piece: &Piece, x: f32, y: f32, kind: TravelKind) -> bool {
775        // println!("checking travelable points");
776        let mut pieces_overlapping_endpoint = HashSet::new();
777
778        // disallow capturing own side. also find which pieces overlap the endpoint
779        for other_piece in &self.inner {
780            if other_piece == piece {
781                debug_assert!(!piece.needs_init());
782                continue;
783            }
784
785            if other_piece.collidepiece(x, y) {
786                pieces_overlapping_endpoint.insert(other_piece);
787
788                if other_piece.side() == piece.side() {
789                    return false;
790                }
791            }
792        }
793
794        if piece.core.kind.can_jump() {
795            match kind {
796                TravelKind::Capture => {
797                    if !pieces_overlapping_endpoint.is_empty() {
798                        return true;
799                    }
800                }
801                TravelKind::Move => return pieces_overlapping_endpoint.is_empty(),
802            };
803        }
804
805        let mut in_the_way = 0;
806        for other_piece in &self.inner {
807            if other_piece == piece {
808                continue;
809            }
810
811            let comp = scalar_comp(piece.x(), piece.y(), other_piece.x(), other_piece.y(), x, y);
812            if 0. < comp && comp < max_hit_distance(piece.x(), piece.y(), x, y) {
813                // piece is within correct distance to block. now check:
814                if point_to_line_dist(piece.x(), piece.y(), x, y, other_piece.x(), other_piece.y())
815                    < 2. * PIECE_RADIUS
816                {
817                    // piece is within correct point to line distance to block. we may be blocked unless we can capture this piece.
818                    // println!("a {:?} can block", other_piece.kind());
819                    if !pieces_overlapping_endpoint.contains(&other_piece) {
820                        in_the_way += 1;
821                    }
822                }
823            }
824        }
825
826        // println!(
827        //     "inway: {in_the_way}, overlaps: {}",
828        //     pieces_overlapping_endpoint.len()
829        // );
830        if in_the_way > 0 {
831            return false;
832        }
833
834        debug_assert!(
835            pieces_overlapping_endpoint
836                .iter()
837                .all(|other_piece| other_piece.side() != piece.side())
838        );
839        match kind {
840            TravelKind::Capture => !pieces_overlapping_endpoint.is_empty(),
841            TravelKind::Move => pieces_overlapping_endpoint.is_empty(),
842        }
843    }
844}