Skip to content

Commit

Permalink
Day 21 and 22 (#18)
Browse files Browse the repository at this point in the history
* Add puzzle1

* Add algorithm.

* Add some types

* Define some variables.

* Implement equipCharacter

* Implement generateWeapon, generateArmor, generateRings

* Implement modifyCharWithEquip

* Implement charAAttacksCharB

* Implement simulateFight

* Find solution for puzzle1

* Find solution for puzzle2

* Add puzzle1 for day 22

* Update algorithm

* Partially implement castSpell

* Implement isSpellAvailable

* Add first test for applyEffects

* Use spellCost

* Implement apply shielded effect

* Implement apply recharging effect

* Implement apply poisoned effect

* Implement isThereAWinner

* Implement getAvailableSpells

* Solve demo example

* Solve demo example II

* Cleanup solve puzzle1

* Solve puzzle2.

* Cleanup
  • Loading branch information
lukasbicus authored Oct 9, 2024
1 parent 9402d7d commit 6fa5e76
Show file tree
Hide file tree
Showing 7 changed files with 1,548 additions and 0 deletions.
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

0 comments on commit 6fa5e76

Please sign in to comment.