bracket_algorithm_traits/
algorithm2d.rs

1//! # `Algorithm2D`
2//!
3//! `Algorithm2D` provides a translation layer between your map definition and `bracket-lib`. It
4//! seems to provide sane defaults, to minimize the work *if* you want to use my defaults for things
5//! like 2D array striding. If you don't, you can override more.
6//!
7//! At the minimum, override `dimensions` to provide a `Point` containing the upper boundaries of your map.
8//! The library can derive other traits from this.
9//!
10//! You can override `in_bounds` to change how you test if a coordinate is valid.
11//!
12//! You can override `point2d_to_index` to define how an X/Y point is mapped to a unique index.
13//!
14//! You can override `index_to_point2d` to override how a unique index is mapped to an X/Y coordinate.
15
16use crate::prelude::BaseMap;
17use bracket_geometry::prelude::Point;
18use std::convert::TryInto;
19
20/// Implement these for handling conversion to/from 2D coordinates (they are separate, because you might
21/// want Dwarf Fortress style 3D!)
22pub trait Algorithm2D: BaseMap {
23    /// Convert a Point (x/y) to an array index. Defaults to an index based on an array
24    /// strided X first.
25    fn point2d_to_index(&self, pt: Point) -> usize {
26        let bounds = self.dimensions();
27        ((pt.y * bounds.x) + pt.x)
28            .try_into()
29            .expect("Not a valid usize. Did something go negative?")
30    }
31
32    /// Convert an array index to a point. Defaults to an index based on an array
33    /// strided X first.
34    fn index_to_point2d(&self, idx: usize) -> Point {
35        let bounds = self.dimensions();
36        let w: usize = bounds
37            .x
38            .try_into()
39            .expect("Not a valid usize. Did something go negative?");
40        Point::new(idx % w, idx / w)
41    }
42
43    /// Retrieve the map's dimensions. Made optional to reduce API breakage.
44    fn dimensions(&self) -> Point {
45        panic!("You must either define the dimensions function (trait Algorithm2D) on your map, or define the various point2d_to_index and index_to_point2d functions.");
46    }
47
48    // Optional - check that an x/y coordinate is within the map bounds. If not provided,
49    // it falls back to using the map's dimensions from that trait implementation. Most of
50    // the time, that's what you want.
51    fn in_bounds(&self, pos: Point) -> bool {
52        let bounds = self.dimensions();
53        pos.x >= 0 && pos.x < bounds.x && pos.y >= 0 && pos.y < bounds.y
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use crate::prelude::{Algorithm2D, BaseMap};
60    use bracket_geometry::prelude::Point;
61
62    #[test]
63    // Tests that we make an RGB triplet at defaults and it is black.
64    #[should_panic]
65    fn test_unimplemented_dimensions() {
66        struct TestMap {}
67        impl BaseMap for TestMap {}
68        impl Algorithm2D for TestMap {}
69
70        let map = TestMap {};
71        assert!(map.in_bounds(Point::new(1, 1)));
72    }
73
74    #[test]
75    fn test_in_bounds() {
76        struct TestMap {}
77        impl BaseMap for TestMap {}
78        impl Algorithm2D for TestMap {
79            fn dimensions(&self) -> Point {
80                Point::new(2, 2)
81            }
82        }
83
84        let map = TestMap {};
85        assert!(map.in_bounds(Point::new(0, 0)));
86        assert!(map.in_bounds(Point::new(1, 1)));
87        assert!(!map.in_bounds(Point::new(3, 3)));
88    }
89
90    #[test]
91    fn test_point2d_to_index() {
92        struct TestMap {}
93        impl BaseMap for TestMap {}
94        impl Algorithm2D for TestMap {
95            fn dimensions(&self) -> Point {
96                Point::new(10, 10)
97            }
98        }
99
100        let map = TestMap {};
101        assert!(map.point2d_to_index(Point::new(0, 0)) == 0);
102        assert!(map.point2d_to_index(Point::new(1, 0)) == 1);
103        assert!(map.point2d_to_index(Point::new(0, 1)) == 10);
104        assert!(map.point2d_to_index(Point::new(9, 9)) == 99);
105    }
106
107    #[test]
108    fn test_index_to_point2d() {
109        struct TestMap {}
110        impl BaseMap for TestMap {}
111        impl Algorithm2D for TestMap {
112            fn dimensions(&self) -> Point {
113                Point::new(10, 10)
114            }
115        }
116
117        let map = TestMap {};
118        let mut x = 0;
119        let mut y = 0;
120        for i in 0..100 {
121            assert!(map.index_to_point2d(i) == Point::new(x, y));
122
123            x += 1;
124            if x > 9 {
125                x = 0;
126                y += 1;
127            }
128        }
129    }
130}