423 lines
8.6 KiB
TypeScript
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
|