Add 2024 Day 13
This commit is contained in:
parent
fe30d53314
commit
8d69ea0cd5
5 changed files with 1527 additions and 0 deletions
13
2024/day13_claw_contraption/Cargo.toml
Normal file
13
2024/day13_claw_contraption/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "day13_claw_contraption"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
# criterion = "0.5.1"
|
||||
|
||||
[[bench]]
|
||||
name = "test_benchmark"
|
||||
harness = false
|
90
2024/day13_claw_contraption/challenge.md
Normal file
90
2024/day13_claw_contraption/challenge.md
Normal file
|
@ -0,0 +1,90 @@
|
|||
Next up: the [lobby](/2020/day/24) of a resort on a tropical island. The Historians take a moment to admire the hexagonal floor tiles before spreading out.
|
||||
|
||||
Fortunately, it looks like the resort has a new [arcade](https://en.wikipedia.org/wiki/Amusement_arcade)! Maybe you can win some prizes from the [claw machines](https://en.wikipedia.org/wiki/Claw_machine)?
|
||||
|
||||
The claw machines here are a little unusual. Instead of a joystick or directional buttons to control the claw, these machines have two buttons labeled `A` and `B`. Worse, you can't just put in a token and play; it costs *3 tokens* to push the `A` button and *1 token* to push the `B` button.
|
||||
|
||||
With a little experimentation, you figure out that each machine's buttons are configured to move the claw a specific amount to the *right* (along the `X` axis) and a specific amount *forward* (along the `Y` axis) each time that button is pressed.
|
||||
|
||||
Each machine contains one *prize*; to win the prize, the claw must be positioned *exactly* above the prize on both the `X` and `Y` axes.
|
||||
|
||||
You wonder: what is the smallest number of tokens you would have to spend to win as many prizes as possible? You assemble a list of every machine's button behavior and prize location (your puzzle input). For example:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
```
|
||||
|
||||
This list describes the button configuration and prize location of four different claw machines.
|
||||
|
||||
For now, consider just the first claw machine in the list:
|
||||
|
||||
* Pushing the machine's `A` button would move the claw `94` units along the `X` axis and `34` units along the `Y` axis.
|
||||
* Pushing the `B` button would move the claw `22` units along the `X` axis and `67` units along the `Y` axis.
|
||||
* The prize is located at `X=8400`, `Y=5400`; this means that from the claw's initial position, it would need to move exactly `8400` units along the `X` axis and exactly `5400` units along the `Y` axis to be perfectly aligned with the prize in this machine.
|
||||
|
||||
The cheapest way to win the prize is by pushing the `A` button `80` times and the `B` button `40` times. This would line up the claw along the `X` axis (because `80*94 + 40*22 = 8400`) and along the `Y` axis (because `80*34 + 40*67 = 5400`). Doing this would cost `80*3` tokens for the `A` presses and `40*1` for the `B` presses, a total of `*280*` tokens.
|
||||
|
||||
For the second and fourth claw machines, there is no combination of A and B presses that will ever win a prize.
|
||||
|
||||
For the third claw machine, the cheapest way to win the prize is by pushing the `A` button `38` times and the `B` button `86` times. Doing this would cost a total of `*200*` tokens.
|
||||
|
||||
So, the most prizes you could possibly win is two; the minimum tokens you would have to spend to win all (two) prizes is `*480*`.
|
||||
|
||||
You estimate that each button would need to be pressed *no more than `100` times* to win a prize. How else would someone be expected to play?
|
||||
|
||||
Figure out how to win as many prizes as possible. *What is the fewest tokens you would have to spend to win all possible prizes?*
|
||||
|
||||
Your puzzle answer was `29598`.
|
||||
|
||||
\--- Part Two ---
|
||||
----------
|
||||
|
||||
As you go to win the first prize, you discover that the claw is nowhere near where you expected it would be. Due to a unit conversion error in your measurements, the position of every prize is actually `10000000000000` higher on both the `X` and `Y` axis!
|
||||
|
||||
Add `10000000000000` to the `X` and `Y` position of every prize. After making this change, the example above would now look like this:
|
||||
|
||||
```
|
||||
Button A: X+94, Y+34
|
||||
Button B: X+22, Y+67
|
||||
Prize: X=10000000008400, Y=10000000005400
|
||||
|
||||
Button A: X+26, Y+66
|
||||
Button B: X+67, Y+21
|
||||
Prize: X=10000000012748, Y=10000000012176
|
||||
|
||||
Button A: X+17, Y+86
|
||||
Button B: X+84, Y+37
|
||||
Prize: X=10000000007870, Y=10000000006450
|
||||
|
||||
Button A: X+69, Y+23
|
||||
Button B: X+27, Y+71
|
||||
Prize: X=10000000018641, Y=10000000010279
|
||||
|
||||
```
|
||||
|
||||
Now, it is only possible to win a prize on the second and fourth claw machines. Unfortunately, it will take *many more than `100` presses* to do so.
|
||||
|
||||
Using the corrected prize coordinates, figure out how to win as many prizes as possible. *What is the fewest tokens you would have to spend to win all possible prizes?*
|
||||
|
||||
Your puzzle answer was `93217456941970`.
|
||||
|
||||
Both parts of this puzzle are complete! They provide two gold stars: \*\*
|
||||
|
||||
At this point, you should [return to your Advent calendar](/2024) and try another puzzle.
|
||||
|
||||
If you still want to see it, you can [get your puzzle input](13/input).
|
130
2024/day13_claw_contraption/src/lib.rs
Normal file
130
2024/day13_claw_contraption/src/lib.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use core::fmt::Display;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseError<'a> {
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
InputMalformed(&'a str),
|
||||
LineMalformed(&'a str),
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for ParseError<'_> {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::ParseIntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InputMalformed(v) => write!(f, "Machine is malformed: {v}"),
|
||||
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
|
||||
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Coordinates = (usize, usize);
|
||||
|
||||
struct Machine {
|
||||
btn_a: Coordinates,
|
||||
btn_b: Coordinates,
|
||||
prize: Coordinates,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Machine {
|
||||
type Error = ParseError<'a>;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
let lines: Vec<_> = value.lines().collect();
|
||||
if lines.len() != 3 {
|
||||
return Err(Self::Error::InputMalformed(value));
|
||||
}
|
||||
let a: Vec<_> = lines[0].split(&['+', ',']).collect();
|
||||
if a.len() != 4 {
|
||||
return Err(Self::Error::LineMalformed(lines[0]));
|
||||
}
|
||||
let btn_a = (a[1].parse()?, a[3].parse()?);
|
||||
|
||||
let b: Vec<_> = lines[1].split(&['+', ',']).collect();
|
||||
if b.len() != 4 {
|
||||
return Err(Self::Error::LineMalformed(lines[1]));
|
||||
}
|
||||
let btn_b = (b[1].parse()?, b[3].parse()?);
|
||||
|
||||
let p: Vec<_> = lines[2].split(&['=', ',']).collect();
|
||||
if p.len() != 4 {
|
||||
return Err(Self::Error::LineMalformed(lines[2]));
|
||||
}
|
||||
let prize = (p[1].parse()?, p[3].parse()?);
|
||||
|
||||
Ok(Self {
|
||||
btn_a,
|
||||
btn_b,
|
||||
prize,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
fn prize_cost(&self) -> Option<usize> {
|
||||
// Determine the winning combination using matrix inversion. This method delivers the only
|
||||
// combination that will work (provided the button vectors are lenearly independent), but
|
||||
// the result may be non-integer, in which case there is no valid solution.
|
||||
let inverse_determinant = 1.0 / ((self.btn_a.0*self.btn_b.1) as f64 - (self.btn_a.1*self.btn_b.0) as f64);
|
||||
let a_presses = (inverse_determinant * (
|
||||
(self.btn_b.1 * self.prize.0) as f64 - (self.btn_b.0 * self.prize.1) as f64)
|
||||
).round() as isize;
|
||||
let b_presses = (inverse_determinant * (
|
||||
(self.btn_a.0 * self.prize.1) as f64 - (self.btn_a.1 * self.prize.0) as f64)
|
||||
).round() as isize;
|
||||
|
||||
// Try all neighbouring combinations to account for rounding errors
|
||||
for delta_a in (-1..=1).filter(|da| a_presses + da >= 0) {
|
||||
let a = (a_presses + delta_a) as usize;
|
||||
for delta_b in (-1..=1).filter(|db| b_presses + db >= 0) {
|
||||
let b = (b_presses + delta_b) as usize;
|
||||
if a * self.btn_a.0 + b * self.btn_b.0 == self.prize.0 &&
|
||||
a * self.btn_a.1 + b * self.btn_b.1 == self.prize.1 {
|
||||
return Some(3 * a + b)
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
|
||||
let mut machines: Vec<_> = input.split("\n\n").map(Machine::try_from).collect::<Result<Vec<_>, _>>()?;
|
||||
let first = machines.iter().filter_map(|m| m.prize_cost()).sum();
|
||||
machines.iter_mut().for_each(|m| {
|
||||
m.prize.0 += 10000000000000;
|
||||
m.prize.1 += 10000000000000;
|
||||
});
|
||||
let second = machines.iter().filter_map(|m| m.prize_cost()).sum();
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let sample_input = read_file("tests/sample_input");
|
||||
// The result for part 2 is not given in the challenge, but is what my solution finds.
|
||||
// Thus, it may be incorrect (although I doubt it since the other 3 results are correct).
|
||||
assert_eq!(run(&sample_input), Ok((480, 875318608908)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run(&challenge_input), Ok((29598, 93217456941970)));
|
||||
}
|
||||
}
|
1279
2024/day13_claw_contraption/tests/challenge_input
Normal file
1279
2024/day13_claw_contraption/tests/challenge_input
Normal file
File diff suppressed because it is too large
Load diff
15
2024/day13_claw_contraption/tests/sample_input
Normal file
15
2024/day13_claw_contraption/tests/sample_input
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue