diff --git a/index.ts b/index.ts index 429e569..567351e 100644 --- a/index.ts +++ b/index.ts @@ -1,2 +1,3 @@ -import {runDaySix} from "./src/day_six"; -runDaySix() \ No newline at end of file +import {runDaySeven} from "./src/day_seven"; + +runDaySeven(); \ No newline at end of file diff --git a/src/day_seven.test.ts b/src/day_seven.test.ts new file mode 100644 index 0000000..2a6b0fd --- /dev/null +++ b/src/day_seven.test.ts @@ -0,0 +1,37 @@ +import {CamelCard, CamelCards} from "./day_seven"; + +describe('Day Seven', () => { + const input = `32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483`; + + it.each([ + ['AAAAA', 7], // Five of a kind + ['AA8AA', 6], // Four of a kind + ['AABBB', 5], // Full house + ['T55C5', 4], // Three of a kind + ['KK677', 3], // Two pairs + ['32T3K', 2], // One pair + ['12345', 1], // High card + + ['T55J5', 6], // Four of a kind with Joker + ['KTJJT', 6], + ['QQQJA', 6] + ])('should should correctly score %s as %s', (card, score) => { + const camelCard = new CamelCard(card); + expect(camelCard.score).toEqual(score); + }); + + it('should correctly compare two clashing cards', () => { + const a = new CamelCard('AA2CC'); + const b = new CamelCard('AATCC'); + expect(a.compare(b)).toBeLessThan(0); + }); + + it('should parse the input and calculate the total winnings', () => { + const camelCards = new CamelCards(input); + expect(camelCards.winnings).toEqual(5905); + }) +}); \ No newline at end of file diff --git a/src/day_seven.ts b/src/day_seven.ts new file mode 100644 index 0000000..3b5ece5 --- /dev/null +++ b/src/day_seven.ts @@ -0,0 +1,78 @@ +import {isEqual, zip} from "lodash"; +import {anyChar, int, newline, rest, space, whitespace} from "parjs"; +import {stringify, manyBetween, between, then, manySepBy, manyTill} from "parjs/combinators"; +import fs from "fs"; + +const CardLetterScores = ['J', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'Q', 'K', 'A']; + +export class CamelCard { + private readonly hand: Record; + public readonly score: number; + + constructor(protected readonly card: string) { + this.hand = this.card.split('').reduce((cards, char) => { + if (!cards[char]) { + cards[char] = 0; + } + + cards[char] += 1; + return cards; + }, {} as Record) + + this.score = this.calculateScore(); + } + + public compare(other: CamelCard): number { + const diff = this.score - other.score; + if (diff !== 0) return diff; + + for (const [a, b] of zip(this.card.split(''), other.card.split('')) as [string, string][]) { + if (a !== b) { + return CardLetterScores.indexOf(a) - CardLetterScores.indexOf(b) + } + } + + return 0; + } + + private calculateScore(): number { + const cards = Object.values(this.hand).sort((a, b) => b-a); + + if (isEqual(cards, [1, 1, 1, 1, 1])) return 1; + if (isEqual(cards, [2, 1, 1, 1])) return 2; + if (isEqual(cards, [2, 2, 1])) return 3; + if (isEqual(cards, [3, 1, 1])) return 4; + if (isEqual(cards, [3, 2])) return 5; + if (isEqual(cards, [4, 1])) return 6; + if (isEqual(cards, [5])) return 7; + + return 0; + } +} + +const cardParser = anyChar().pipe(manyTill(space()), stringify()); +const bidParser = int().pipe(between(whitespace())); +const parser = cardParser.pipe(then(bidParser), manySepBy(whitespace())); + +export class CamelCards { + private cards: [CamelCard, number][] = []; + + constructor(input: string) { + const pairs = parser.parse(input).value; + + this.cards = pairs.map(([cards, bid]) => [new CamelCard(cards), bid]); + } + + get winnings(): number { + this.cards.sort(([a], [b]) => a.compare(b)); + + return this.cards.reduce((total, [_, value], index) => total + (value * (index + 1)), 0); + } +} + +export const runDaySeven = () => { + const input = fs.readFileSync('./inputs/day_seven_input.txt', 'utf-8').trimEnd(); + + const cards = new CamelCards(input); + console.log(cards.winnings); +} \ No newline at end of file