Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions scripts/aoc2015/day21/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Hit Points: 109
Damage: 8
Armor: 2
129 changes: 129 additions & 0 deletions scripts/aoc2015/day21/puzzle1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
--- Day 21: RPG Simulator 20XX ---
Little Henry Case got a new video game for Christmas.
It's an RPG, and he's stuck on a boss.
He needs to know what equipment to buy at the shop. He hands you the controller.

In this game, the player (you) and the enemy (the boss) take turns attacking.
The player always goes first.
Each attack reduces the opponent's hit points by at least 1.
The first character at or below 0 hit points loses.

Damage dealt by an attacker each turn is equal to the attacker's damage score minus the defender's armor score.
An attacker always does at least 1 damage.
So, if the attacker has a damage score of 8, and the defender has an armor score of 3, the defender loses 5 hit points.
If the defender had an armor score of 300, the defender would still lose 1 hit point.

Your damage score and armor score both start at zero.
They can be increased by buying items in exchange for gold.
You start with no items and have as much gold as you need.
Your total damage or armor is equal to the sum of those stats from all of your items.
You have 100 hit points.

Here is what the item shop is selling:

Weapons: Cost Damage Armor
Dagger 8 4 0
Shortsword 10 5 0
Warhammer 25 6 0
Longsword 40 7 0
Greataxe 74 8 0

Armor: Cost Damage Armor
Leather 13 0 1
Chainmail 31 0 2
Splintmail 53 0 3
Bandedmail 75 0 4
Platemail 102 0 5

Rings: Cost Damage Armor
Damage +1 25 1 0
Damage +2 50 2 0
Damage +3 100 3 0
Defense +1 20 0 1
Defense +2 40 0 2
Defense +3 80 0 3

You must buy exactly one weapon; no dual-wielding.
Armor is optional, but you can't use more than one.
You can buy 0-2 rings (at most one for each hand).
You must use any items you buy. The shop only has one of each item, so you can't buy, for example, two rings of Damage +3.

For example, suppose you have 8 hit points, 5 damage, and 5 armor, and that the boss has 12 hit points, 7 damage, and 2 armor:

The player deals 5-2 = 3 damage; the boss goes down to 9 hit points.
The boss deals 7-5 = 2 damage; the player goes down to 6 hit points.
The player deals 5-2 = 3 damage; the boss goes down to 6 hit points.
The boss deals 7-5 = 2 damage; the player goes down to 4 hit points.
The player deals 5-2 = 3 damage; the boss goes down to 3 hit points.
The boss deals 7-5 = 2 damage; the player goes down to 2 hit points.
The player deals 5-2 = 3 damage; the boss goes down to 0 hit points.
In this scenario, the player wins! (Barely.)

You have 100 hit points. The boss's actual stats are in your puzzle input. What is the least amount of gold you can spend and still win the fight?
*/

// Algorithm

// create your basic character
import { Character, modifyCharWithEquip, simulateFight } from "./utils.ts";

const basicPlayer: Character = {
hitPoints: 100,
damage: 0,
defense: 0,
};

// create boss character (read file)
const boss: Character = {
hitPoints: 109,
damage: 8,
defense: 2,
};

// create list of items in shop

// create board with prices
const board: { character: Character; goldSpent: number }[] = [];

// loop
// create variations of armor (make sure to safe it's complete price)
// modify your basic character
for (const playerWithEquip of modifyCharWithEquip(basicPlayer)) {
// simulate fight
const playerWins = simulateFight(
playerWithEquip.character,
Object.assign({}, boss),
);
// if you win, store price into an board
if (playerWins) {
board.push(playerWithEquip);
}
}
console.log("min price ", Math.min(...board.map(({ goldSpent }) => goldSpent)));
console.log("winner ", board.find(({ goldSpent }) => goldSpent === 111));
// find min price

// find max price and loose

const looseBoard: { character: Character; goldSpent: number }[] = [];

