diff --git a/AOC.Test/Test2015.cs b/AOC.Test/Test2015.cs index 239d057..0d73fa4 100644 --- a/AOC.Test/Test2015.cs +++ b/AOC.Test/Test2015.cs @@ -26,6 +26,7 @@ public class Test2015 [DataRow(typeof(Day18), "1061", "1006")] [DataRow(typeof(Day19), "576", "207")] [DataRow(typeof(Day20), "665280", "705600")] + [DataRow(typeof(Day21), "78", "148")] public void CheckAllDays(Type dayType, string part1, string part2) { Common.CheckDay(dayType, part1, part2); diff --git a/AOC2015/Day21.cs b/AOC2015/Day21.cs index e645348..4f48829 100644 --- a/AOC2015/Day21.cs +++ b/AOC2015/Day21.cs @@ -5,11 +5,79 @@ namespace AOC2015; /// public sealed class Day21() : Day(2015, 21, "RPG Simulator 20XX") { - public override void ProcessInput() + private const int PlayerHp = 100; + private IEnumerable? _combinations; + private Dictionary? _boss; + + private record Combination(Item Weapon, Item Armor, Item Ring1, Item Ring2) { + public int TotalDamage => Weapon.Damage + Ring1.Damage + Ring2.Damage; + public int TotalArmor => Armor.Armor + Ring1.Armor + Ring2.Armor; + public int TotalCost => Weapon.Cost + Armor.Cost + Ring1.Cost + Ring2.Cost; } - public override object Part1() => ""; + private record Item(string Name, int Cost = 0, int Damage = 0, int Armor = 0); - public override object Part2() => ""; -} + public override void ProcessInput() + { + _boss = Input.ToDictionary(k => k.Split(": ")[0], v => int.Parse(v.Split(": ")[1])); + + var weapons = new Item[] + { + new(Name: "Dagger", Cost: 8, Damage: 4), + new(Name: "Shortsword", Cost: 10, Damage: 5), + new(Name: "Warhammer", Cost: 25, Damage: 6), + new(Name: "Longsword", Cost: 40, Damage: 7), + new(Name: "Greataxe", Cost: 74, Damage: 8) + }; + + var armor = new Item[] + { + new(Name: "No armor", Cost: 0, Armor: 0), + new(Name: "Leather", Cost: 13, Armor: 1), + new(Name: "Chainmail", Cost: 31, Armor: 2), + new(Name: "Splintmail", Cost: 53, Armor: 3), + new(Name: "Bandedmail", Cost: 75, Armor: 4), + new(Name: "Platemail", Cost: 102, Armor: 5) + }; + + var rings = new Item[] + { + new(Name: "No ring", Cost: 0, Damage: 0, Armor: 0), + new(Name: "Damage +1", Cost: 25, Damage: 1, Armor: 0), + new(Name: "Damage +2", Cost: 50, Damage: 2, Armor: 0), + new(Name: "Damage +3", Cost: 100, Damage: 3, Armor: 0), + new(Name: "Defense +1", Cost: 20, Damage: 0, Armor: 1), + new(Name: "Defense +2", Cost: 40, Damage: 0, Armor: 2), + new(Name: "Defense +3", Cost: 80, Damage: 0, Armor: 3) + }; + + _combinations = + from w in weapons + from a in armor + from ring1 in rings + from ring2 in rings + where ring1.Cost == 0 || ring1.Cost != ring2.Cost + select new Combination(w, a, ring1, ring2); + } + + private bool StillAlive(Combination combination) + { + var myDamage = Math.Max(combination.TotalDamage - _boss!["Armor"], 1); + var bossDamagePerTurn = Math.Max(_boss["Damage"] - combination.TotalArmor, 1); + + var turnsToLose = PlayerHp / bossDamagePerTurn; + if (PlayerHp % bossDamagePerTurn > 0) turnsToLose++; + + var turnsToKillBoss = _boss["Hit Points"] / myDamage; + if (_boss["Hit Points"] % myDamage > 0) turnsToKillBoss++; + + return turnsToLose >= turnsToKillBoss; + } + + public override object Part1() => + _combinations!.Where(StillAlive).Min(c => c.TotalCost); + + public override object Part2() => + _combinations!.Where(c => !StillAlive(c)).Max(c => c.TotalCost); +} \ No newline at end of file