1
+ import { Aggregates , AocDay , AocDayDecorator , SolutionParts } from "../aoc" ;
2
+
3
+ @AocDayDecorator ( '2023' , '7' )
4
+ export class Day07 extends AocDay {
5
+
6
+ solveImpl ( ) : SolutionParts {
7
+ const CardSymbols = [ "A" , "K" , "Q" , "J" , "T" , "9" , "8" , "7" , "6" , "5" , "4" , "3" , "2" ] as const ;
8
+ // eslint-disable-next-line @typescript-eslint/no-redeclare
9
+ type CardSymbols = typeof CardSymbols [ number ] ;
10
+
11
+ type CardGameRound = { hand : CardSymbols [ ] , bid : number } ;
12
+
13
+ const cardGameRounds : CardGameRound [ ] = this . input . split ( "\n" ) . map ( r => r . trim ( ) ) . map ( r => {
14
+ const [ handStr , bidStr ] = r . split ( " " ) ;
15
+ return { hand : handStr . trim ( ) . split ( "" ) as CardSymbols [ ] , bid : parseInt ( bidStr . trim ( ) , 10 ) } ;
16
+ } ) ;
17
+
18
+ const getCardAmount = ( round : CardGameRound ) => round . hand . reduce < Record < string , number > > ( ( acc , cur ) => {
19
+ acc [ cur ] = ( acc [ cur ] ?? 0 ) + 1 ;
20
+ return acc ;
21
+ } , { } ) ;
22
+
23
+ const getHandTypeRank = ( round : CardGameRound ) : number => {
24
+
25
+ const hasDistinctValues = ( amount : number ) => Object . keys ( getCardAmount ( round ) ) . length === amount ;
26
+ const hasNofAKind = ( amount : number ) => Object . values ( getCardAmount ( round ) ) . find ( ( v ) => v === amount ) !== undefined ;
27
+
28
+ const typeRules : Record < string , ( ) => boolean > = {
29
+ isFiveOfAKind : ( ) => hasDistinctValues ( 1 ) ,
30
+ isFourOfAKind : ( ) => hasDistinctValues ( 2 ) && hasNofAKind ( 4 ) ,
31
+ isFullHouse : ( ) => hasDistinctValues ( 2 ) && hasNofAKind ( 3 ) ,
32
+ isThreeOfAKind : ( ) => hasDistinctValues ( 3 ) && hasNofAKind ( 3 ) ,
33
+ isTwoPair : ( ) => hasDistinctValues ( 3 ) && hasNofAKind ( 2 ) ,
34
+ isOnePair : ( ) => hasDistinctValues ( 4 ) && hasNofAKind ( 2 ) ,
35
+ isHighCard : ( ) => hasDistinctValues ( 5 )
36
+ } ;
37
+
38
+ return Object . keys ( typeRules ) . length - Object . values ( typeRules ) . findIndex ( ( typeRuleFn ) => typeRuleFn ( ) ) - 1 ;
39
+ } ;
40
+
41
+ const getBesPossibleRoundWithJokers = ( round : CardGameRound ) : CardGameRound => {
42
+ let best = {
43
+ round,
44
+ roundType : getHandTypeRank ( round )
45
+ } ;
46
+ for ( const cardSymbol of CardSymbols . filter ( s => s !== "J" ) ) {
47
+ const curRound : CardGameRound = {
48
+ bid : round . bid ,
49
+ hand : round . hand . map ( c => c === "J" ? cardSymbol : c )
50
+ } ;
51
+ const curRoundType = getHandTypeRank ( curRound ) ;
52
+ if ( best . roundType < curRoundType ) {
53
+ best = {
54
+ round : curRound ,
55
+ roundType : curRoundType
56
+ } ;
57
+ }
58
+ }
59
+
60
+ return best . round ;
61
+ } ;
62
+
63
+ function createHandSorter ( singleCardRankHighestToLowest : CardSymbols [ ] , withJokerUsage : boolean ) : ( a : CardGameRound , b : CardGameRound ) => number {
64
+ const singleCardRank = Object . fromEntries ( singleCardRankHighestToLowest . map ( ( v , idx , arr ) => [ v , arr . length - idx - 1 ] ) ) ;
65
+ const byHighestCard = ( a : CardGameRound , b : CardGameRound ) : number => a . hand . map ( ( v , idx ) => singleCardRank [ b . hand [ idx ] ] - singleCardRank [ a . hand [ idx ] ] ) . find ( v => v !== 0 ) ?? 0 ;
66
+ const byHandyTypeRank = ( a : CardGameRound , b : CardGameRound ) : number => {
67
+ if ( withJokerUsage ) {
68
+ return getHandTypeRank ( getBesPossibleRoundWithJokers ( b ) ) - getHandTypeRank ( getBesPossibleRoundWithJokers ( a ) ) ;
69
+ }
70
+ return getHandTypeRank ( b ) - getHandTypeRank ( a ) ;
71
+ } ;
72
+
73
+ return ( a : CardGameRound , b : CardGameRound ) => {
74
+ const typeDiff = byHandyTypeRank ( a , b ) ;
75
+ return typeDiff === 0 ? byHighestCard ( a , b ) : typeDiff ;
76
+ } ;
77
+ }
78
+
79
+ const calculateTotalWinnings = ( rounds : CardGameRound [ ] , singleCardRankHighestToLowest : CardSymbols [ ] , withJokerUsage : boolean ) => {
80
+ const byHandOrder = createHandSorter ( singleCardRankHighestToLowest , withJokerUsage ) ;
81
+ return rounds
82
+ . sort ( byHandOrder )
83
+ . map ( ( game , idx , acc ) => game . bid * ( acc . length - idx ) )
84
+ . reduce ( Aggregates . sum ) ;
85
+ } ;
86
+
87
+ const part1 = ( ) => calculateTotalWinnings ( cardGameRounds , [ "A" , "K" , "Q" , "J" , "T" , "9" , "8" , "7" , "6" , "5" , "4" , "3" , "2" ] , false ) ;
88
+ const part2 = ( ) => calculateTotalWinnings ( cardGameRounds , [ "A" , "K" , "Q" , "T" , "9" , "8" , "7" , "6" , "5" , "4" , "3" , "2" , "J" ] , true ) ;
89
+
90
+ return {
91
+ part1 : part1 ( ) ,
92
+ part2 : part2 ( )
93
+ } ;
94
+ }
95
+ }
0 commit comments