for (const playerWithEquip of modifyCharWithEquip(basicPlayer)) {
// simulate fight
const playerWins = simulateFight(
playerWithEquip.character,
Object.assign({}, boss),
);
// if you win, store price into an looseBoard
if (!playerWins) {
looseBoard.push(playerWithEquip);
}
}
console.log(
"Max price ",
Math.max(...looseBoard.map(({ goldSpent }) => goldSpent)),
);
console.log(
"looser with most expensive armor ",
looseBoard.find(({ goldSpent }) => goldSpent === 188),
);
130 changes: 130 additions & 0 deletions scripts/aoc2015/day21/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { assert, assertEquals, assertThrows } from "@std/assert";
import { beforeEach, describe, it } from "@std/testing/bdd";
import {
allArmors,
allRings,
allWeapons,
charAAttacksCharB,
Character,
equipCharacter,
generateRings,
simulateFight,
} from "./utils.ts";

describe("equipCharacter", function () {
it("should equip character with some items", function () {
const character: Character = {
hitPoints: 100,
damage: 0,
defense: 0,
};
const armor = allArmors[0];
const weapon = allWeapons[0];
const ring = allRings[0];
assertEquals(
equipCharacter(character, {
armor,
weapon,
ringsLeft: ring,
}),
{
character: {
...character,
defense: armor.defense + weapon.defense + ring.defense,
damage: armor.damage + weapon.damage + ring.damage,
},
goldSpent: armor.cost + weapon.cost + ring.cost,
},
);
});
});

describe("generateRings", function () {
it("should generate all variations for rings", function () {
assertEquals([...generateRings()], [
[undefined, undefined],
[allRings[0], undefined],
[allRings[1], undefined],
[allRings[2], undefined],
[allRings[3], undefined],
[allRings[4], undefined],
[allRings[5], undefined],
[allRings[0], allRings[1]],
[allRings[0], allRings[2]],
[allRings[0], allRings[3]],
[allRings[0], allRings[4]],
[allRings[0], allRings[5]],
[allRings[1], allRings[0]],
[allRings[1], allRings[2]],
[allRings[1], allRings[3]],
[allRings[1], allRings[4]],
[allRings[1], allRings[5]],
[allRings[2], allRings[0]],
[allRings[2], allRings[1]],
[allRings[2], allRings[3]],
[allRings[2], allRings[4]],
[allRings[2], allRings[5]],
[allRings[3], allRings[0]],
[allRings[3], allRings[1]],
[allRings[3], allRings[2]],
[allRings[3], allRings[4]],
[allRings[3], allRings[5]],
[allRings[4], allRings[0]],
[allRings[4], allRings[1]],
[allRings[4], allRings[2]],
[allRings[4], allRings[3]],
[allRings[4], allRings[5]],
[allRings[5], allRings[0]],
[allRings[5], allRings[1]],
[allRings[5], allRings[2]],
[allRings[5], allRings[3]],
[allRings[5], allRings[4]],
]);
});
});

describe("charAAttacksCharB", function () {
const charA: Character = { hitPoints: 100, damage: 10, defense: 20 };
const charB: Character = { hitPoints: 200, damage: 10, defense: 3 };

it("should execute charA attack on charB", function () {
const charAClone = Object.assign({}, charA);
const charBClone = Object.assign({}, charB);
charAAttacksCharB(charAClone, charBClone);
assertEquals(charAClone, charA);
assertEquals(charBClone, {
...charB,
hitPoints: charB.hitPoints - (charA.damage - charB.defense),
});
});

it("should execute charB attack on charA", function () {
const charAClone = Object.assign({}, charA);
const charBClone = Object.assign({}, charB);
charAAttacksCharB(charBClone, charAClone);
assertEquals(charBClone, charB);
assertEquals(charAClone, {
...charA,
hitPoints: charA.hitPoints - 1,
});
});
});

describe("simulateFight", function () {
it("return true, if player wins", function () {
// For example, suppose you have 8 hit points, 5 damage, and 5 armor,
// and that the boss has 12 hit points, 7 damage, and 2 armor:
assertEquals(
simulateFight({
hitPoints: 8,
damage: 5,
defense: 5,
}, {
hitPoints: 12,
damage: 7,
defense: 2,
}),
true,
);
});
});
Loading
Loading