Skip to content

Commit e6e3850

Browse files
committed
feat(day07): part 1
1 parent 42e5c1d commit e6e3850

File tree

2 files changed

+1201
-0
lines changed

2 files changed

+1201
-0
lines changed

day07_test.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { assertEquals } from "https://deno.land/std@0.208.0/assert/assert_equals.ts";
2+
3+
const example = [
4+
`32T3K 765`,
5+
`T55J5 684`,
6+
`KK677 28`,
7+
`QQQJA 483`,
8+
`KTJJT 220`,
9+
];
10+
11+
Deno.test("Day 7: Camel Cards", async (t) => {
12+
await t.step("Example", async (t) => {
13+
await t.step("it should solve the example", () =>
14+
assertEquals(totalWinnings(example), 6440)
15+
);
16+
});
17+
18+
await t.step("Solution", async (t) => {
19+
await t.step("it should solve", async (t) =>
20+
assertEquals(
21+
totalWinnings(
22+
(await Deno.readTextFile("./input/day07.txt")).split("\n")
23+
),
24+
251545216
25+
)
26+
);
27+
});
28+
29+
await t.step("handType()", async (t) => {
30+
for (const [hand, expectedType] of [
31+
["AAAAA", HandType.FiveOfAKind],
32+
["AA8AA", HandType.FourOfAKind],
33+
["23332", HandType.FullHouse],
34+
["TTT98", HandType.ThreeOfAKind],
35+
["23432", HandType.TwoPair],
36+
["A23A4", HandType.OnePair],
37+
["23456", HandType.HighCard],
38+
[`32T3K`, HandType.OnePair],
39+
[`KTJJT`, HandType.TwoPair],
40+
[`KK677`, HandType.TwoPair],
41+
[`T55J5`, HandType.ThreeOfAKind],
42+
[`QQQJA`, HandType.ThreeOfAKind],
43+
] as Array<[string, HandType]>) {
44+
await t.step(`${hand} should be type ${expectedType}`, () =>
45+
assertEquals(getHandType(hand), expectedType)
46+
);
47+
}
48+
});
49+
50+
await t.step("compare()", async (t) => {
51+
await t.step(
52+
`33332 and 2AAAA are both four of a kind hands, but 33332 is stronger because its first card is stronger`,
53+
() => assertEquals(compareByCards("33332", "2AAAA"), 1)
54+
);
55+
await t.step(
56+
`77888 and 77788 are both a full house, but 77888 is stronger because its third card is stronger (and both hands have the same first and second card)`,
57+
() => assertEquals(compareByCards("77788", "77888"), -1)
58+
);
59+
await t.step(
60+
`T55J5 and QQQJA are both three of a kind. QQQJA has a stronger first card`,
61+
() => assertEquals(compareByCards("QQQJA", "T55J5"), 1)
62+
);
63+
await t.step(
64+
`KK677 and KTJJT are both two pair. Their first cards both have the same label, but the second card of KK677 is stronger (K vs T)`,
65+
() => assertEquals(compareByCards("KTJJT", "KK677"), -1)
66+
);
67+
});
68+
69+
await t.step("rank()", () =>
70+
assertEquals(example.sort(byRankAndCards), [
71+
// 32T3K is the only one pair and the other hands are all a stronger type, so it gets rank 1.
72+
`32T3K 765`,
73+
// KK677 and KTJJT are both two pair. Their first cards both have the same label, but the second card of KK677 is stronger (K vs T), so KTJJT gets rank 2 and KK677 gets rank 3.
74+
`KTJJT 220`,
75+
`KK677 28`,
76+
// T55J5 and QQQJA are both three of a kind. QQQJA has a stronger first card, so it gets rank 5 and T55J5 gets rank 4.
77+
`T55J5 684`,
78+
`QQQJA 483`,
79+
])
80+
);
81+
});
82+
83+
const totalWinnings = (hands: string[]): number => {
84+
const rankedHands = hands.sort(byRankAndCards);
85+
return hands
86+
.map(parseHandBids)
87+
.reduce(
88+
(total, [hand, bidAmount]) =>
89+
total + (rankedHands.indexOf(`${hand} ${bidAmount}`) + 1) * bidAmount,
90+
0
91+
);
92+
};
93+
94+
const parseHandBids = (handBid: string): [string, number] => {
95+
const [hand, bid] = handBid.split(" ");
96+
return [hand, parseInt(bid, 10)];
97+
};
98+
99+
const cardStrength = [
100+
"A",
101+
"K",
102+
"Q",
103+
"J",
104+
"T",
105+
"9",
106+
"8",
107+
"7",
108+
"6",
109+
"5",
110+
"4",
111+
"3",
112+
"2",
113+
];
114+
115+
const byRankAndCards = (handBid1: string, handBid2: string) => {
116+
const [hand1] = parseHandBids(handBid1);
117+
const [hand2] = parseHandBids(handBid2);
118+
const handType1 = getHandType(hand1);
119+
const handType2 = getHandType(hand2);
120+
if (handType1 < handType2) return 1;
121+
if (handType1 > handType2) return -1;
122+
return compareByCards(hand1, hand2);
123+
};
124+
125+
/**
126+
* Every hand is exactly one type. From strongest to weakest, they are:
127+
*/
128+
enum HandType {
129+
// Five of a kind, where all five cards have the same label: AAAAA
130+
FiveOfAKind = 0,
131+
// Four of a kind, where four cards have the same label and one card has a different label: AA8AA
132+
FourOfAKind = 1,
133+
// Full house, where three cards have the same label, and the remaining two cards share a different label: 23332
134+
FullHouse = 2,
135+
// Three of a kind, where three cards have the same label, and the remaining two cards are each different from any other card in the hand: TTT98
136+
ThreeOfAKind = 3,
137+
// Two pair, where two cards share one label, two other cards share a second label, and the remaining card has a third label: 23432
138+
TwoPair = 4,
139+
// One pair, where two cards share one label, and the other three cards have a different label from the pair and each other: A23A4
140+
OnePair = 5,
141+
// High card, where all cards' labels are distinct: 23456
142+
HighCard = 6,
143+
}
144+
145+
const getHandType = (hand: string): HandType => {
146+
if (isFiveOfAKind(hand)) return HandType.FiveOfAKind;
147+
if (isFourOfAKind(hand)) return HandType.FourOfAKind;
148+
if (isFullHouse(hand)) return HandType.FullHouse;
149+
if (isThreeOfAKind(hand)) return HandType.ThreeOfAKind;
150+
if (isTwoPair(hand)) return HandType.TwoPair;
151+
if (isOnePair(hand)) return HandType.OnePair;
152+
return HandType.HighCard;
153+
};
154+
155+
const setSize = (hand: string): number => new Set(hand).size;
156+
const isNOfAKind = (n: number) => (hand: string) => new Set(hand).size === n;
157+
const isFiveOfAKind = isNOfAKind(1);
158+
const isFourOfAKind = (hand: string): boolean => {
159+
const s = new Set(hand);
160+
if (s.size !== 2) return false;
161+
const [a, b] = s.values();
162+
const count = countCards(hand);
163+
return count(a) === 4 || count(b) === 4;
164+
};
165+
const isThreeOfAKind = (hand: string): boolean => {
166+
if (setSize(hand) !== 3) return false;
167+
return cardsInSets(hand)[0] === 3;
168+
};
169+
const isFullHouse = isNOfAKind(2);
170+
const isTwoPair = (hand: string): boolean => {
171+
if (setSize(hand) !== 3) return false;
172+
const counts = cardsInSets(hand);
173+
return counts[0] === 2 && counts[1] === 2;
174+
};
175+
const isOnePair = isNOfAKind(4);
176+
177+
const countCards = (hand: string) => (card: string) =>
178+
hand.split("").filter((c) => c === card).length;
179+
180+
const desc = (a: number, b: number): number => b - a;
181+
182+
const cardsInSets = (hand: string): number[] => {
183+
const count = countCards(hand);
184+
return [...new Set(hand).values()].map(count).sort(desc);
185+
};
186+
187+
const compareByCards = (hand1: string, hand2: string): number => {
188+
for (let i = 0; i < hand1.length; i++) {
189+
const c1 = hand1[i];
190+
const c2 = hand2[i];
191+
if (c1 === c2) continue;
192+
return compareCard(c1, c2);
193+
}
194+
return 0;
195+
};
196+
197+
const compareCard = (card1: string, card2: string): number => {
198+
if (cardStrength.indexOf(card1) < cardStrength.indexOf(card2)) return 1;
199+
if (cardStrength.indexOf(card1) > cardStrength.indexOf(card2)) return -1;
200+
return 0;
201+
};

0 commit comments

Comments
 (0)