advent-of-code/2023/day7/src/main.rs

257 lines
5.6 KiB
Rust

use std::cmp::Ordering;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use HandType::*;
const FILE_STR: &str = include_str!("input.txt");
#[derive(Copy, Clone, Debug, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
#[repr(u8)]
enum NormalCardType {
#[default]
Two = 0,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Joker,
Jack,
Queen,
King,
Ace,
}
impl From<char> for NormalCardType {
fn from(value: char) -> Self {
use NormalCardType::*;
match value {
'2' => Two,
'3' => Three,
'4' => Four,
'5' => Five,
'6' => Six,
'7' => Seven,
'8' => Eight,
'9' => Nine,
'T' => Joker,
'J' => Jack,
'Q' => Queen,
'K' => King,
'A' => Ace,
_ => panic!("Invalid card character"),
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
#[repr(u8)]
enum WildJokerCardType {
#[default]
Joker = 0,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Jack,
Queen,
King,
Ace,
}
impl From<char> for WildJokerCardType {
fn from(value: char) -> Self {
use WildJokerCardType::*;
match value {
'J' => Joker,
'2' => Two,
'3' => Three,
'4' => Four,
'5' => Five,
'6' => Six,
'7' => Seven,
'8' => Eight,
'9' => Nine,
'T' => Jack,
'Q' => Queen,
'K' => King,
'A' => Ace,
_ => panic!("Invalid card character"),
}
}
}
type PartOne = NormalCardType;
type PartTwo = WildJokerCardType;
trait CardType: Eq + Copy + PartialOrd + Ord + Debug + Hash + From<char> {}
impl CardType for NormalCardType {}
impl CardType for WildJokerCardType {}
#[derive(Debug, Default, PartialEq, Eq)]
struct Hand<T> {
cards: [T; 5],
kind: HandType,
bet: usize,
}
impl<T: CardType> Hand<T>
where
HandType: From<[T; 5]>,
{
fn parse(line: &str) -> Self {
let mut parts = line.split_whitespace();
let raw_hand = parts.next().unwrap();
let raw_bet = parts.next().unwrap();
let cards: [T; 5] = raw_hand
.trim()
.chars()
.map(|ch| ch.into())
.collect::<Vec<T>>()
.try_into()
.unwrap();
let bet = raw_bet.trim().parse::<usize>().unwrap();
let kind = HandType::from(cards);
Hand { cards, kind, bet }
}
}
impl<T: CardType> PartialOrd for Hand<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: CardType> Ord for Hand<T> {
/// Implement the sorting for each hand, first comparing the type of
/// hand, and then comparing the individual cards, if necessary
fn cmp(&self, other: &Self) -> Ordering {
if self.kind != other.kind {
return self.kind.cmp(&other.kind);
}
for (i, card) in self.cards.into_iter().enumerate() {
if card != other.cards[i] {
return card.cmp(&other.cards[i]);
}
}
Ordering::Equal
}
}
#[derive(Debug, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Hash)]
#[repr(u8)]
enum HandType {
#[default]
HighCard = 0,
OnePair,
TwoPair,
ThreeOfAKind,
FullHouse,
FourOfAKind,
FiveOfAKind,
}
impl HandType {
fn from_cards(value: [WildJokerCardType; 5]) -> Self {
todo!();
}
}
impl<T: CardType> From<[T; 5]> for HandType {
fn from(value: [T; 5]) -> Self {
let mut cards = Vec::from(value);
cards.sort_unstable();
let mut count_map: HashMap<T, usize> = HashMap::new();
cards.into_iter().for_each(|ct| {
match count_map.get(&ct) {
Some(count) => count_map.insert(ct, *count + 1),
None => count_map.insert(ct, 1),
};
});
let mut card_type_counts: Vec<usize> = count_map.into_values().collect();
card_type_counts.sort_unstable();
match card_type_counts[..] {
[5] => FiveOfAKind,
[1, 4] => FourOfAKind,
[2, 3] => FullHouse,
[1, 1, 3] => ThreeOfAKind,
[1, 2, 2] => TwoPair,
[1, 1, 1, 2] => OnePair,
_ => HighCard,
}
}
}
#[derive(Debug, Default)]
struct Game<T> {
hands: Vec<Hand<T>>,
}
impl<T: CardType> Game<T> {
fn parse(input: &str) -> Game<T> {
let hands = input
.split('\n')
.filter(|s| !s.is_empty())
.map(Hand::parse)
.collect::<Vec<Hand<T>>>();
Game { hands }
}
fn get_score_sum(&mut self) -> usize {
// Rank the hands
self.hands.sort_unstable();
self.hands.iter().enumerate().fold(0, |prev, curr| {
let (i, hand) = curr;
let rank = i + 1;
prev + (rank * hand.bet)
})
}
}
fn part_one() {
let mut game: Game<PartOne> = Game::parse(FILE_STR);
let score = game.get_score_sum();
println!("Part 1: Sum of scores: {}", score);
}
fn part_two() {
let _game: Game<PartTwo> = Game::parse(FILE_STR);
}
fn main() {
part_one();
part_two();
}
#[cfg(test)]
mod test {
const EXAMPLE_FILE_STR: &str = include_str!("example-input.txt");
use super::*;
#[test]
fn get_score_sum() {
let mut game: Game<PartOne> = Game::parse(EXAMPLE_FILE_STR);
assert_eq!(6440, game.get_score_sum());
}
#[test]
fn get_wild_score_sum() {}
}