2022-12-02 11:13:13 +01:00
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
2022-12-02 10:24:20 +01:00
|
|
|
|
use aoc_2022::*;
|
|
|
|
|
|
|
|
|
|
use color_eyre::eyre::Result;
|
2022-12-02 11:13:13 +01:00
|
|
|
|
use thiserror::Error;
|
2022-12-02 10:24:20 +01:00
|
|
|
|
use tracing::*;
|
|
|
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
enum Shape {
|
|
|
|
|
Rock,
|
|
|
|
|
Paper,
|
|
|
|
|
Scissors,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Shape {
|
|
|
|
|
fn score(&self) -> i32 {
|
|
|
|
|
match self {
|
|
|
|
|
Shape::Rock => 1,
|
|
|
|
|
Shape::Paper => 2,
|
|
|
|
|
Shape::Scissors => 3,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 11:13:13 +01:00
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
|
enum ShapeError {
|
|
|
|
|
#[error("empty string given")]
|
|
|
|
|
Empty,
|
|
|
|
|
|
|
|
|
|
#[error("unknown shape ‹{0}›")]
|
|
|
|
|
UnknownShape(String),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for Shape {
|
|
|
|
|
type Err = ShapeError;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
if s.is_empty() {
|
|
|
|
|
return Err(ShapeError::Empty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match s {
|
|
|
|
|
"A" | "X" => Ok(Shape::Rock),
|
|
|
|
|
"B" | "Y" => Ok(Shape::Paper),
|
|
|
|
|
"C" | "Z" => Ok(Shape::Scissors),
|
|
|
|
|
_ => Err(ShapeError::UnknownShape(s.to_string())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 10:24:20 +01:00
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
enum Outcome {
|
|
|
|
|
Lose,
|
|
|
|
|
Draw,
|
|
|
|
|
Win,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
struct Round {
|
|
|
|
|
opponent: Shape,
|
|
|
|
|
me: Shape,
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 11:13:13 +01:00
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
|
enum RoundError {
|
|
|
|
|
#[error("invalid shape ‹{0}›")]
|
|
|
|
|
InvalidShape(#[from] ShapeError),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for Round {
|
|
|
|
|
type Err = RoundError;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
let mut split_str = s.split(' ');
|
|
|
|
|
|
|
|
|
|
let opponent = split_str.next().unwrap().parse::<Shape>();
|
|
|
|
|
let me = split_str.next().unwrap().parse::<Shape>();
|
|
|
|
|
|
|
|
|
|
Ok(Round {
|
|
|
|
|
opponent: opponent?,
|
|
|
|
|
me: me?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 10:24:20 +01:00
|
|
|
|
impl Round {
|
|
|
|
|
fn score(&self) -> i32 {
|
|
|
|
|
let shape_score = self.me.score();
|
|
|
|
|
|
|
|
|
|
let result_score = match (self.me, self.opponent) {
|
|
|
|
|
(Shape::Scissors, Shape::Paper) => 6,
|
|
|
|
|
(Shape::Paper, Shape::Rock) => 6,
|
|
|
|
|
(Shape::Rock, Shape::Scissors) => 6,
|
|
|
|
|
(Shape::Paper, Shape::Scissors) => 0,
|
|
|
|
|
(Shape::Rock, Shape::Paper) => 0,
|
|
|
|
|
(Shape::Scissors, Shape::Rock) => 0,
|
|
|
|
|
_ => 3,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
shape_score + result_score
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn expected_outcome(&self) -> Outcome {
|
|
|
|
|
match self.me {
|
|
|
|
|
Shape::Rock => Outcome::Lose,
|
|
|
|
|
Shape::Paper => Outcome::Draw,
|
|
|
|
|
Shape::Scissors => Outcome::Win,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn expected_score(&self) -> i32 {
|
|
|
|
|
let result_score = match self.expected_outcome() {
|
|
|
|
|
Outcome::Lose => 0,
|
|
|
|
|
Outcome::Draw => 3,
|
|
|
|
|
Outcome::Win => 6,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let shape_score = match (self.expected_outcome(), self.opponent) {
|
|
|
|
|
(Outcome::Lose, Shape::Rock) => Shape::Scissors.score(),
|
|
|
|
|
(Outcome::Lose, Shape::Paper) => Shape::Rock.score(),
|
|
|
|
|
(Outcome::Lose, Shape::Scissors) => Shape::Paper.score(),
|
|
|
|
|
(Outcome::Win, Shape::Rock) => Shape::Paper.score(),
|
|
|
|
|
(Outcome::Win, Shape::Paper) => Shape::Scissors.score(),
|
|
|
|
|
(Outcome::Win, Shape::Scissors) => Shape::Rock.score(),
|
|
|
|
|
_ => self.opponent.score(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
result_score + shape_score
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Input = Vec<Round>;
|
|
|
|
|
type Output = i32;
|
|
|
|
|
|
|
|
|
|
fn part_1(input: &Input) -> Output {
|
|
|
|
|
input.iter().map(Round::score).sum()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn part_2(input: &Input) -> Output {
|
|
|
|
|
input.iter().map(Round::expected_score).sum()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_input(pathname: &str) -> Input {
|
2022-12-02 11:13:13 +01:00
|
|
|
|
file_to_structs(pathname)
|
2022-12-02 10:24:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() -> Result<()> {
|
|
|
|
|
tracing_subscriber::fmt()
|
|
|
|
|
.with_env_filter(EnvFilter::from_default_env())
|
|
|
|
|
.with_target(false)
|
|
|
|
|
.with_file(true)
|
|
|
|
|
.with_line_number(true)
|
|
|
|
|
.without_time()
|
|
|
|
|
.compact()
|
|
|
|
|
.init();
|
|
|
|
|
color_eyre::install()?;
|
|
|
|
|
|
|
|
|
|
let input = parse_input("inputs/day02.txt");
|
|
|
|
|
|
|
|
|
|
info!("Part 1: {}", part_1(&input));
|
|
|
|
|
info!("Part 2: {}", part_2(&input));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_part_1() {
|
|
|
|
|
let sample = parse_input("samples/day02.txt");
|
|
|
|
|
assert_eq!(part_1(&sample), 15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_part_2() {
|
|
|
|
|
let sample = parse_input("samples/day02.txt");
|
|
|
|
|
assert_eq!(part_2(&sample), 12);
|
|
|
|
|
}
|
|
|
|
|
}
|