const sample = await Deno.readTextFile("sample.txt"); const input = await Deno.readTextFile("input.txt"); const cardsSolution1 = [ "2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A", ] as const; const cardsSolution2 = [ "J", "2", "3", "4", "5", "6", "7", "8", "9", "T", "Q", "K", "A", ] as const; type CardListSolution1 = typeof cardsSolution1; type CardListSolution2 = typeof cardsSolution2; type CardList = CardListSolution1 | CardListSolution2; type Card = typeof cardsSolution1[number]; type Cards = string; type Hand = [ [Card, number], [Card, number], [Card, number], [Card, number], [Card, number], ]; type GroupedHand = Partial>; type CardCounts = number[]; const getCardValue = ( cards: CardList, card: Card, ): number => cards.indexOf(card) + 1; const groupHand = (hand: Hand): GroupedHand => hand.reduce( (acc, [card]) => ({ ...acc, [card]: (acc[card] || 0) + 1 }), {} as GroupedHand, ); const countSameCards = ( groupedHand: GroupedHand, withJokers = false, ): CardCounts => { if (!withJokers) return Object.values(groupedHand).sort((a, b) => b - a); const groupedHandWithoutJokers = { ...groupedHand } satisfies GroupedHand; delete groupedHandWithoutJokers.J; return Object.values(groupedHandWithoutJokers).sort((a, b) => b - a); }; class JokerCounter { constructor(private jokers: number) {} getMax() { const jokers = Math.max(0, this.jokers); this.jokers = this.jokers - jokers; return jokers; } getDelta(max: number, current: number) { const jokersNeeded = Math.abs(max - current); const currentNumberOfJokers = this.jokers; const hasEnoughJokers = currentNumberOfJokers >= jokersNeeded; if (!hasEnoughJokers) { this.jokers = 0; return currentNumberOfJokers + current; } this.jokers = this.jokers - jokersNeeded; return max; } get(count = 1) { const jokers = Math.max(0, this.jokers - count); this.jokers = this.jokers - jokers; return jokers; } } const handTypes = [ function fiveOfAKind( cardCounts: CardCounts, groupedHand: GroupedHand, withJokers = false, ): boolean { const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0); return jokers.getDelta(5, cardCounts[0]) >= 5; }, function fourOfAKind( cardCounts: CardCounts, groupedHand: GroupedHand, withJokers = false, ): boolean { const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0); return jokers.getDelta(4, cardCounts[0]) >= 4; }, function fullHouse( cardCounts: CardCounts, groupedHand: GroupedHand, withJokers = false, ): boolean { const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0); return (jokers.getDelta(3, cardCounts[0]) === 3 && jokers.getDelta(2, cardCounts[1]) === 2); }, function threeOfAKind( cardCounts: CardCounts, groupedHand: GroupedHand, withJokers = false, ): boolean { const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0); return jokers.getDelta(3, cardCounts[0]) === 3 && jokers.getDelta(1, cardCounts[1]) === 1 && jokers.getDelta(1, cardCounts[2]) === 1; }, function twoPair( cardCounts: CardCounts, groupedHand: GroupedHand, withJokers = false, ): boolean { const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0); const numberOfPairs = cardCounts.filter((count) => count === 2).length; if (numberOfPairs === 2) return true; if (numberOfPairs === 1) return jokers.get(2) === 2; return jokers.get(2) === 2 && jokers.get(2) === 2; }, function onePair( cardCounts: CardCounts, groupedHand: GroupedHand, withJokers = false, ): boolean { const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0); const hasOnePair = cardCounts.filter((count) => count === 2).length === 1; if (hasOnePair) return true; return jokers.get(2) === 2; }, function highCard( cardCounts: CardCounts, groupedHand: GroupedHand, withJokers = false, ): boolean { const jokers = new JokerCounter(withJokers ? groupedHand.J || 0 : 0); const length = cardCounts.length; return jokers.getDelta(5, length) === 5; }, ]; const handTypeName = [ "fiveOfAKind", "fourOfAKind", "fullHouse", "threeOfAKind", "twoPair", "onePair", "highCard", ] as const; const compareHands = ( cards: CardList, ) => ( [, hand1, , handType1]: [Cards, Hand, number, number], [, hand2, , handType2]: [Cards, Hand, number, number], ): -1 | 0 | 1 => { if (handType1 < handType2) { return -1; } else if (handType1 > handType2) { return 1; } else { const hand1TotalValue = hand1.reduce( (s, [card]) => s + getCardValue(cards, card), 0, ); const hand2TotalValue = hand2.reduce( (s, [card]) => s + getCardValue(cards, card), 0, ); console.log( "hand1", handType1, hand1, hand1TotalValue, ); console.log( "hand2", handType2, hand2, hand2TotalValue, ); return hand1TotalValue > hand2TotalValue ? 1 : -1; } // for (let i = 0; i < 5; i += 1) { // console.log( // "hand1", // handType1, // hand1[i][0], // getCardValue(cardsSolution1, hand1[i][0]), // ); // console.log( // "hand2", // handType2, // hand2[i][0], // getCardValue(cardsSolution1, hand2[i][0]), // ); // if (hand1[i] > hand2[i]) return 1; // if (hand1[i] < hand2[i]) return -1; // } // return 0; // } }; const solvePart1 = (data: string): number => { return data .split("\n") .filter(Boolean) .map((line) => line.split(" ")) // Convert lines to hands and bids .map(( [hand, bid], ): [Cards, Hand, number] => { const [card1, card2, card3, card4, card5] = hand.toUpperCase().split("") .map(( card, ): [Card, number] => [ card as Card, getCardValue(cardsSolution1, card as Card), ]); return [ hand, [card1, card2, card3, card4, card5], Number(bid), ]; // Determine hand type per hand }).map(( [cards, hand, bid], ): [Cards, Hand, number, number] => [ cards, hand, bid, handTypes.length - (handTypes.findIndex((handType) => { return handType(countSameCards(groupHand(hand)), groupHand(hand)); }) + 1), ]) // Sort the hands by hand type and card value .sort(compareHands(cardsSolution1)).map(( result, index, ): [Cards, Hand, number, number, number, number] => [ ...result, index + 1, (index + 1) * result[2], ]) .reduce((s, [, , , , , v]) => s + v, 0); }; console.log("Sample:", solvePart1(sample)); // 6440 console.log("Input", solvePart1(input)); // 251927063 const solvePart2 = (data: string): number => { const r = data .split("\n") .filter(Boolean) .map((line) => line.split(" ")) // Convert lines to hands and bids .map(( [hand, bid], ): [Cards, Hand, number] => { const [card1, card2, card3, card4, card5] = hand.toUpperCase().split("") .map(( card, ): [Card, number] => [ card as Card, getCardValue(cardsSolution2, card as Card), ]); return [ hand, [card1, card2, card3, card4, card5], Number(bid), ]; // Determine hand type per hand }).map(( [cards, hand, bid], ): [Cards, Hand, number, number] => [ cards, hand, bid, handTypes.length - (handTypes.findIndex((handType) => { return handType( countSameCards(groupHand(hand), true), groupHand(hand), true, ); }) + 1), ]) // Sort the hands by hand type and card value .sort(compareHands(cardsSolution2)).map(( result, index, ): [Cards, Hand, number, number, number, number] => [ ...result, index + 1, (index + 1) * result[2], ]); console.log( "r", r, ); return r.reduce((s, [, , , , , v]) => s + v, 0); }; console.log("Sample:", solvePart2(sample)); // console.log("Input", solvePart2(input));