my advent of code solutions
1namespace Solutions._2015;
2
3/// <summary>
4/// Day 21: <a href="https://adventofcode.com/2015/day/21"/>
5/// </summary>
6public sealed class Day21RpgSimulator20Xx() : Day(2015, 21, "RPG Simulator 20XX")
7{
8 private const int PlayerHp = 100;
9 private IEnumerable<Combination>? _combinations;
10 private Dictionary<string, int>? _boss;
11
12 private record Combination(Item Weapon, Item Armor, Item Ring1, Item Ring2)
13 {
14 public int TotalDamage => Weapon.Damage + Ring1.Damage + Ring2.Damage;
15 public int TotalArmor => Armor.Armor + Ring1.Armor + Ring2.Armor;
16 public int TotalCost => Weapon.Cost + Armor.Cost + Ring1.Cost + Ring2.Cost;
17 }
18
19 private record Item(string Name, int Cost = 0, int Damage = 0, int Armor = 0);
20
21 public override void ProcessInput()
22 {
23 _boss = Input.ToDictionary(k => k.Split(": ")[0], v => int.Parse(v.Split(": ")[1]));
24
25 var weapons = new Item[]
26 {
27 new(Name: "Dagger", Cost: 8, Damage: 4),
28 new(Name: "Shortsword", Cost: 10, Damage: 5),
29 new(Name: "Warhammer", Cost: 25, Damage: 6),
30 new(Name: "Longsword", Cost: 40, Damage: 7),
31 new(Name: "Greataxe", Cost: 74, Damage: 8),
32 };
33
34 var armor = new Item[]
35 {
36 new(Name: "No armor", Cost: 0, Armor: 0),
37 new(Name: "Leather", Cost: 13, Armor: 1),
38 new(Name: "Chainmail", Cost: 31, Armor: 2),
39 new(Name: "Splintmail", Cost: 53, Armor: 3),
40 new(Name: "Bandedmail", Cost: 75, Armor: 4),
41 new(Name: "Platemail", Cost: 102, Armor: 5),
42 };
43
44 var rings = new Item[]
45 {
46 new(Name: "No ring", Cost: 0, Damage: 0, Armor: 0),
47 new(Name: "Damage +1", Cost: 25, Damage: 1, Armor: 0),
48 new(Name: "Damage +2", Cost: 50, Damage: 2, Armor: 0),
49 new(Name: "Damage +3", Cost: 100, Damage: 3, Armor: 0),
50 new(Name: "Defense +1", Cost: 20, Damage: 0, Armor: 1),
51 new(Name: "Defense +2", Cost: 40, Damage: 0, Armor: 2),
52 new(Name: "Defense +3", Cost: 80, Damage: 0, Armor: 3),
53 };
54
55 _combinations =
56 from w in weapons
57 from a in armor
58 from ring1 in rings
59 from ring2 in rings
60 where ring1.Cost == 0 || ring1.Cost != ring2.Cost
61 select new Combination(w, a, ring1, ring2);
62 }
63
64 private bool StillAlive(Combination combination)
65 {
66 var myDamage = Math.Max(combination.TotalDamage - _boss!["Armor"], 1);
67 var bossDamagePerTurn = Math.Max(_boss["Damage"] - combination.TotalArmor, 1);
68
69 var turnsToLose = PlayerHp / bossDamagePerTurn;
70 if (PlayerHp % bossDamagePerTurn > 0) turnsToLose++;
71
72 var turnsToKillBoss = _boss["Hit Points"] / myDamage;
73 if (_boss["Hit Points"] % myDamage > 0) turnsToKillBoss++;
74
75 return turnsToLose >= turnsToKillBoss;
76 }
77
78 public override object Part1() =>
79 _combinations!.Where(StillAlive).Min(c => c.TotalCost);
80
81 public override object Part2() =>
82 _combinations!.Where(c => !StillAlive(c)).Max(c => c.TotalCost);
83}