|
@@ -0,0 +1,170 @@
|
|
|
+import { LoadInput } from "../common.ts";
|
|
|
+import { type CamelCardsHand, CamelCardRanking } from "./7_common.ts";
|
|
|
+
|
|
|
+const tests = [
|
|
|
+ "32T3K 765",
|
|
|
+ "T55J5 684",
|
|
|
+ "KK677 28",
|
|
|
+ "KTJJT 220",
|
|
|
+ "QQQJA 483",
|
|
|
+];
|
|
|
+
|
|
|
+const tests_reddit = [
|
|
|
+ "2345A 1",
|
|
|
+ "Q2KJJ 13",
|
|
|
+ "Q2Q2Q 19",
|
|
|
+ "T3T3J 17",
|
|
|
+ "T3Q33 11",
|
|
|
+ "2345J 3",
|
|
|
+ "J345A 2",
|
|
|
+ "32T3K 5",
|
|
|
+ "T55J5 29",
|
|
|
+ "KK677 7",
|
|
|
+ "KTJJT 34",
|
|
|
+ "QQQJA 31",
|
|
|
+ "JJJJJ 37",
|
|
|
+ "JAAAA 43",
|
|
|
+ "AAAAJ 59",
|
|
|
+ "AAAAA 61",
|
|
|
+ "2AAAA 23",
|
|
|
+ "2JJJJ 53",
|
|
|
+ "JJJJ2 41",
|
|
|
+];
|
|
|
+
|
|
|
+const input = await LoadInput(7);
|
|
|
+
|
|
|
+// Parse the input
|
|
|
+const parsedHands = ParseInput(input);
|
|
|
+// Sort the cards
|
|
|
+const sortedHands = parsedHands.sort(SortHands);
|
|
|
+
|
|
|
+// Find the input
|
|
|
+let totalWinnings = 0;
|
|
|
+sortedHands.forEach((hand, idx) => {
|
|
|
+ totalWinnings += hand.Bid * (idx + 1);
|
|
|
+});
|
|
|
+
|
|
|
+console.log(`The total winnings are $${totalWinnings}`);
|
|
|
+
|
|
|
+/**
|
|
|
+ * Parse the input
|
|
|
+ *
|
|
|
+ * @param {string[]} handList An array of strings represnting hands of Camel Cards and their bids
|
|
|
+ * @returns {CamelCardsHand[]} An array of parsed Camel Card hands
|
|
|
+ */
|
|
|
+function ParseInput(handList: string[]): CamelCardsHand[] {
|
|
|
+ const output: CamelCardsHand[] = [];
|
|
|
+
|
|
|
+ handList.forEach((hand) => {
|
|
|
+ const [handStr, bidStr] = hand.split(" ");
|
|
|
+ output.push({
|
|
|
+ Hand: handStr,
|
|
|
+ Bid: Number(bidStr),
|
|
|
+ Rank: FindCamelCardRank(handStr),
|
|
|
+ });
|
|
|
+ })
|
|
|
+
|
|
|
+ return output;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Assign a base rank to a hand of Camel Cards
|
|
|
+ *
|
|
|
+ * Assigns a Camel Card Ranking to a hand of cards based on the
|
|
|
+ * strength of the hand on its own.
|
|
|
+ *
|
|
|
+ * @param {string} hand The hand a player drew
|
|
|
+ * @returns {CamelCardRanking} The ranking of the hand
|
|
|
+ */
|
|
|
+function FindCamelCardRank(hand: string): CamelCardRanking {
|
|
|
+ const cardFacesSorted = ReplaceJokers(hand).split("").sort().join("");
|
|
|
+
|
|
|
+ const fiveOfKind = new RegExp("(.)\\1{4}", "g");
|
|
|
+ const fourOfKind = new RegExp("(.)\\1{3}", "g");
|
|
|
+ const threeOfKind = new RegExp("(.)\\1{2}", "g");
|
|
|
+ const twoOfKind = new RegExp("(.)\\1{1}", "g");
|
|
|
+ // Can't get these two to work in one regex, so I'll split them for now
|
|
|
+ const fullHouse_A = new RegExp("(.)\\1(.)\\2{2}", "g");
|
|
|
+ const fullHouse_B = new RegExp("(.)\\1{2}(.)\\2", "g");
|
|
|
+
|
|
|
+ if (cardFacesSorted.replace(twoOfKind, "").length == 5) {
|
|
|
+ return CamelCardRanking.HIGH_CARD;
|
|
|
+ }
|
|
|
+ else if(cardFacesSorted.replace(fiveOfKind, "").length == 0) {
|
|
|
+ return CamelCardRanking.FIVE_OF_A_KIND;
|
|
|
+ }
|
|
|
+ else if(cardFacesSorted.replace(fourOfKind, "").length == 1) {
|
|
|
+ return CamelCardRanking.FOUR_OF_A_KIND;
|
|
|
+ }
|
|
|
+ else if(cardFacesSorted.replace(fullHouse_A, "").length == 0
|
|
|
+ || cardFacesSorted.replace(fullHouse_B, "").length == 0) {
|
|
|
+ return CamelCardRanking.FULL_HOUSE;
|
|
|
+ }
|
|
|
+ else if(cardFacesSorted.replace(threeOfKind, "").length == 2) {
|
|
|
+ return CamelCardRanking.THREE_OF_A_KIND;
|
|
|
+ }
|
|
|
+ else if(cardFacesSorted.replace(twoOfKind, "").length == 1) {
|
|
|
+ return CamelCardRanking.TWO_PAIR;
|
|
|
+ }
|
|
|
+ else if(cardFacesSorted.replace(twoOfKind, "").length == 3) {
|
|
|
+ return CamelCardRanking.ONE_PAIR;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ throw new Error(`Unsure how we reached this condition!\nSorted hand was: ${cardFacesSorted}`);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Compare two hands of Camel Cards for sorting
|
|
|
+ */
|
|
|
+function SortHands(a: CamelCardsHand, b: CamelCardsHand): number {
|
|
|
+ if (a.Rank < b.Rank) { return -1; }
|
|
|
+ if (a.Rank > b.Rank) { return 1; }
|
|
|
+
|
|
|
+ // Replace the letters with something easier to compare
|
|
|
+ const aHand = a.Hand.replaceAll("A", "Z").replaceAll("K", "Y").replaceAll("Q", "X").replaceAll("J", "0").replaceAll("T", "V");
|
|
|
+ const bHand = b.Hand.replaceAll("A", "Z").replaceAll("K", "Y").replaceAll("Q", "X").replaceAll("J", "0").replaceAll("T", "V");
|
|
|
+
|
|
|
+ for (let i = 0; i < 5; i++) {
|
|
|
+ if (aHand[i] < bHand[i]) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (aHand[i] > bHand[i]) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+function ReplaceJokers(hand: string): string {
|
|
|
+ // Make sure the hand has a joker card, but is not only joker cards, before attempting the replacement
|
|
|
+ if (!/J/.test(hand) && hand != "JJJJJ") { return hand; }
|
|
|
+
|
|
|
+ /** Track how many jokers we need to add back to the hand */
|
|
|
+ const jokerCount = hand.replace(/[^J]/g, "").length;
|
|
|
+ /** An array containing the cards in the hand less any jokers */
|
|
|
+ const handFaces = hand.replaceAll("J", "").split("");
|
|
|
+
|
|
|
+ /** An object for counting the amounts of each face in the hand */
|
|
|
+ const faceMatches: { [face: string]: number; } = {"": -1};
|
|
|
+
|
|
|
+ /* Count the amount of each face in the string */
|
|
|
+ handFaces.forEach((face) => {
|
|
|
+ if (faceMatches[face] == undefined) {
|
|
|
+ faceMatches[face] = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ faceMatches[face]++;
|
|
|
+ });
|
|
|
+
|
|
|
+ /* Find the most occurring face in the hand */
|
|
|
+ let highestCard:string = "";
|
|
|
+ for (const face in faceMatches) {
|
|
|
+ if (faceMatches[face] > faceMatches[highestCard]) {
|
|
|
+ highestCard = face;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return handFaces.join("") + highestCard.repeat(jokerCount);
|
|
|
+}
|