diff --git a/2022/ruby/day_21.rb b/2022/ruby/day_21.rb
new file mode 100644
index 0000000..b0ee219
--- /dev/null
+++ b/2022/ruby/day_21.rb
@@ -0,0 +1,81 @@
+monkeys = ARGF.read.lines(chomp: true).to_h {|line| line.split(": ") }
+
+# part one
+# monkeys.each do |name, body|
+# define_method(name) do
+# eval(body)
+# end
+# end
+
+# p root
+
+# part two
+sub_monkeys = monkeys.delete("root").split(" + ")
+monkeys.delete("humn")
+
+monkeys.each do |name, body|
+ define_method(name) do
+ eval(body)
+ end
+end
+
+target, unknown = sub_monkeys.partition { eval(_1) rescue nil}.map(&:first)
+target = eval(target)
+
+monkeys = monkeys.to_h {|monkey, job|
+ a, op, b = job.split(" ")
+ [monkey, { op:, a:, b: }]
+}
+monkeys["humn"] = { op: :humn }
+
+def apply(monkeys, monkey)
+ job = monkeys.fetch(monkey)
+ case op = job.fetch(:op)
+ when nil
+ job.fetch(:a).to_i
+ when :humn
+ :humn
+ else
+ a = apply(monkeys, job.fetch(:a))
+ b = apply(monkeys, job.fetch(:b))
+ if a.is_a?(Integer) && b.is_a?(Integer)
+ eval([a, op, b].join(" "))
+ else
+ [job.fetch(:op), a, b]
+ end
+ end
+end
+
+tree = apply(monkeys, unknown)
+
+def reverse(target, tree)
+ loop do
+ case tree
+ in :humn
+ return target
+ in [op, Integer, b]
+ a = tree.fetch(1)
+ case op
+ when ?* then target /= a
+ when ?+ then target -= a
+ when ?- then target = a - target
+ else fail tree.inspect
+ end
+ tree = b
+ in [op, a, Integer]
+ b = tree.fetch(2)
+ case op
+ when ?- then target += b
+ when ?/ then target *= b
+ when ?* then target /= b
+ when ?+ then target -= b
+ else fail tree.inspect
+ end
+ tree = a
+ else
+ fail tree.inspect
+ end
+ end
+end
+
+p reverse(target, tree)