use std::{collections::HashMap, fmt::Display, path::PathBuf};
#[derive(clap::Parser, Debug)]
struct Args {
input: PathBuf,
}
#[derive(Copy, Clone, Debug)]
enum Hand {
Rock,
Paper,
Scissors,
}
#[derive(Copy, Clone, Debug, Hash)]
enum Round {
Loss,
Draw,
Win,
}
impl Display for Hand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Self::Rock => write!(f, "Rock"),
Self::Paper => write!(f, "Paper"),
Self::Scissors => write!(f, "Scissors"),
}
}
}
impl Hand {
fn round(self, other: Self) -> Round {
use Round::*;
match (self, other) {
(Hand::Rock, Hand::Rock) => Draw,
(Hand::Rock, Hand::Paper) => Loss,
(Hand::Rock, Hand::Scissors) => Win,
(Hand::Paper, Hand::Rock) => Win,
(Hand::Paper, Hand::Paper) => Draw,
(Hand::Paper, Hand::Scissors) => Loss,
(Hand::Scissors, Hand::Rock) => Loss,
(Hand::Scissors, Hand::Paper) => Win,
(Hand::Scissors, Hand::Scissors) => Draw,
}
}
}
#[derive(Debug)]
enum ParseStreamError {
UnknownCommand,
BrokenPair,
}
impl std::error::Error for ParseStreamError {}
impl Display for ParseStreamError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
<Self as std::fmt::Debug>::fmt(self, f)
}
}
fn parse_stream<'a, I: Iterator<Item = char> + 'a>(
stream: I,
mapping: &'a HashMap<char, Hand>,
) -> impl Iterator<Item = Result<(Hand, Hand), ParseStreamError>> + 'a {
struct ParseStream<'a, I> {
stream: I,
mapping: &'a HashMap<char, Hand>,
}
impl<'a, I: Iterator<Item = char>> Iterator for ParseStream<'a, I> {
type Item = Result<(Hand, Hand), ParseStreamError>;
fn next(&mut self) -> Option<Self::Item> {
let a = loop {
match self.stream.next() {
Some(c) => match self.mapping.get(&c) {
Some(a) => break *a,
None if c.is_whitespace() => (),
None => return Some(Err(ParseStreamError::UnknownCommand)),
},
None => return None,
}
};
let b = loop {
match self.stream.next() {
Some(c) if c == '\n' => return Some(Err(ParseStreamError::BrokenPair)),
Some(c) => match self.mapping.get(&c) {
Some(b) => break *b,
None if c.is_whitespace() => (),
None => return Some(Err(ParseStreamError::UnknownCommand)),
},
None => return Some(Err(ParseStreamError::BrokenPair)),
}
};
Some(Ok((a, b)))
}
}
ParseStream { stream, mapping }
}
enum ErrGrabber<J, E, I: Iterator<Item = Result<J, E>>> {
Running(I),
Finished,
Error(E),
}
impl<J, E, I: Iterator<Item = Result<J, E>>> Iterator for ErrGrabber<J, E, I> {
type Item = J;
fn next(&mut self) -> Option<J> {
match self {
Self::Running(inner) => match inner.next() {
Some(Ok(v)) => Some(v),
Some(Err(e)) => {
*self = Self::Error(e);
None
}
None => {
*self = Self::Finished;
None
}
},
_ => None,
}
}
}
impl<J, E, I: Iterator<Item = Result<J, E>>> ErrGrabber<J, E, I> {
fn new(inner: I) -> Self {
Self::Running(inner)
}
fn check(self) -> Result<(), E> {
match self {
Self::Error(e) => Err(e),
_ => Ok(()),
}
}
}
fn score_1(a: Hand, b: Hand) -> u32 {
(match a.round(b) {
Round::Loss => 0,
Round::Draw => 3,
Round::Win => 6,
}) + match a {
Hand::Rock => 1,
Hand::Paper => 2,
Hand::Scissors => 3,
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
use clap::Parser;
let args = Args::parse();
let dict = <[(char, Hand); 6] as IntoIterator>::into_iter([
('a', Hand::Rock),
('b', Hand::Paper),
('c', Hand::Scissors),
('x', Hand::Rock),
('y', Hand::Paper),
('z', Hand::Scissors),
])
.collect();
let mut buffer = String::new();
let input = {
use std::io::Read;
let mut file = std::fs::File::open(&args.input)?;
file.read_to_string(&mut buffer)?;
buffer.chars()
};
let mut rounds = ErrGrabber::new(parse_stream(input.flat_map(|c| c.to_lowercase()), &dict));
let score: u32 = (&mut rounds)
.map(|(theirs, mine)| score_1(mine, theirs))
.sum();
rounds.check()?;
println!("Total score: {}", score);
Ok(())
}