228 lines
7.9 KiB
Rust
228 lines
7.9 KiB
Rust
use std::fmt;
|
|
use std::io::{self, BufRead};
|
|
use std::cmp::{Ord, Ordering};
|
|
|
|
const HIGH_CARD: u32 = 0;
|
|
const ONE_PAIR: u32 = 1;
|
|
const TWO_PAIR: u32 = 2;
|
|
const THREE_OF_KIND: u32 = 3;
|
|
const FULL_HOUSE: u32 = 4;
|
|
const FOUR_OF_KIND: u32 = 5;
|
|
const FIVE_OF_KIND: u32 = 6;
|
|
|
|
|
|
#[derive(Eq)]
|
|
struct Hand {
|
|
cards: Vec<usize>,
|
|
hand_type: u32,
|
|
bid: u32,
|
|
}
|
|
impl Hand {
|
|
fn new1(cards: &str, bid: u32, card_counts: &mut [usize; 13]) -> Hand {
|
|
let mut hand = Hand{ cards: cards.chars().map(|c| Hand::char_to_num1(c)).collect(), hand_type: HIGH_CARD, bid: bid };
|
|
assert!(hand.cards.len() == 5, "Found {} cards instead of five", hand.cards.len());
|
|
hand.determine_type1(card_counts);
|
|
return hand;
|
|
}
|
|
fn new2(cards: &str, bid: u32, card_counts: &mut [usize; 13]) -> Hand {
|
|
let mut hand = Hand{ cards: cards.chars().map(|c| Hand::char_to_num2(c)).collect(), hand_type: HIGH_CARD, bid: bid };
|
|
assert!(hand.cards.len() == 5, "Found {} cards instead of five", hand.cards.len());
|
|
hand.determine_type2(card_counts);
|
|
return hand;
|
|
}
|
|
fn char_to_num1(c: char) -> usize {
|
|
match c {
|
|
'2' => return 0,
|
|
'3' => return 1,
|
|
'4' => return 2,
|
|
'5' => return 3,
|
|
'6' => return 4,
|
|
'7' => return 5,
|
|
'8' => return 6,
|
|
'9' => return 7,
|
|
'T' => return 8,
|
|
'J' => return 9,
|
|
'Q' => return 10,
|
|
'K' => return 11,
|
|
'A' => return 12,
|
|
_ => panic!("Invalid char found: {}", c),
|
|
}
|
|
}
|
|
fn char_to_num2(c: char) -> usize {
|
|
match c {
|
|
'J' => return 0,
|
|
'2' => return 1,
|
|
'3' => return 2,
|
|
'4' => return 3,
|
|
'5' => return 4,
|
|
'6' => return 5,
|
|
'7' => return 6,
|
|
'8' => return 7,
|
|
'9' => return 8,
|
|
'T' => return 9,
|
|
'Q' => return 10,
|
|
'K' => return 11,
|
|
'A' => return 12,
|
|
_ => panic!("Invalid char found: {}", c),
|
|
}
|
|
}
|
|
fn determine_type1(&mut self, card_counts: &mut [usize; 13]) {
|
|
// use given array to reduce memory allocations
|
|
for count in card_counts.iter_mut() { *count = 0 };
|
|
for card in &self.cards {
|
|
card_counts[*card] += 1;
|
|
}
|
|
let mut found_three = false;
|
|
let mut found_two = false;
|
|
for count in card_counts {
|
|
match count {
|
|
5 => { self.hand_type = FIVE_OF_KIND; return; },
|
|
4 => { self.hand_type = FOUR_OF_KIND; return; },
|
|
3 => { self.hand_type = THREE_OF_KIND; found_three = true; },
|
|
2 => {
|
|
if found_two {
|
|
self.hand_type = TWO_PAIR;
|
|
return;
|
|
}
|
|
else {
|
|
self.hand_type = ONE_PAIR;
|
|
found_two = true;
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
if found_two && found_three {
|
|
self.hand_type = FULL_HOUSE;
|
|
}
|
|
}
|
|
}
|
|
fn determine_type2(&mut self, card_counts: &mut [usize; 13]) {
|
|
// better solution would be to just add the number of jokers to the card with the highest
|
|
// number
|
|
// use given array to reduce memory allocations
|
|
for count in card_counts.iter_mut() { *count = 0 };
|
|
for card in &self.cards {
|
|
card_counts[*card] += 1;
|
|
}
|
|
let mut found_three = false;
|
|
let mut found_two = false;
|
|
// determine without jokers
|
|
for count in card_counts.iter().skip(1) {
|
|
match count {
|
|
5 => { self.hand_type = FIVE_OF_KIND; return; },
|
|
4 => { self.hand_type = FOUR_OF_KIND; break; },
|
|
3 => { self.hand_type = THREE_OF_KIND; found_three = true; },
|
|
2 => {
|
|
if found_two {
|
|
self.hand_type = TWO_PAIR;
|
|
break;
|
|
}
|
|
else {
|
|
self.hand_type = ONE_PAIR;
|
|
found_two = true;
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
if found_two && found_three {
|
|
self.hand_type = FULL_HOUSE;
|
|
return
|
|
}
|
|
}
|
|
match card_counts[0] {
|
|
0 => return, // no jokers
|
|
1 => match self.hand_type {
|
|
FOUR_OF_KIND => self.hand_type = FIVE_OF_KIND,
|
|
THREE_OF_KIND => self.hand_type = FOUR_OF_KIND,
|
|
TWO_PAIR => self.hand_type = FULL_HOUSE,
|
|
ONE_PAIR => self.hand_type = THREE_OF_KIND,
|
|
HIGH_CARD => self.hand_type = ONE_PAIR,
|
|
_ => panic!("Unhandled joker(1) case: hand_type={}", self.hand_type),
|
|
},
|
|
2 => match self.hand_type {
|
|
THREE_OF_KIND => self.hand_type = FIVE_OF_KIND,
|
|
ONE_PAIR => self.hand_type = FOUR_OF_KIND,
|
|
HIGH_CARD => self.hand_type = THREE_OF_KIND,
|
|
_ => panic!("Unhandled joker(2) case: hand_type={}", self.hand_type)
|
|
},
|
|
3 => match self.hand_type {
|
|
ONE_PAIR => self.hand_type = FIVE_OF_KIND,
|
|
HIGH_CARD => self.hand_type = FOUR_OF_KIND,
|
|
_ => panic!("Unhandled joker(3) case: hand_type={}", self.hand_type)
|
|
},
|
|
_ => self.hand_type = FIVE_OF_KIND
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
impl Ord for Hand {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
let mut order = self.hand_type.cmp(&other.hand_type);
|
|
if order != Ordering::Equal { return order };
|
|
for i in 0..self.cards.len() {
|
|
order = self.cards[i].cmp(&other.cards[i]);
|
|
if order != Ordering::Equal { return order };
|
|
}
|
|
panic!("Equal elements: {} and {}", self, other);
|
|
}
|
|
}
|
|
impl PartialOrd for Hand {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
return Some(self.cmp(other));
|
|
}
|
|
}
|
|
impl PartialEq for Hand {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
return self.cmp(other) == Ordering::Equal;
|
|
}
|
|
}
|
|
impl fmt::Display for Hand {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let _ =write!(f, "Hand=");
|
|
for c in &self.cards {
|
|
let _ = write!(f, "{}", c);
|
|
}
|
|
write!(f, ", type={}, bid={}", self.hand_type, self.bid)
|
|
}
|
|
}
|
|
|
|
|
|
fn main() {
|
|
let input = "input.txt";
|
|
let lines = read_lines(&input);
|
|
let mut hands1: Vec<Hand> = Vec::new();
|
|
hands1.reserve(1000);
|
|
let mut hands2: Vec<Hand> = Vec::new();
|
|
hands2.reserve(1000);
|
|
// use given array to reduce memory allocations
|
|
let mut card_counts: [usize; 13] = [0; 13];
|
|
for (_, line) in lines.enumerate() {
|
|
let Ok(line) = line else { panic!("Line not ok"); };
|
|
hands1.push(Hand::new1(&line[..5], line[6..].parse::<u32>().unwrap(), &mut card_counts));
|
|
hands2.push(Hand::new2(&line[..5], line[6..].parse::<u32>().unwrap(), &mut card_counts));
|
|
// println!("Hand: {}", hands.last().expect("No hand"));
|
|
}
|
|
hands1.sort_unstable();
|
|
hands2.sort_unstable();
|
|
let mut total_winnings1: u32 = 0;
|
|
let mut total_winnings2: u32 = 0;
|
|
for (i, hand) in hands1.iter().enumerate() {
|
|
total_winnings1 += (i as u32 + 1) * hand.bid;
|
|
}
|
|
for (i, hand) in hands2.iter().enumerate() {
|
|
total_winnings2 += (i as u32 + 1) * hand.bid;
|
|
}
|
|
println!("Total winnings (1): {}", total_winnings1);
|
|
println!("Total winnings (2): {}", total_winnings2);
|
|
}
|
|
|
|
|
|
fn read_lines<P>(filename: P) -> io::Lines<io::BufReader<std::fs::File>>
|
|
where P: AsRef<std::path::Path>, {
|
|
return match std::fs::File::open(filename) {
|
|
Err(why) => panic!("Could not open file. {}", why),
|
|
Ok(file) => std::io::BufReader::new(file).lines()
|
|
};
|
|
}
|