|
|
|
|
|
|
|
|
|
|
|
from collections import Counter |
|
|
|
|
|
|
|
|
|
|
|
from helpers import Helper |
|
|
|
|
|
|
|
|
|
|
|
helper = Helper(debug=True) |
|
|
|
|
|
debug = helper.debug |
|
|
|
|
|
load_input = helper.load_input |
|
|
|
|
|
|
|
|
|
|
|
CARD_VALUES = ["J"] |
|
|
|
|
|
for i in range(2,10): |
|
|
|
|
|
CARD_VALUES.append(str(i)) |
|
|
|
|
|
CARD_VALUES.extend(["T", "Q", "K", "A"]) |
|
|
|
|
|
|
|
|
|
|
|
HAND_VALUES = [ |
|
|
|
|
|
"High card", |
|
|
|
|
|
"One pair", |
|
|
|
|
|
"Two pair", |
|
|
|
|
|
"Three of a kind", |
|
|
|
|
|
"Full House", |
|
|
|
|
|
"Four of a kind", |
|
|
|
|
|
"Five of a kind" |
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
class Hand: |
|
|
|
|
|
def __init__(self, cards, bid): |
|
|
|
|
|
self.cards = cards |
|
|
|
|
|
self.bid = bid |
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
|
def value(self): |
|
|
|
|
|
card_counter = Counter(self.cards) |
|
|
|
|
|
card_counts = card_counter.most_common() |
|
|
|
|
|
# We have to separate J being the most common card out, |
|
|
|
|
|
# or else it might get counted twice. |
|
|
|
|
|
if card_counts[0][0] == "J": |
|
|
|
|
|
# Five jacks is five of a kind. |
|
|
|
|
|
# Four jacks is five of a kind too, because the other card counts. |
|
|
|
|
|
# Three jacks plus two of another card is five of a kind. |
|
|
|
|
|
if (card_counts[0][1] == 5 |
|
|
|
|
|
or card_counts[0][1] == 4 |
|
|
|
|
|
or (card_counts[0][1] == 3 and card_counts[1][1] == 2) |
|
|
|
|
|
): |
|
|
|
|
|
return "Five of a kind" |
|
|
|
|
|
# Three jacks is four of a kind, because the next most common card counts. |
|
|
|
|
|
# Two jacks plus two of another card is four of a kind. |
|
|
|
|
|
if (card_counts[0][1] == 3 |
|
|
|
|
|
or (card_counts[0][1] == 2 and card_counts[1][1] == 2) |
|
|
|
|
|
): |
|
|
|
|
|
return "Four of a kind" |
|
|
|
|
|
# Weirdly, you can only get a full house if there's only one jack... |
|
|
|
|
|
# and if that's the case, it won't be at the front of most_common(). |
|
|
|
|
|
if card_counts[0][1] == 2: |
|
|
|
|
|
return "Three of a kind" |
|
|
|
|
|
# If J is at the front of most_common(), and there's only one of it, |
|
|
|
|
|
# then there's no more than 1 of any other card. So we have a pair. |
|
|
|
|
|
return "One pair" |
|
|
|
|
|
|
|
|
|
|
|
# Okay, done with J being at the front. |
|
|
|
|
|
# There are five cards of a kind, or (three/four) cards of a kind and |
|
|
|
|
|
# (one/two) J. |
|
|
|
|
|
# If there are only two of a kind at the front, then there aren't more |
|
|
|
|
|
# than two of any other kind, so we can't get to 5. |
|
|
|
|
|
if card_counts[0][1] == 5 or card_counts[0][1] + card_counter["J"] == 5: |
|
|
|
|
|
return "Five of a kind" |
|
|
|
|
|
# There are four cards of a kind without a J, or (two/three) cards of |
|
|
|
|
|
# a kind and (one/two) J. |
|
|
|
|
|
if card_counts[0][1] == 4 or card_counts[0][1] + card_counter["J"] == 4: |
|
|
|
|
|
return "Four of a kind" |
|
|
|
|
|
# There are three cards of a kind without a J, or two cards of a kind and |
|
|
|
|
|
# exactly one J. |
|
|
|
|
|
if card_counts[0][1] == 3 or card_counts[0][1] + card_counter["J"] == 3: |
|
|
|
|
|
# We know the most common card isn't a J; we already covered that |
|
|
|
|
|
# in a separate branch. |
|
|
|
|
|
# If the most common count is 3 and there's a J, we already covered |
|
|
|
|
|
# that too, with four and five of a kind. |
|
|
|
|
|
# If the most common count is 2 and there are 2 Js, we already covered |
|
|
|
|
|
# that with four of a kind. |
|
|
|
|
|
# So if the most common count is 2 and there's a J, it can't be in second |
|
|
|
|
|
# position, and if the most common count is 3 then there can't be a J. |
|
|
|
|
|
# So we can discard the possibility that J is in second position. |
|
|
|
|
|
if card_counts[1][1] == 2: |
|
|
|
|
|
return "Full House" |
|
|
|
|
|
return "Three of a kind" |
|
|
|
|
|
# There are two of the most common card without any Js, or one and a single J. |
|
|
|
|
|
if card_counts[0][1] == 2 or card_counts[0][1] + card_counter["J"] == 2: |
|
|
|
|
|
# Same logic as above. If J were the second-most-common card we'd have |
|
|
|
|
|
# already encountered it. |
|
|
|
|
|
if card_counts[1][1] == 2: |
|
|
|
|
|
return "Two pair" |
|
|
|
|
|
return "One pair" |
|
|
|
|
|
return "High card" |
|
|
|
|
|
|
|
|
|
|
|
def __lt__(self, other): |
|
|
|
|
|
# They have different hand values |
|
|
|
|
|
if self.value != other.value: |
|
|
|
|
|
return HAND_VALUES.index(self.value) < HAND_VALUES.index(other.value) |
|
|
|
|
|
# They have the same hand value |
|
|
|
|
|
# So check each card in sequence |
|
|
|
|
|
for i in range(len(self.cards)): |
|
|
|
|
|
if self.cards[i] != other.cards[i]: |
|
|
|
|
|
# They have different nth cards |
|
|
|
|
|
return CARD_VALUES.index(self.cards[i]) < CARD_VALUES.index(other.cards[i]) |
|
|
|
|
|
# They're the same |
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
|
|
return f"Camel Cards hand: {self.cards} (value {self.value}, bid {self.bid})" |
|
|
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
|
return self.__str__() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
|
|
|
|
lines = load_input(7) |
|
|
|
|
|
hands = [] |
|
|
|
|
|
for line in lines: |
|
|
|
|
|
cards, bid = line.split() |
|
|
|
|
|
hands.append(Hand( |
|
|
|
|
|
cards = cards, |
|
|
|
|
|
bid = bid |
|
|
|
|
|
)) |
|
|
|
|
|
hands.sort() |
|
|
|
|
|
print(hands[:10]) |
|
|
|
|
|
|
|
|
|
|
|
total_winnings = 0 |
|
|
|
|
|
for i, hand in enumerate(hands): |
|
|
|
|
|
total_winnings += ((i+1) * int(hand.bid)) |
|
|
|
|
|
|
|
|
|
|
|
print(f"Total winnings: {total_winnings}") |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
main() |