From 75d1327446e4d0cec33e44da37ac41d3615916de Mon Sep 17 00:00:00 2001 From: Lucy Date: Mon, 16 Dec 2024 08:47:10 +0100 Subject: [PATCH] feat: day 13 --- Cargo.lock | 21 +++++ Cargo.toml | 3 + inputs/day13/example.txt | 15 ++++ src/day13.rs | 186 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 + 5 files changed, 229 insertions(+) create mode 100644 inputs/day13/example.txt create mode 100644 src/day13.rs diff --git a/Cargo.lock b/Cargo.lock index c3e91c7..3cc10c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,7 @@ dependencies = [ "color-eyre", "rayon", "regex", + "thiserror", "tracing", "tracing-subscriber", "tracing-test", @@ -353,6 +354,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" diff --git a/Cargo.toml b/Cargo.toml index 6d5b77c..9d6a5c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ full = [ "day10", "day11", "day12", + "day13", ] default = ["full"] day1 = [] @@ -31,6 +32,7 @@ day9 = [] day10 = [] day11 = [] day12 = [] +day13 = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -39,6 +41,7 @@ day12 = [] color-eyre = "0.6.3" rayon = "1.10.0" regex = "1.11.1" +thiserror = "2.0.6" tracing = "0.1.41" tracing-subscriber = "0.3.19" tracing-test = "0.2.5" diff --git a/inputs/day13/example.txt b/inputs/day13/example.txt new file mode 100644 index 0000000..444a287 --- /dev/null +++ b/inputs/day13/example.txt @@ -0,0 +1,15 @@ +Button A: X+94, Y+34 +Button B: X+22, Y+67 +Prize: X=8400, Y=5400 + +Button A: X+26, Y+66 +Button B: X+67, Y+21 +Prize: X=12748, Y=12176 + +Button A: X+17, Y+86 +Button B: X+84, Y+37 +Prize: X=7870, Y=6450 + +Button A: X+69, Y+23 +Button B: X+27, Y+71 +Prize: X=18641, Y=10279 \ No newline at end of file diff --git a/src/day13.rs b/src/day13.rs new file mode 100644 index 0000000..703faee --- /dev/null +++ b/src/day13.rs @@ -0,0 +1,186 @@ +#![allow( + clippy::cast_sign_loss, + clippy::cast_possible_wrap, + clippy::too_many_lines, + clippy::cast_possible_truncation, + clippy::cast_precision_loss, + clippy::float_cmp +)] +use std::str::FromStr; + +use color_eyre::eyre::{self, OptionExt}; + +struct Elem { + x: usize, + y: usize, +} +struct Machine { + button_a: Elem, + button_b: Elem, + prize: Elem, +} + +fn parse_button(s: &str) -> Result { + let positions = s.split_once(", ").ok_or_eyre("Button A line malformed")?; + Ok(Elem { + x: positions + .0 + .split_once('+') + .ok_or_eyre("seems to be malformed")? + .1 + .parse::()?, + y: positions + .1 + .split_once('+') + .ok_or_eyre("seems to be malformed")? + .1 + .parse::()?, + }) +} + +impl FromStr for Machine { + type Err = eyre::Error; + fn from_str(s: &str) -> Result { + let lines = s.lines().collect::>(); + let button_a = parse_button(lines.first().ok_or_eyre("Could not find button a line")?)?; + let button_b = parse_button(lines.get(1).ok_or_eyre("could not find button b line")?)?; + let prize_line = lines.get(2).ok_or_eyre("Could not find prize line")?; + let prizes = prize_line + .split_once(", ") + .ok_or_eyre("Could not parse prize line")?; + let prize = Elem { + x: prizes + .0 + .split_once('=') + .ok_or_eyre("Could not parse prize")? + .1 + .parse::()?, + y: prizes + .1 + .split_once('=') + .ok_or_eyre("Could not parse prize")? + .1 + .parse::()?, + }; + Ok(Machine { + button_a, + button_b, + prize, + }) + } +} + +impl Machine { + pub fn play(&self) -> Option { + let machine = self; + let determinant = machine.button_a.x as f64 * machine.button_b.y as f64 + - machine.button_a.y as f64 * machine.button_b.x as f64; + let a = ((-(machine.button_b.x as f64)) * (machine.prize.y as f64) + + machine.button_b.y as f64 * machine.prize.x as f64) + / determinant; + let b = (machine.button_a.x as f64 * machine.prize.y as f64 + - machine.button_a.y as f64 * machine.prize.x as f64) + / determinant; + if a.floor() == a && b.floor() == b && a >= 0.0 && b >= 0.0 { + if a > 100.0 || b > 100.0 { + None + } else { + Some(a.round() as usize * 3 + b as usize) + } + } else { + None + } + } + pub fn play_without_limits(&self) -> Option { + let machine = self; + let determinant = machine.button_a.x as f64 * machine.button_b.y as f64 + - machine.button_a.y as f64 * machine.button_b.x as f64; + let a = ((-(machine.button_b.x as f64)) * (machine.prize.y as f64) + + machine.button_b.y as f64 * machine.prize.x as f64) + / determinant; + let b = (machine.button_a.x as f64 * machine.prize.y as f64 + - machine.button_a.y as f64 * machine.prize.x as f64) + / determinant; + if a.floor() == a && b.floor() == b && a >= 0.0 && b >= 0.0 { + Some(a.round() as usize * 3 + b as usize) + } else { + None + } + } +} + +pub fn pt1(input: &str) -> usize { + input + .split("\n\n") + .flat_map(Machine::from_str) + .filter_map(|m| m.play()) + .sum() +} +pub fn pt2(input: &str) -> usize { + input + .split("\n\n") + .flat_map(Machine::from_str) + .map(|mut m| { + m.prize.x += 10_000_000_000_000; + m.prize.y += 10_000_000_000_000; + m + }) + .filter_map(|m| m.play_without_limits()) + .sum() +} + +#[cfg(test)] +mod tests { + use tracing_test::traced_test; + + use super::{pt1, pt2, Machine}; + use std::fs::read_to_string; + + #[traced_test] + #[test] + pub fn test_pt1() { + let input = + read_to_string("./inputs/day13/example.txt").expect("Missing example.txt for day13"); + assert_eq!(pt1(&input), 480) + } + #[traced_test] + #[test] + pub fn test_machine_execution1() { + let m = Machine { + button_a: crate::day13::Elem { x: 94, y: 34 }, + button_b: crate::day13::Elem { x: 22, y: 67 }, + prize: crate::day13::Elem { x: 8400, y: 5400 }, + }; + assert_eq!(m.play(), Some(280)) + } + #[traced_test] + #[test] + pub fn test_machine_execution2() { + let m = Machine { + button_a: crate::day13::Elem { x: 26, y: 66 }, + button_b: crate::day13::Elem { x: 67, y: 21 }, + prize: crate::day13::Elem { x: 12748, y: 12176 }, + }; + assert_eq!(m.play(), None) + } + #[traced_test] + #[test] + pub fn test_machine_execution3() { + let m = Machine { + button_a: crate::day13::Elem { x: 17, y: 86 }, + button_b: crate::day13::Elem { x: 84, y: 37 }, + prize: crate::day13::Elem { x: 7870, y: 6450 }, + }; + assert_eq!(m.play(), Some(200)) + } + #[traced_test] + #[test] + pub fn test_machine_execution4() { + let m = Machine { + button_a: crate::day13::Elem { x: 69, y: 23 }, + button_b: crate::day13::Elem { x: 27, y: 71 }, + prize: crate::day13::Elem { x: 18641, y: 10279 }, + }; + assert_eq!(m.play(), None) + } +} diff --git a/src/main.rs b/src/main.rs index 04036e9..a2f12ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,8 @@ mod day10; mod day11; #[cfg(feature = "day12")] mod day12; +#[cfg(feature = "day13")] +mod day13; fn main() -> eyre::Result<()> { color_eyre::install()?; @@ -57,6 +59,8 @@ fn main() -> eyre::Result<()> { solve_day(11, day11::pt1, day11::pt2); #[cfg(feature = "day12")] solve_day(12, day12::pt1, day12::pt2); + #[cfg(feature = "day13")] + solve_day(13, day13::pt1, day13::pt2); Ok(()) }