224 lines
5.8 KiB
Rust
224 lines
5.8 KiB
Rust
use std::cmp::Ordering;
|
|
|
|
use crate::common::file::split_lines;
|
|
|
|
use super::template::{DayTrait, ResultType};
|
|
use thiserror::Error;
|
|
|
|
const DAY_NUMBER: usize = 2;
|
|
|
|
pub struct Day;
|
|
|
|
impl DayTrait for Day {
|
|
fn get_day_number(&self) -> usize {
|
|
DAY_NUMBER
|
|
}
|
|
|
|
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
|
let sum = split_lines(lines)
|
|
.map(|line| Rps::parse_line(line))
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
.into_iter()
|
|
.map(|(first, second)| second.asses_pair(&first))
|
|
.sum();
|
|
|
|
Ok(ResultType::Integer(sum))
|
|
}
|
|
|
|
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
|
let sum = split_lines(lines)
|
|
.map(|line| Strategy::parse_line(line))
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
.into_iter()
|
|
.map(|(first, second)| second.fullfill(&first).asses_pair(&first))
|
|
.sum();
|
|
|
|
Ok(ResultType::Integer(sum))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
enum RPSError {
|
|
#[error("No a valid RPS: {0}")]
|
|
ParseError(String),
|
|
|
|
#[error("Not a logal RPS line: {0}")]
|
|
IllegalLine(String),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
enum Rps {
|
|
Rock,
|
|
Paper,
|
|
Scissors,
|
|
}
|
|
|
|
impl Rps {
|
|
pub fn parse_line(line: &str) -> Result<(Self, Self), RPSError> {
|
|
let mut parts = line.split(' ');
|
|
let (Some(first), Some(second)) = (parts.next(), parts.next()) else {
|
|
Err(RPSError::IllegalLine(line.to_owned()))?
|
|
};
|
|
|
|
let first = Rps::try_from(first)?;
|
|
let second = Rps::try_from(second)?;
|
|
Ok((first, second))
|
|
}
|
|
|
|
pub fn value(&self) -> i64 {
|
|
match self {
|
|
Rps::Rock => 1,
|
|
Rps::Paper => 2,
|
|
Rps::Scissors => 3,
|
|
}
|
|
}
|
|
|
|
pub fn asses_pair(&self, other: &Self) -> i64 {
|
|
let outcome = match self.partial_cmp(other) {
|
|
Some(Ordering::Less) => 0,
|
|
Some(Ordering::Equal) => 3,
|
|
Some(Ordering::Greater) => 6,
|
|
None => unreachable!(),
|
|
};
|
|
outcome + self.value()
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for Rps {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
match (self, other) {
|
|
(Rps::Rock, Rps::Paper) | (Rps::Paper, Rps::Scissors) | (Rps::Scissors, Rps::Rock) => {
|
|
Some(Ordering::Less)
|
|
}
|
|
(Rps::Rock, Rps::Scissors) | (Rps::Paper, Rps::Rock) | (Rps::Scissors, Rps::Paper) => {
|
|
Some(Ordering::Greater)
|
|
}
|
|
_ => Some(Ordering::Equal),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&str> for Rps {
|
|
type Error = RPSError;
|
|
|
|
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
|
match value {
|
|
"A" | "X" => Ok(Rps::Rock),
|
|
"B" | "Y" => Ok(Rps::Paper),
|
|
"C" | "Z" => Ok(Rps::Scissors),
|
|
_ => Err(RPSError::ParseError(value.to_owned())),
|
|
}
|
|
}
|
|
}
|
|
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
|
enum Strategy {
|
|
Loose,
|
|
Draw,
|
|
Win,
|
|
}
|
|
|
|
impl Strategy {
|
|
pub fn parse_line(line: &str) -> Result<(Rps, Self), RPSError> {
|
|
let mut parts = line.split(' ');
|
|
let (Some(first), Some(second)) = (parts.next(), parts.next()) else {
|
|
Err(RPSError::IllegalLine(line.to_owned()))?
|
|
};
|
|
|
|
let first = Rps::try_from(first)?;
|
|
let second = Strategy::try_from(second)?;
|
|
Ok((first, second))
|
|
}
|
|
|
|
pub fn fullfill(&self, other: &Rps) -> Rps {
|
|
match (other, self) {
|
|
(_, Strategy::Draw) => *other,
|
|
(Rps::Rock, Strategy::Win) | (Rps::Scissors, Strategy::Loose) => Rps::Paper,
|
|
(Rps::Paper, Strategy::Win) | (Rps::Rock, Strategy::Loose) => Rps::Scissors,
|
|
(Rps::Scissors, Strategy::Win) | (Rps::Paper, Strategy::Loose) => Rps::Rock,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&str> for Strategy {
|
|
type Error = RPSError;
|
|
|
|
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
|
match value {
|
|
"X" => Ok(Strategy::Loose),
|
|
"Y" => Ok(Strategy::Draw),
|
|
"Z" => Ok(Strategy::Win),
|
|
_ => Err(RPSError::ParseError(value.to_owned())),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use crate::common::file::read_string;
|
|
use anyhow::Result;
|
|
|
|
#[test]
|
|
fn test_parse() -> Result<()> {
|
|
let input = "A Y";
|
|
let expected = (Rps::Rock, Rps::Paper);
|
|
let result = Rps::parse_line(input)?;
|
|
assert_eq!(result, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_assess() -> Result<()> {
|
|
let first = Rps::Scissors;
|
|
let second = Rps::Paper;
|
|
let expected = 9;
|
|
let result = first.asses_pair(&second);
|
|
assert_eq!(result, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_part1() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let expected = ResultType::Integer(15);
|
|
let result = day.part1(&lines)?;
|
|
assert_eq!(result, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_strategy() -> Result<()> {
|
|
let input = "A Y";
|
|
let expected = (Rps::Rock, Strategy::Draw);
|
|
let result = Strategy::parse_line(input)?;
|
|
assert_eq!(result, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_assess_stragety() -> Result<()> {
|
|
let first = Rps::Scissors;
|
|
let second = Strategy::Win;
|
|
let expected = Rps::Rock;
|
|
let result = second.fullfill(&first);
|
|
assert_eq!(result, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_part2() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let expected = ResultType::Integer(12);
|
|
let result = day.part2(&lines)?;
|
|
assert_eq!(result, expected);
|
|
|
|
Ok(())
|
|
}
|
|
}
|