1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! Methods related to approximating positions between other positions.
use super::Position;

impl Position {
    /// Calculates an approximate midpoint between this point and the target.
    ///
    /// In case of a tie, rounds towards this point.
    ///
    /// If `distance_towards_target` is bigger than the distance to the target,
    /// the target is returned.
    pub fn towards(self, target: Position, distance_towards_target: i32) -> Position {
        let (offset_x, offset_y) = target - self;
        let total_distance = offset_x.abs().max(offset_y.abs());
        if distance_towards_target > total_distance {
            return target;
        }

        let new_offset_x = (offset_x * distance_towards_target) / total_distance;
        let new_offset_y = (offset_y * distance_towards_target) / total_distance;

        self + (new_offset_x, new_offset_y)
    }

    /// Calculates an approximate midpoint between this point and the target.
    ///
    /// In case of a tie, rounds towards the target.
    ///
    /// If `distance_from_target` is bigger than the distance to the target,
    /// this position is returned.
    pub fn between(self, target: Position, distance_from_target: i32) -> Position {
        target.towards(self, distance_from_target)
    }

    /// Calculates an approximate midpoint between this point and the target.
    ///
    /// In case of a tie, rounds towards the target.
    pub fn midpoint_between(self, target: Position) -> Position {
        let (offset_x, offset_y) = self - target;

        let new_offset_x = offset_x / 2;
        let new_offset_y = offset_y / 2;

        target + (new_offset_x, new_offset_y)
    }
}

#[cfg(test)]
mod test {
    use std::convert::TryInto;

    use super::Position;
    use crate::RoomName;

    fn test_rooms() -> impl Iterator<Item = RoomName> {
        ["E0N0", "E20N20", "W20N0", "E20S20", "W20S20"]
            .iter()
            .map(|s| s.parse().unwrap())
    }

    fn pos(room: RoomName, x: u8, y: u8) -> Position {
        Position::new(x.try_into().unwrap(), y.try_into().unwrap(), room)
    }

    #[test]
    fn towards_accurate() {
        for room in test_rooms() {
            let start = pos(room, 10, 10);
            assert_eq!(start.towards(pos(room, 10, 15), 1), pos(room, 10, 11));
            assert_eq!(start.towards(pos(room, 10, 15), 4), pos(room, 10, 14));
            assert_eq!(start.towards(pos(room, 10, 15), 10), pos(room, 10, 15));
            assert_eq!(start.towards(pos(room, 15, 15), 1), pos(room, 11, 11));
            assert_eq!(start.towards(pos(room, 15, 15), 3), pos(room, 13, 13));
            assert_eq!(start.towards(pos(room, 15, 20), 2), pos(room, 11, 12));
            assert_eq!(start.towards(pos(room, 0, 5), 2), pos(room, 8, 9));
        }
    }
    #[test]
    fn towards_approximate() {
        for room in test_rooms() {
            let start = pos(room, 10, 10);
            assert_eq!(start.towards(pos(room, 15, 20), 1), pos(room, 10, 11));
            assert_eq!(start.towards(pos(room, 15, 20), 9), pos(room, 14, 19));
            assert_eq!(start.towards(pos(room, 0, 5), 1), pos(room, 9, 10));
        }
    }
    #[test]
    fn midpoint_accurate() {
        for room in test_rooms() {
            let start = pos(room, 10, 10);
            assert_eq!(start.midpoint_between(pos(room, 10, 16)), pos(room, 10, 13));
            assert_eq!(start.midpoint_between(pos(room, 20, 10)), pos(room, 15, 10));
            assert_eq!(start.midpoint_between(pos(room, 12, 12)), pos(room, 11, 11));
            assert_eq!(start.midpoint_between(pos(room, 4, 4)), pos(room, 7, 7));
        }
    }
    #[test]
    fn midpoint_approximate() {
        for room in test_rooms() {
            let start = pos(room, 10, 10);
            assert_eq!(start.midpoint_between(pos(room, 10, 15)), pos(room, 10, 13));
            assert_eq!(start.midpoint_between(pos(room, 19, 10)), pos(room, 15, 10));
            assert_eq!(start.midpoint_between(pos(room, 11, 11)), pos(room, 11, 11));
            assert_eq!(start.midpoint_between(pos(room, 15, 15)), pos(room, 13, 13));
            assert_eq!(start.midpoint_between(pos(room, 15, 25)), pos(room, 13, 18));
            assert_eq!(start.midpoint_between(pos(room, 9, 10)), pos(room, 9, 10));
            assert_eq!(start.midpoint_between(pos(room, 7, 10)), pos(room, 8, 10));
            assert_eq!(start.midpoint_between(pos(room, 1, 3)), pos(room, 5, 6));
        }
    }
}