|  |  | @@ -0,0 +1,132 @@ | 
		
	
		
			
			|  |  |  | 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() |