Files
advent-of-code-2022/2023/07/solution.ts
2024-07-29 00:09:03 +02:00

423 lines
8.6 KiB
TypeScript

const sample = await Deno.readTextFile("sample.txt");
const input = await Deno.readTextFile("input.txt");
abstract class HandType {
abstract get isMatch(): boolean;
abstract get name(): string;
abstract get weight(): number;
}
class FiveOfAKind extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(5);
}
get weight() {
return 1;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Five of a Kind";
}
}
class FourOfAKind extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(4, 1);
}
get weight() {
return 2;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Four of a Kind";
}
}
class FullHouse extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(3, 2);
}
get weight() {
return 3;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Full House";
}
}
class ThreeOfAKind extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(3, 1, 1);
}
get weight() {
return 4;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Three of a Kind";
}
}
class TwoPair extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(2, 2, 1);
}
get weight() {
return 5;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "Two Pair";
}
}
class OnePair extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(2, 1, 1, 1);
}
get weight() {
return 6;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "One Pair";
}
}
class HighCard extends HandType {
private _isMatch: boolean;
constructor(hand: Hand) {
super();
this._isMatch = hand.doesHaveSameCardCountIncludedJokers(1, 1, 1, 1, 1);
}
get weight() {
return 7;
}
get isMatch() {
return this._isMatch;
}
get name() {
return "High Card";
}
}
class Card {
private _label: string;
private _weight: number;
private _isJoker: boolean;
constructor(
label: string,
cardsWeights: Readonly<string[]>,
includeJokers: boolean,
) {
this._label = label;
this._weight = cardsWeights.indexOf(label) + 1;
this._isJoker = includeJokers && label === "J";
}
get label() {
return this._label;
}
get weight() {
return this._weight;
}
get isJoker() {
return this._isJoker;
}
}
class Hand {
private _card1: Card;
private _card2: Card;
private _card3: Card;
private _card4: Card;
private _card5: Card;
private _handTypes: HandType[];
private _handType: HandType | undefined;
private _groupedHand: string[][];
constructor(
public hand: string,
cardsWeights: Readonly<string[]>,
includeJokers: boolean,
) {
this._card1 = new Card(this.hand[0], cardsWeights, includeJokers);
this._card2 = new Card(this.hand[1], cardsWeights, includeJokers);
this._card3 = new Card(this.hand[2], cardsWeights, includeJokers);
this._card4 = new Card(this.hand[3], cardsWeights, includeJokers);
this._card5 = new Card(this.hand[4], cardsWeights, includeJokers);
this._groupedHand = this.cards.reduce((acc, card): string[][] => {
if (acc.flatMap((entry) => entry).includes(card.label)) {
return acc.map((entry): string[] =>
entry[0] === card.label ? [...entry, entry[0]] : entry
);
}
if (includeJokers && card.isJoker) return acc;
return [...acc, [card.label]];
}, [] as string[][]).sort((a, b) => b.length - a.length);
this._handTypes = [
new FiveOfAKind(this),
new FourOfAKind(this),
new FullHouse(this),
new ThreeOfAKind(this),
new TwoPair(this),
new OnePair(this),
new HighCard(this),
];
this._handType = this._handTypes
.filter((handType) => handType.isMatch)
.sort((a, b) => a.weight - b.weight)
.find((handType) => handType.isMatch);
}
get handType() {
return this._handType;
}
get cards() {
return [this._card1, this._card2, this._card3, this._card4, this._card5];
}
get jokerCount() {
return this.cards.filter((card) => card.isJoker).length;
}
get groupedHand() {
return this._groupedHand;
}
doesHaveSameCardCountIncludedJokers(...cardCounts: number[]): boolean {
const sum = cardCounts.reduce((acc, cardCount) => acc + cardCount, 0);
if (sum !== 5) throw new Error("Invalid card counts");
let jokerCount = this.jokerCount;
const groupedHand = this.groupedHand.slice();
for (const cardCount of cardCounts) {
for (const group of groupedHand) {
if (group.length === cardCount) {
groupedHand.splice(groupedHand.indexOf(group), 1);
cardCounts.splice(cardCounts.indexOf(cardCount), 1);
}
}
}
for (const cardCount of cardCounts) {
for (const group of groupedHand) {
if (group.length <= cardCount) {
const d = cardCount - group.length;
if (d <= jokerCount) {
jokerCount -= d;
groupedHand.splice(groupedHand.indexOf(group), 1);
cardCounts.splice(cardCounts.indexOf(cardCount), 1);
}
}
}
}
if (cardCounts.length === groupedHand.flatMap((entry) => entry).length) {
return true;
}
if (cardCounts.length === 0 && groupedHand.length === 0) return true;
if (jokerCount === 0) return false;
const remainingSum = cardCounts.reduce(
(acc, cardCount) => acc + cardCount,
0,
);
if (remainingSum === jokerCount) {
return true;
}
return false;
}
get card1() {
return this._card1;
}
get card2() {
return this._card2;
}
get card3() {
return this._card3;
}
get card4() {
return this._card4;
}
get card5() {
return this._card5;
}
}
class Line {
private _hand: Hand;
private _bid: number;
constructor(
public line: string,
cardsWeights: Readonly<string[]>,
includeJokers: boolean,
) {
this._hand = new Hand(this.line.split(" ")[0], cardsWeights, includeJokers);
this._bid = Number(this.line.split(" ")[1]);
}
get hand() {
return this._hand;
}
get bid() {
return this._bid;
}
}
class Game {
private _lines: Line[];
constructor(
public input: string,
cardsWeights: Readonly<string[]>,
includeJokers: boolean = false,
) {
this._lines = input.split("\n").filter(Boolean).map((line) =>
new Line(line, cardsWeights, includeJokers)
);
}
get lines() {
return this._lines;
}
}
const solvePart1 = (data: string): number => {
const cardsWeights = [
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"T",
"J",
"Q",
"K",
"A",
] as const;
const game = new Game(data, cardsWeights);
return game.lines.sort((a, b) => {
const weightA = a.hand.handType?.weight || 0;
const weightB = b.hand.handType?.weight || 0;
if (weightA > weightB) return -1;
if (weightA < weightB) return 1;
for (let i = 0; i < 5; i += 1) {
const valueA = a.hand.cards[i].weight;
const valueB = b.hand.cards[i].weight;
if (valueA > valueB) return 1;
if (valueA < valueB) return -1;
}
return 0;
})
.reduce((sum, line, index) => {
return sum + ((index + 1) * line.bid);
}, 0);
};
console.log("Sample:", solvePart1(sample)); // 6440
console.log("Input", solvePart1(input)); // 251927063
const solvePart2 = (data: string): number => {
const cardsWeights = [
"J",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"T",
"Q",
"K",
"A",
] as const;
const game = new Game(data, cardsWeights, true);
return game.lines.sort((a, b) => {
const weightA = a.hand.handType?.weight || 0;
const weightB = b.hand.handType?.weight || 0;
if (weightA > weightB) return -1;
if (weightA < weightB) return 1;
for (let i = 0; i < 5; i += 1) {
const valueA = a.hand.cards[i].weight;
const valueB = b.hand.cards[i].weight;
if (valueA > valueB) return 1;
if (valueA < valueB) return -1;
}
return 0;
})
.reduce((sum, line, index) => {
return sum + ((index + 1) * line.bid);
}, 0);
};
console.log("Sample:", solvePart2(sample)); // 5905
console.log("Input", solvePart2(input)); // 255632664