From 939a723ec26924d15031e000b8d590e019833394 Mon Sep 17 00:00:00 2001 From: Alpha Chen Date: Wed, 23 Dec 2015 08:38:41 -0800 Subject: [PATCH] Day 22.0 --- ruby/day_22.rb | 167 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 ruby/day_22.rb diff --git a/ruby/day_22.rb b/ruby/day_22.rb new file mode 100644 index 0000000..aca6dd0 --- /dev/null +++ b/ruby/day_22.rb @@ -0,0 +1,167 @@ +require "logger" + +require "letters" +require "minitest" + +class Dude + attr_accessor *%i[ hp damage armor mana ] + + def initialize(hp:, damage: 0, armor: 0, mana: 0) + @hp, @damage, @armor, @mana = hp, damage, armor, mana + end +end + +class Spell + NOOP = ->(*){} + + attr_reader *%i[ name cost timer start effect finish ] + + def initialize(name, cost, timer: nil, start: NOOP, effect: NOOP, finish: NOOP) + @name, @cost, @timer, @start, @effect, @finish = name, cost, timer, start, effect, finish + end +end + +SPELLS = [ + Spell.new(:magic_missile, 53, effect: ->(s) { s.boss.hp -= 4 }), + Spell.new(:drain, 73, effect: ->(s) { s.boss.hp -= 2; s.player.hp += 2 }), + Spell.new(:shield, 113, timer: 6, + start: ->(s) { s.player.armor += 7 }, + finish: ->(s) { s.player.armor -= 7 }), + Spell.new(:poison, 173, timer: 6, effect: ->(s) { s.boss.hp -= 3 }), + Spell.new(:recharge, 229, timer: 5, effect: ->(s) { s.player.mana += 101 }), +] + +class Effect < SimpleDelegator + attr_accessor *%i[ timer ] + + def initialize(spell) + super + + @timer = spell.timer + end +end + +class Simulation + NOOP = ->(_){} + + attr_reader *%i[ boss player log ] + attr_accessor *%i[ effects ] + + def initialize(boss:, player:, log:) + @boss, @player, @log = boss, player, log + @effects = [] + end + + def simulate!(spells) + spells = spells.dup + + catch(:done) do + while true + spell = spells.shift + throw :done if spell.nil? + + self.apply_effects + self.cast(spell) + self.check! + + self.apply_effects + damage = self.boss.damage - self.player.armor + damage = [damage, 1].max + self.player.hp -= damage + self.check! + end + end + end + + def apply_effects + self.effects.each do |effect| + effect.effect.call(self) + effect.timer -= 1 + end + + finished, self.effects = self.effects.partition {|effect| effect.timer.zero? } + finished.each do |effect| + effect.finish.call(self) + end + + self.check! + end + + def cast(spell) + throw :done if self.effects.map(&:name).include?(spell.name) + + self.player.mana -= spell.cost + throw :done if self.player.mana < 0 + + spell.start.call(self) + + if spell.timer.nil? + spell.effect.call(self) + else + self.effects << Effect.new(spell) + end + end + + def check! + throw :done if self.player.hp <= 0 || + self.player.mana <= 0 || + self.boss.hp <= 0 + end +end + +class TestDay22 < Minitest::Test + def test_basic + s = Simulation.new(boss: Dude.new(hp: 13, damage: 8), + player: Dude.new(hp: 10, mana: 250)) + s.simulate!(%i[ magic_missile ]) + + assert_equal 9, s.boss.hp + assert_equal 2, s.player.hp + end + + def test_example_1 + s = Simulation.new(boss: Dude.new(hp: 13, damage: 8), + player: Dude.new(hp: 10, mana: 250)) + s.simulate!(%i[ poison magic_missile ]) + + assert_equal 0, s.boss.hp + assert_equal 2, s.player.hp + assert_equal 24, s.player.mana + end + + def test_example_2 + s = Simulation.new(boss: Dude.new(hp: 14, damage: 8), + player: Dude.new(hp: 10, mana: 250)) + s.simulate!(%i[ recharge shield drain poison magic_missile ]) + + assert_equal -1, s.boss.hp + assert_equal 1, s.player.hp + assert_equal 114, s.player.mana + end +end + +if __FILE__ == $0 + log = Logger.new(STDOUT) + log.level = Logger::WARN + + num_spells = 0 + while true + num_spells += 1 + puts num_spells + + permutations = SPELLS.repeated_permutation(num_spells) + .sort_by {|permutation| permutation.map(&:cost).inject(:+) } + permutations.each do |spells| + s = Simulation.new(boss: Dude.new(hp: 55, damage: 8), + player: Dude.new(hp: 50, mana: 500), + log: log) + s.simulate!(spells) + + if s.player.hp > 0 && s.player.mana > 0 && s.boss.hp <= 0 + p spells.map(&:name) + puts spells.map(&:cost).inject(:+) + exit + end + end + end +end