diff --git a/res/2015/day22/test_input.txt b/res/2015/day22/test_input.txt index e69de29..4660d92 100644 --- a/res/2015/day22/test_input.txt +++ b/res/2015/day22/test_input.txt @@ -0,0 +1,2 @@ +Hit Points: 13 +Damage: 8 diff --git a/src/2015/Day22.cpp b/src/2015/Day22.cpp index 451c72a..5332f6d 100644 --- a/src/2015/Day22.cpp +++ b/src/2015/Day22.cpp @@ -2,30 +2,163 @@ namespace y2015::day22 { - REGISTER_DAY(2015, Day22, std::vector, int); + struct Player + { + int hp; + int armor; + int mana; + }; - REGISTER_TEST_EXAMPLE(2015, Day22, ExampleInput, 1, 0); - REGISTER_TEST(2015, Day22, Input, 1, 0); - REGISTER_TEST_EXAMPLE(2015, Day22, ExampleInput, 2, 0); - REGISTER_TEST(2015, Day22, Input, 2, 0); + struct Monster + { + int hp; + int dmg; + }; + + REGISTER_DAY(2015, Day22, Monster, int); + + REGISTER_TEST_EXAMPLE(2015, Day22, ExampleInput, 1, 212); + REGISTER_TEST(2015, Day22, Input, 1, 900); + REGISTER_TEST_EXAMPLE(2015, Day22, ExampleInput, 2, 212); + REGISTER_TEST(2015, Day22, Input, 2, 1216); READ_INPUT(input) { - std::vector vec; - std::string str; - while (getline(input, str)) + Monster monster; + input >> "Hit Points: " >> monster.hp >> "\n"; + input >> "Damage: " >> monster.dmg >> "\n"; + return monster; + } + + struct Spell + { + int cost; + int timer; + int dmg; + int armor; + int manaGain; + int heal; + }; + + struct State + { + Player player; + Monster monster; + std::vector spellTimers; + + bool operator<(const State& other) const { + if (player.hp != other.player.hp) + return player.hp < other.player.hp; + if (player.armor != other.player.armor) + return player.armor < other.player.armor; + if (player.mana != other.player.mana) + return player.mana < other.player.mana; + + if (monster.hp != other.monster.hp) + return monster.hp < other.monster.hp; + if (monster.dmg != other.monster.dmg) + return monster.dmg < other.monster.dmg; + + return spellTimers < other.spellTimers; } - return vec; + }; + + // clang-format off + // cost timer dmg armor manaGain heal + const std::vector spells{Spell{53, 1, 4, 0, 0, 0}, + Spell{73, 1, 2, 0, 0, 2}, + Spell{113, 6, 0, 7, 0, 0}, + Spell{173, 6, 3, 0, 0, 0}, + Spell{229, 5, 0, 0, 101, 0}}; + // clang-format-on + + void ApplyEffects(State& state) + { + for (int i = 0; i < state.spellTimers.size(); i++) + { + if (state.spellTimers[i] == 0) + continue; + + const auto& spell = spells[i]; + state.spellTimers[i]--; + state.monster.hp -= spell.dmg; + state.player.hp += spell.heal; + state.player.mana += spell.manaGain; + if (state.spellTimers[i] == 0) + state.player.armor -= spell.armor; + } + } + + std::pair PlayerTurn(State state, bool hardmode, std::map>& memoization) + { + // Apply rules at start of turn + if (hardmode) + state.player.hp--; + if (state.player.hp <= 0) + return {false, 0}; + ApplyEffects(state); + + if (state.monster.hp <= 0) + return {true, 0}; + + auto it = memoization.find(state); + if (it != memoization.end()) + return it->second; + + bool canWin = false; + int minManaCost = std::numeric_limits::max(); + for (int i = 0; i < spells.size(); i++) + { + // Spell is already active + if (state.spellTimers[i] > 0) + continue; + + const auto& spell = spells[i]; + if (state.player.mana < spell.cost) + continue; + + // Cast spell + State newState = state; + newState.spellTimers[i] = spell.timer; + newState.player.mana -= spell.cost; + newState.player.armor += spell.armor; + + // Boss turn + ApplyEffects(newState); + if (newState.monster.hp <= 0) + { + if (spell.cost < minManaCost) + { + minManaCost = spell.cost; + canWin = true; + } + continue; + } + newState.player.hp -= std::max(1, newState.monster.dmg - newState.player.armor); + + // Player turn + auto result = PlayerTurn(newState, hardmode, memoization); + if (result.first && result.second + spell.cost < minManaCost) + { + canWin = true; + minManaCost = result.second + spell.cost; + } + } + + memoization[state] = {canWin, minManaCost}; + return {canWin, minManaCost}; } OUTPUT1(input) { - return 0; + std::map> memoization; + return PlayerTurn(State{Player{50, 0, 500}, input, {0, 0, 0, 0, 0}}, false, memoization).second; } OUTPUT2(input) { - return 0; + std::map> memoization; + return PlayerTurn(State{Player{50, 0, 500}, input, {0, 0, 0, 0, 0}}, true, memoization).second; } }