Advent of Code 2022, Day 02 in Rust
December 02, 2022
Time for the second day of Advent of Code!
Solving the first part
Appreciative of your help yesterday, one Elf gives you an encrypted strategy guide (your puzzle input) that they say will be sure to help you win. "The first column is what your opponent is going to play: A for Rock, B for Paper, and C for Scissors. The second column–" Suddenly, the Elf is called away to help with someone's tent.
The second column, you reason, must be what you should play in response: X for Rock, Y for Paper, and Z for Scissors. Winning every time would be suspicious, so the responses must have been carefully chosen.
The winner of the whole tournament is the player with the highest score. Your total score is the sum of your scores for each round. The score for a single round is the score for the shape you selected (1 for Rock, 2 for Paper, and 3 for Scissors) plus the score for the outcome of the round (0 if you lost, 3 if the round was a draw, and 6 if you won).
Since you can't be sure if the Elf is trying to help you or trick you, you should calculate the score you would get if you were to follow the strategy guide.
For example, suppose you were given the following strategy guide:
1 2 3 A Y B X C ZThis strategy guide predicts and recommends the following:
- In the first round, your opponent will choose Rock (
A
), and you should choose Paper (Y
). This ends in a win for you with a score of 8 (2 because you chose Paper + 6 because you won).- In the second round, your opponent will choose Paper (
B
), and you should choose Rock (X
). This ends in a loss for you with a score of 1 (1 + 0).- The third round is a draw with both players choosing Scissors, giving you a score of 3 + 3 =
6
.In this example, if you were to follow the strategy guide, you would get a total score of
15
(8 + 1 + 6).What would your total score be if everything goes exactly according to your strategy guide?
Once again, I feel like this is another parsing question. My main idea is to go through each line, splitting the line into two parts: the opponent
's move, and our response
to it, and do things with these two parts.
First things first, I'll be editing the test case to return the value of 15
.
1
2
3
4
5
#[test]
fn test_part_one() {
let input = advent_of_code::read_file("examples", 2);
assert_eq!(part_one(&input), Some(15));
}
Looking at the question statement, we have Rock, Paper and Scissors having 1, 2 and 3, respectively, as their score. So, similarly, I will be parsing both opponent
and response
to their numeric values, and assigning it to a tuple (left, right)
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let mut score: u32 = 0;
for line in input.lines() {
let (opponent, response) = line.split_once(' ').unwrap();
let (left, right): (i32, i32);
match opponent {
"A" => left = 1,
"B" => left = 2,
"C" => left = 3,
_ => unreachable!(),
}
match response {
"X" => right = 1,
"Y" => right = 2,
"Z" => right = 3,
_ => unreachable!(),
}
}
Because we get a score according to our response
's numeric values, I'll add what we choose immediately to the score
counter. I'll be editing the match
arms for response
to the one below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
match response {
"X" => {
right = 1;
score += right as u32;
}
"Y" => {
right = 2;
score += right as u32;
}
"Z" => {
right = 3;
score += right as u32;
}
_ => unreachable!(),
}
The unreachable!()
macro is for matching against all other theoretically possible values of opponent
and response
, which are both of the &str
type. This shouldn't be possible with our inputs, but since Rust is rather pedantic about careful checks, this is what we will have to do.
Let's take care of the case where we draw first, since it's the simpler one. If we convert both opponent
and response
to their numeric values (assigned to left
and right
respectively, as above), it's clear that right - left == 0
. After that, our score
will increase by 3, as stated by the question. We will have our first check:
1
2
3
if right - left == 0 {
score += 3;
}
For the case where we win, it's a bit more complicated. Again, Rock, Paper and Scissors have the values of 1, 2 and 3 respectively. For example, if the opponent used Rock, and we were Paper, we would have right - left == 1
. It would be the same with the opponent using Paper and us using Scissors. But, what about the case of us using Rock, and the opponent used Scissors? right - left
would equate to -2
, and this is the edge case that we would have to check for as well. Our score
will increase by 6 in that case. So, at the end, our code to check for the winning case would be:
1
2
3
if (right - left == 1) || (right - left == -1) {
score += 6;
}
We will be returning the value of score
for the function:
1
Some(score)
And that's basically the first part finished! We don't have to do anything in the case we lost, since our score doesn't increase from it, according to the question statement.
Solving the second part
The Elf finishes helping with the tent and sneaks back over to you. "Anyway, the second column says how the round needs to end:
X
means you need to lose,Y
means you need to end the round in a draw, andZ
means you need to win. Good luck!"The total score is still calculated in the same way, but now you need to figure out what shape to choose so the round ends as indicated. The example above now goes like this:
- In the first round, your opponent will choose Rock (
A
), and you need the round to end in a draw (Y
), so you also choose Rock. This gives you a score of 1 + 3 = 4.- In the second round, your opponent will choose Paper (
B
), and you choose Rock so you lose (X
) with a score of 1 + 0 = 1.- In the third round, you will defeat your opponent's Scissors with Rock for a score of 1 + 6 = 7.
Now that you're correctly decrypting the ultra top secret strategy guide, you would get a total score of
12
.Following the Elf's instructions for the second column, what would your total score be if everything goes exactly according to your strategy guide?
This is where it gets a little bit more complicated. I can't just parse the values as is anymore, because we only have what the opponent
chose and the result of the match-up instead. I'll solve this using the same method I used to solve a previous AoC question. I'll create an enumerator with values for winning, losing and drawing to some value, like such below:
1
2
3
4
5
6
#[derive(PartialEq)]
enum MatchVal {
Win(i32),
Draw(i32),
Lose(i32),
}
I will have a .matches()
method implemented for this enumerator, which gives us a value that will win, lose or draw against the value stored in the enumerator. For example, MatchVal::Win(1)
will return us 2
, as Paper (2
) wins against Rock (1
). I will have to guard this against overflow and underflow, similar to the above case when Paper and Scissors were chosen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
impl MatchVal {
fn matches(&self) -> i32 {
match self {
MatchVal::Win(left) => {
if left + 1 > 3 {
left - 2
} else {
left + 1
}
}
MatchVal::Draw(left) => *left,
MatchVal::Lose(left) => {
if left - 1 == 0 {
left + 2
} else {
left - 1
}
}
}
}
}
And that's basically it! The only thing left is to write the function body for the actual second part of the program:
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
pub fn part_two(input: &str) -> Option<u32> {
let mut score: u32 = 0;
for line in input.lines() {
let (opponent, response) = line.split_once(' ').unwrap();
let left = match opponent {
"A" => 1,
"B" => 2,
"C" => 3,
_ => unreachable!(),
};
let right = match response {
"X" => MatchVal::Lose(left)
"Y" => {
score += 3;
MatchVal::Draw(left)
}
"Z" => {
score += 6;
MatchVal::Win(left)
}
_ => unreachable!(),
}
score += right.matches() as u32;
}
Some(score)
}
Once again, I'll be going through each line, splitting it into opponent
and response
, then parsing these two parts. left
will once again be the opponent's move, in numbers, and right will be our response according to the strategy guide. I'll be adding the scores right when we assign the value to right
for convenience's sake, then it's just adding the value of our response into the score.
Afterword
This is a pretty tough one to implement, if I had to be honest. I cycled through a few ideas, including just brute-forcing through all the cases, but the idea from that 2015 AoC problem helped me get through it. I also made a really dumb mistake of assigning X
as winning and Z
as losing and it took me so long to realize T_T.
My code, once again, can be found on my GitHub.