diff --git a/ruby/day_21.rb b/ruby/day_21.rb
new file mode 100644
index 0000000..3e6945d
--- /dev/null
+++ b/ruby/day_21.rb
@@ -0,0 +1,99 @@
+require "letters"
+
+class Dude < Struct.new(*%i[ name hp damage armor ])
+ def attack!(dude)
+ damage = self.damage - dude.armor
+ damage = 1 unless damage > 0
+
+ dude.hp = dude.hp - damage
+ return unless dude.hp > 0
+
+ dude.attack!(self)
+ end
+
+ def equipped(equipment)
+ self.class.new(name, hp,
+ self.damage + equipment.damage_bonus,
+ self.armor + equipment.armor_bonus)
+ end
+
+ def win_against?(dude)
+ self_hp = self.hp
+ dude_hp = dude.hp
+
+ self.attack!(dude)
+ result = (self.hp > 0)
+
+ self.hp = self_hp
+ dude.hp = dude_hp
+
+ result
+ end
+end
+
+Item = Struct.new(*%i[ name cost damage armor ])
+NULL_ITEM = Item.new("Nothing", 0, 0, 0)
+
+class Equipment < Struct.new(*%i[ weapon armor rings ])
+ attr_accessor *%i[ cost damage_bonus armor_bonus ]
+
+ def initialize(*)
+ super
+
+ equipment = [self.weapon, self.armor].concat(self.rings)
+ self.cost = equipment.map(&:cost).inject(:+)
+ self.damage_bonus = equipment.map(&:damage).inject(:+)
+ self.armor_bonus = equipment.map(&:armor).inject(:+)
+ end
+end
+
+weapons, armors, rings = DATA.read.split("\n\n").map {|set|
+ set.split("\n").drop(1).map {|attrs|
+ attrs = attrs.split(/\s+/)
+ name = attrs[0..-4].join(" ")
+ attrs = attrs[-3..-1].map(&:to_i)
+ Item.new(name, *attrs)
+ }
+}
+armors << NULL_ITEM
+rings << NULL_ITEM
+rings = rings.combination(2).to_a
+rings << [NULL_ITEM, NULL_ITEM]
+
+equipments = weapons.flat_map {|weapon|
+ armors.flat_map {|armor|
+ rings.flat_map {|rings|
+ Equipment.new(weapon, armor, rings)
+ }
+ }
+}.sort_by {|e| e.cost }
+
+BOSS = Dude.new("boss", 100, 8, 2)
+YOU = Dude.new("you", 100, 0, 0)
+
+puts equipments.find {|e|
+ YOU.equipped(e).win_against?(BOSS)
+}.cost
+
+__END__
+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