use crate::Solution;
use std::cmp::Ordering;
use std::collections::HashMap;
problem!(Problem0054, 54, "Poker Hands");
impl Solution for Problem0054 {
fn solve(&self) -> String {
const INPUT: &str = include_str!("0054_poker.txt");
INPUT
.lines()
.map(|line| Game::new(line).winner())
.filter(|&winner| winner == 1)
.count()
.to_string()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Suit {
Spades,
Clubs,
Hearts,
Diamonds,
}
impl Suit {
fn new(suit: char) -> Self {
match suit {
'S' => Self::Spades,
'C' => Self::Clubs,
'H' => Self::Hearts,
'D' => Self::Diamonds,
_ => panic!("Invalid suit."),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
enum Rank {
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Ten,
Jack,
Queen,
King,
Ace,
}
impl Rank {
fn new(rank: char) -> Self {
match rank {
'2' => Self::Two,
'3' => Self::Three,
'4' => Self::Four,
'5' => Self::Five,
'6' => Self::Six,
'7' => Self::Seven,
'8' => Self::Eight,
'9' => Self::Nine,
'T' => Self::Ten,
'J' => Self::Jack,
'Q' => Self::Queen,
'K' => Self::King,
'A' => Self::Ace,
_ => panic!("Invalid rank."),
}
}
fn value(&self) -> u8 {
match self {
Self::Two => 0,
Self::Three => 1,
Self::Four => 2,
Self::Five => 3,
Self::Six => 4,
Self::Seven => 5,
Self::Eight => 6,
Self::Nine => 7,
Self::Ten => 8,
Self::Jack => 9,
Self::Queen => 10,
Self::King => 11,
Self::Ace => 12,
}
}
}
impl PartialOrd for Rank {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Rank {
fn cmp(&self, other: &Self) -> Ordering {
self.value().cmp(&other.value())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct Card {
suit: Suit,
rank: Rank,
}
impl Card {
fn new(card: &str) -> Self {
let mut chars = card.chars();
let rank = Rank::new(chars.next().unwrap());
let suit = Suit::new(chars.next().unwrap());
Self { suit, rank }
}
fn value(&self) -> u8 {
self.rank.value()
}
}
impl PartialOrd for Card {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Card {
fn cmp(&self, other: &Self) -> Ordering {
self.rank.cmp(&other.rank)
}
}
#[derive(Debug, PartialEq, Eq)]
struct Hand {
cards: [Card; 5],
}
impl Hand {
fn new(hand: &str) -> Self {
let cards = hand
.split_whitespace()
.map(Card::new)
.collect::<Vec<Card>>()
.try_into()
.unwrap();
Self { cards }
}
fn score(&self) -> u32 {
let mut score = 0;
let mut cards = self.cards;
cards.sort();
score = score.max(self.generate_hand_code(HandType::HighCard, cards));
let mut straight = true;
for (i, card) in cards.iter().enumerate().skip(1) {
if card.value() != cards[i - 1].value() + 1 {
straight = false;
break;
}
}
if straight {
score = score.max(self.generate_hand_code(HandType::Straight, cards));
}
let mut flush = false;
if cards.iter().all(|card| card.suit == cards[0].suit) {
score = score.max(self.generate_hand_code(HandType::Flush, cards));
flush = true;
}
if straight && flush {
score = score.max(self.generate_hand_code(HandType::StraightFlush, cards));
}
let mut rank_counts = HashMap::new();
for card in &cards {
*rank_counts.entry(card.rank).or_insert(0) += 1;
}
if let Some((&rank, _)) = rank_counts.iter().find(|&(_, value)| *value == 4) {
let other_index = cards.iter().position(|card| card.rank != rank).unwrap();
cards.swap(0, other_index);
score = score.max(self.generate_hand_code(HandType::FourOfAKind, cards));
}
if let Some((&rank3, _)) = rank_counts.iter().find(|&(_, value)| *value == 3) {
let mut other_indices = cards
.iter()
.enumerate()
.filter(|(_, card)| card.rank != rank3)
.map(|(i, _)| i);
let other_index_1 = other_indices.next().unwrap();
let other_index_2 = other_indices.next().unwrap();
cards.swap(0, other_index_1);
cards.swap(1, other_index_2);
score = score.max(self.generate_hand_code(HandType::ThreeOfAKind, cards));
if rank_counts.iter().any(|(_, value)| *value == 2) {
score = score.max(self.generate_hand_code(HandType::FullHouse, cards));
}
}
if let Some((&rank2, _)) = rank_counts.iter().find(|&(_, value)| *value == 2) {
let mut other_indices = cards
.iter()
.enumerate()
.filter(|(_, card)| card.rank != rank2)
.map(|(i, _)| i);
let other_index_1 = other_indices.next().unwrap();
let other_index_2 = other_indices.next().unwrap();
let other_index_3 = other_indices.next().unwrap();
cards.swap(0, other_index_1);
cards.swap(1, other_index_2);
cards.swap(2, other_index_3);
score = score.max(self.generate_hand_code(HandType::OnePair, cards));
if let Some((&rank2_2, _)) = rank_counts.iter().filter(|&(_, value)| *value == 2).nth(1)
{
let other_index = cards
.iter()
.position(|card| card.rank != rank2 && card.rank != rank2_2)
.unwrap();
cards.swap(0, other_index);
if cards[1] > cards[3] {
cards.swap(1, 3);
cards.swap(2, 4);
}
score = score.max(self.generate_hand_code(HandType::TwoPairs, cards));
}
}
score
}
fn generate_hand_code(&self, hand_type: HandType, cards: [Card; 5]) -> u32 {
let mut code = 0;
let mut factor = 1;
for card in cards {
code += factor * card.value() as u32;
factor *= 13;
}
code += factor * hand_type.value() as u32;
code
}
}
impl PartialOrd for Hand {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> Ordering {
self.score().cmp(&other.score())
}
}
struct Game {
player1: Hand,
player2: Hand,
}
impl Game {
fn new(game: &str) -> Self {
let (player1, player2) = game.trim().split_at(game.len() / 2);
let player1 = Hand::new(player1.trim());
let player2 = Hand::new(player2.trim());
Self { player1, player2 }
}
fn winner(&mut self) -> u8 {
match self.player1.cmp(&self.player2) {
Ordering::Greater => 1,
Ordering::Less => 2,
Ordering::Equal => 0,
}
}
}
enum HandType {
HighCard,
OnePair,
TwoPairs,
ThreeOfAKind,
Straight,
Flush,
FullHouse,
FourOfAKind,
StraightFlush,
}
impl HandType {
fn value(&self) -> u8 {
match self {
Self::HighCard => 0,
Self::OnePair => 1,
Self::TwoPairs => 2,
Self::ThreeOfAKind => 3,
Self::Straight => 4,
Self::Flush => 5,
Self::FullHouse => 6,
Self::FourOfAKind => 7,
Self::StraightFlush => 8,
}
}
}