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
use crate::common::character_recognition::{recognize, CHAR_HEIGHT, CHAR_WIDTH};
use crate::input::Input;

pub fn solve(input: &mut Input) -> Result<String, String> {
    const NUM_LETTERS: usize = 8;
    let mut dots = Vec::new();

    let parse_number = |num_str: &str| {
        num_str
            .parse::<u16>()
            .map_err(|_| "Invalid number - not an u16")
    };

    for line in input.text.lines() {
        if let Some((x, y)) = line.split_once(',') {
            let x = parse_number(x)?;
            let y = parse_number(y)?;
            dots.push((x, y));
        } else if let Some((prefix, coord)) = line.split_once('=') {
            let coord = parse_number(coord)?;
            let updater = |n: &mut u16| {
                if *n > coord {
                    if *n > 2 * coord {
                        return Err("Folding would create dot with negative coordinate".to_string());
                    }
                    *n = 2 * coord - *n;
                }
                Ok(())
            };

            if prefix.ends_with('x') {
                for p in dots.iter_mut() {
                    updater(&mut p.0)?;
                }
            } else if prefix.ends_with('y') {
                for p in dots.iter_mut() {
                    updater(&mut p.1)?;
                }
            } else {
                return Err("Invalid line not ending width x=.. or y=..".to_string());
            }

            if input.is_part_one() {
                dots.sort_unstable();
                dots.dedup();
                return Ok(dots.len().to_string());
            }
        }
    }

    let mut screen = [false; NUM_LETTERS * CHAR_HEIGHT * CHAR_WIDTH];
    for (x, y) in dots {
        screen[usize::from(y) * NUM_LETTERS * CHAR_WIDTH + usize::from(x)] = true;
    }
    recognize(&screen)
}

#[test]
pub fn tests() {
    use crate::input::{test_part_one, test_part_two};

    let example = "6,10
0,14
9,10
0,3
10,4
4,11
6,0
6,12
4,1
0,13
10,12
3,4
3,0
8,4
1,10
2,14
8,10
9,0

fold along y=7
fold along x=5";
    test_part_one!(example => "17".to_string());

    let real_input = include_str!("day13_input.txt");
    test_part_one!(real_input => "763".to_string());
    test_part_two!(real_input => "RHALRCRA".to_string());
}