Advent of Code 2022 - Day 02
Tags: rust, adventofcode, programming
Published: 2022-12-06 02:57:31

---
title: Advent of Code 2022 - Day 02
published: true
date: 2022-12-02 00:00:00 UTC
tags: rust, adventofcode, programming
canonical_url: https://www.ericburden.work/blog/2022/12/02/advent-of-code-2022-day-02/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vohhupus9y94rij7qcta.png
---

It’s that time of year again! Just like last year, I’ll be posting my solutions to the [Advent of Code](https://adventofcode.com/) puzzles. This year, I’ll be solving the puzzles in [Rust](https://www.rust-lang.org/). I’ll post my solutions and code to [GitHub](https://github.com/ericwburden/advent_of_code_2022) as well. After finishing last year (and 2015-2019) in [Julia](https://julialang.org/), I needed to spend some time with Rust again! If you haven’t given AoC a try, I encourage you to do so along with me!

# Day 02 - Rock Paper Scissors

Find the problem description [HERE](https://adventofcode.com/2022/day/2).

## The Input - First Come, First Served

Today, my suspicions from prior years was confirmed: the surprisingly deterministic nature of elf behavior belies the fact that elves are, in fact, toys constructed by Santa and managed by a (mostly) sophisticated AI. If that’s true, though, then… am _I_ real? Well, that rabbit hole got real deep real fast. Let’s talk about input parsing!

```rust
use nom::{
    character::complete::{anychar, space1},
    error::Error as NomError,
    sequence::separated_pair,
    Finish, IResult,
};

// Today's input is a list of character pairs
pub type Input = Vec<(char, char)>;

const INPUT: &str = include_str!("../../input/02/input.txt");

/// Attempts to parse a line from the INPUT
fn parse_line(line: &str) -> Result<(char, char), NomError<&str>> {
    // parses lines that contain a character, one or more spaces,
    // then another character
    let (_, char_pair) = separated_pair(anychar, space1, anychar)(line).finish()?;
    Ok(char_pair)
}

/// Parse the INPUT
pub fn read() -> Input {
    // Parse the lines into (char, char) values and return the resulting
    // list. Ignores any lines that fail to parse.
    INPUT.lines().flat_map(parse_line).collect()
}

```

Nothing revolutionary here in terms of functionality, just converting each line like `"A X"` to a tuple of `('A', 'X')`. The big thing to note (and keep an eye out for on subsequent days) is the use of the `nom` parser combinator crate. One of my personal goals for Advent of Code this year is to improve my text parsing skills, and I’ve chosen to get familiar with `nom`. Normally, I’d lean on regular expressions, but I find that the parser combinators are a lot more_debuggable_ when parsing gets complex. So, expect to see more `nom` in coming days!

## Part One - Rock, Paper, Cheaters

Turns out, one ~~traitor to his people~~ friendly elf has the key to unlocking our chance to pitch a tent right next to the expedition’s snack storage. Excellent! All we need to do is decrypt the strategy guide (based on at least one shaky assumption) and ensure that it won’t be totally obvious how much we’re cheating at this child’s game. No problem!

```rust
/// Represents a 'shape' in a game of Rock, Paper, Scissors
#[derive(Clone, Copy)]
pub enum Shape {
    Rock,
    Paper,
    Scissors,
}

/// Convert a `Shape` into it's score value
impl From<Shape> for u32 {
    fn from(shape: Shape) -> Self {
        match shape {
            Shape::Rock => 1,
            Shape::Paper => 2,
            Shape::Scissors => 3,
        }
    }
}

/// Trait for converting a character from the input into a `Shape`.
/// I'm using a Trait here so that I can use the same function names
/// in the two different parts but have them behave differently, while 
/// sharing some base functionality between parts.
trait TryIntoShape {
    type Error;
    fn try_into_shape(&self) -> Result<Shape, Self::Error>;
}

impl TryIntoShape for char {
    type Error = &'static str;

    /// Attempt to convert an input character into a `Shape`. Yes, we know
    /// that there will be no other characters, but I like to practice good
    /// input hygiene when I can.
    fn try_into_shape(&self) -> Result<Shape, Self::Error> {
        match self {
            'A' | 'X' => Ok(Shape::Rock),
            'B' | 'Y' => Ok(Shape::Paper),
            'C' | 'Z' => Ok(Shape::Scissors),
            _ => Err("Character cannot be converted to `Shape`!"),
        }
    }
}

/// Represents the outcome of a game of Rock, Paper, Scissors, from the
/// perspective of you, the player. Each variant encapsulates the shape
/// you made to achieve that outcome.
pub enum Outcome {
    Win(Shape),
    Draw(Shape),
    Lose(Shape),
}

impl Outcome {
    /// Calculate the score from a given outcome
    pub fn score(&self) -> u32 {
        match self {
            // 6 points for winning + the points for your shape
            Outcome::Win(t) => 6 + u32::from(*t),

            // 3 points for a draw + the points for your shape
            Outcome::Draw(t) => 3 + u32::from(*t),

            // 0 points for losing + the points for your shape
            Outcome::Lose(t) => u32::from(*t),
        }
    }
}

/// Trait for converting a pair of characters from the input into an `Outcome`.
/// Same deal as the other trait above.
trait TryIntoOutcome {
    type Error;
    fn try_into_outcome(&self) -> Result<Outcome, Self::Error>;
}

impl TryIntoOutcome for (char, char) {
    type Error = &'static str;

    #[rustfmt::skip] // I _like_ my pretty match statement below
    fn try_into_outcome(&self) -> Result<Outcome, Self::Error> {
        // Attempt to convert both characters into their respective `Shape`
        let (ch1, ch2) = self;
        let opponent = ch1.try_into_shape()?;
        let player = ch2.try_into_shape()?;

        // Based on the shapes, determine who won and return the `Outcome`
        use Shape::*;
        let result = match (opponent, player) {
            (Rock, Rock) => Outcome::Draw(player),
            (Rock, Paper) => Outcome::Win(player),
            (Rock, Scissors) => Outcome::Lose(player),
            (Paper, Rock) => Outcome::Lose(player),
            (Paper, Paper) => Outcome::Draw(player),
            (Paper, Scissors) => Outcome::Win(player),
            (Scissors, Rock) => Outcome::Win(player),
            (Scissors, Paper) => Outcome::Lose(player),
            (Scissors, Scissors) => Outcome::Draw(player),
        };
        Ok(result)
    }
}

/// Solve part one
pub fn solve(input: &Input) -> u32 {
    // For each pair of characters in the input, convert each to an `Outcome`,
    // calculate the score of that `Outcome`, and return the total as an `Output`.
    input
        .iter()
        .flat_map(|pair| pair.try_into_outcome())
        .map(|outcome| outcome.score())
        .sum::<u32>()
}

```

There’s actually quite a lot going on here, so let’s break it down:

- Each letter in the input represents a `Shape` to be thrown by either an elf or you. The `Shape` struct just formalized the relationship between that input letter and the shape thrown. Converting a `Shape` to a `u32` provides the score for throwing that shape (regardless of who wins). The `TryIntoShape` trait specifies how a character can be transformed into a `Shape` and provides error checking for invalid characters as well.
- Each round of play results in an `Outcome`, where you either win, lose, or draw with your opponent. Because your score for that round is based on the outcome and the shape you threw, we encapsulate your `Shape` in each variant of`Outcome`, providing all the information needed to calculate a score, which is accomplished via the `Outcome.score()` method. The `TryIntoOutcome` trait facilitates converting a tuple of characters (from the input) into an`Outcome`, and it can return an error in cases where an invalid letter is given.
- The `TryIntoShape` and `TryIntoOutcome` traits are provided this way because of my project structure. Because (spoiler alert!) the meaning of the letters changes a bit in part 2, I wanted to be able to change the _some_ of the functionality of these objects between parts, but not _all_. So, I broke the parts I wanted to change out into traits, and defined these traits separately for part one and part two. This means my project crate has a`crate::day02::part1::TryIntoShape` trait and a `crate::day02::part2::TryIntoShape`trait that do different things. Probably not a great strategy in production code, but useful for keeping my parts separate without too much copying and pasting of code in between.

So, given what we know, we convert each line from the input into an `Outcome`, score that `Outcome`, and pass out the resulting total of all the scores.

## Part Two - Patience is a Virtue

Yeah…we all knew that we dun goofed when we made assumptions about what was in that elf strategy plan, right? Right. Turns out, ‘X’, ‘Y’, and ‘Z’ tell us how the round should _end_, and it’s up to us to figure out how to make it so. No problem, just gotta change out our traits a bit.

```rust
/// Trait for converting a character from the input into a `Shape`.
/// I'm using a Trait here so that I can use the same function names
/// in the two different parts but have them behave differently, while 
/// sharing some base functionality between parts.
trait TryIntoShape {
    type Error;
    fn try_into_shape(&self) -> Result<Shape, Self::Error>;
}

impl TryIntoShape for char {
    type Error = &'static str;

    /// Attempt to convert an input character into a `Shape`. This time,
    /// we know that 'X', 'Y', and 'Z' do not represent shapes, so we don't
    /// try to convert them.
    fn try_into_shape(&self) -> Result<Shape, Self::Error> {
        match self {
            'A' => Ok(Shape::Rock),
            'B' => Ok(Shape::Paper),
            'C' => Ok(Shape::Scissors),
            _ => Err("Character cannot be converted to `Shape`!"),
        }
    }
}

/// Trait for converting a pair of characters from the input into an `Outcome`.
/// Same deal as the other trait above.
trait TryIntoOutcome {
    type Error;
    fn try_into_outcome(&self) -> Result<Outcome, Self::Error>;
}

impl TryIntoOutcome for (char, char) {
    type Error = &'static str;

    #[rustfmt::skip] // I _still like_ my pretty match statement below
    fn try_into_outcome(&self) -> Result<Outcome, Self::Error> {
        // Now, we only convert the first character into a `Shape`
        let (ch1, result) = self;
        let opponent = ch1.try_into_shape()?;

        // Using the mapping that 'X' means we lose, 'Y' means we draw, and 
        // 'Z' means we win, determine the outcome of the game and what shape
        // you the player need to make to achieve that outcome, and return 
        // the `Outcome`.
        use Shape::*;
        match (opponent, result) {
            (Rock, 'Y') => Ok(Outcome::Draw(Rock)),
            (Rock, 'Z') => Ok(Outcome::Win(Paper)),
            (Rock, 'X') => Ok(Outcome::Lose(Scissors)),
            (Paper, 'X') => Ok(Outcome::Lose(Rock)),
            (Paper, 'Y') => Ok(Outcome::Draw(Paper)),
            (Paper, 'Z') => Ok(Outcome::Win(Scissors)),
            (Scissors, 'Z') => Ok(Outcome::Win(Rock)),
            (Scissors, 'X') => Ok(Outcome::Lose(Paper)),
            (Scissors, 'Y') => Ok(Outcome::Draw(Scissors)),
            (_, _) => Err("Cannot convert character pair to an Outcome!"),
        }
    }
}

/// Solve part two
///
/// Why yes, this is _exactly_ the same solve function as part one. Why do you
/// ask?
pub fn solve(input: &Input) -> u32 {
    // For each pair of characters in the input, convert each to an `Outcome`,
    // calculate the score of that `Outcome`, and return the total as an `Output`.
    input
        .iter()
        .flat_map(|pair| pair.try_into_outcome())
        .map(|outcome| outcome.score())
        .sum::<u32>()
}

```

And, that’s all there is to it. See, I told you all I needed to change was the functionality of those traits.

## Wrap Up

Honestly, the hardest part about today’s puzzle was the self-imposed need to keep my code as DRY as I reasonably could without making it harder to read or reason about. Turns out, you can’t have conflicting implementations for structs or enums, even if they’re in two modules that will never conflict, because the Rust compiler _can’t be sure_ that they won’t _ever_ come into conflict. Good lookin’ out, Rust compiler! So, I learned a bit about Rust module and project structure today. If I didn’t explain myself well enough, I encourage you to check out the [repo](https://github.com/ericwburden/advent_of_code_2022/tree/main/src/day02)so you can see exactly how my code for today is arranged. Maybe you’ll learn something too!