diff --git a/2021/ruby/day_22.rb b/2021/ruby/day_22.rb
new file mode 100644
index 0000000..9f1bf7b
--- /dev/null
+++ b/2021/ruby/day_22.rb
@@ -0,0 +1,109 @@
+require "set"
+
+steps = ARGF.read.scan(/(on|off) x=(.+),y=(.+),z=(.+)/)
+ .map {|power, *ranges| [power == "on", *ranges.map { eval(_1) }] }
+ # .select {|_, *ranges| ranges.all? { _1.begin >= -50 && _1.end <= 50 }}
+
+# reactor = Hash.new(false)
+# steps.each do |power,x,y,z|
+# x.each do |x|
+# y.each do |y|
+# z.each do |z|
+# reactor[[x,y,z]] = power
+# end
+# end
+# end
+# end
+
+# p reactor.count { _2 }
+
+# xs = steps.flat_map { [_2.begin, _2.end] }.minmax
+# ys = steps.flat_map { [_3.begin, _3.end] }.minmax
+# zs = steps.flat_map { [_4.begin, _4.end] }.minmax
+# ons = Range.new(*xs).sum {|x| Range.new(*ys).sum {|y| Range.new(*zs).count {|z|
+# step = steps.find { _2.cover?(x) && _3.cover?(y) && _4.cover?(z) }
+# step ? step[0] : false
+# }}}
+
+# p ons
+
+class Range
+ def overlap(other)
+ if cover?(other.begin)
+ min = other.begin
+ max = [self.end, other.end].min
+ (min..max)
+ elsif cover?(other.end)
+ min = [self.begin, other.begin].max
+ max = other.end
+ (min..max)
+ elsif other.cover?(self.begin)
+ min = self.begin
+ max = [self.end, other.end].min
+ (min..max)
+ elsif other.cover?(self.end)
+ min = [self.begin, other.begin].max
+ max = self.end
+ (min..max)
+ else
+ nil
+ end
+ end
+
+ def split(other)
+ overlap = self.overlap(other)
+ return [] if overlap.nil?
+
+ splits = [ overlap ]
+ splits << (self.begin..overlap.begin-1) if overlap.begin > self.begin
+ splits << (overlap.end+1..self.end) if overlap.end < self.end
+ splits
+ end
+end
+
+Cube = Struct.new(:xs, :ys, :zs) do
+ def -(other)
+ return self unless o = overlap(other)
+
+ xxs = xs.split(other.xs)
+ yys = ys.split(other.ys)
+ zzs = zs.split(other.zs)
+
+ cuboids = xxs.flat_map {|x| yys.flat_map {|y| zzs.map {|z|
+ Cube.new(x, y, z)
+ }}}
+ cuboids.delete(o)
+
+ cuboids
+ end
+
+ def overlap(other)
+ overlaps = to_a.zip(other.to_a).map { _1.overlap(_2) }
+ return nil if overlaps.any?(&:nil?)
+
+ Cube.new(*overlaps)
+ end
+
+ def overlap?(other) = !overlap(other).nil?
+
+ def volume = (xs.end - xs.begin + 1) * (ys.end - ys.begin + 1) * (zs.end - zs.begin + 1)
+
+ def to_s = "[#{[xs, ys, zs].map(&:to_s).join(", ")}]"
+ alias_method :inspect, :to_s
+end
+
+steps = steps.map {|power, *ranges| [power, Cube.new(*ranges)] }
+
+reactor = Set.new
+steps.each do |power, cube|
+ reactor
+ .select { _1.overlap?(cube) }
+ .each do |overlap|
+ reactor.delete(overlap)
+ reactor.merge(overlap - cube)
+ end
+
+ reactor << cube if power
+end
+
+p reactor.sum(&:volume)