diff --git a/2022/ruby/day_09.rb b/2022/ruby/day_09.rb
new file mode 100644
index 0000000..15239d0
--- /dev/null
+++ b/2022/ruby/day_09.rb
@@ -0,0 +1,43 @@
+require "set"
+
+motions = ARGF.read.scan(/([RLUD])\s+(\d+)/).map { [_1, _2.to_i] }
+
+class Snake
+ attr_reader :tail
+
+ def initialize
+ @head, @tail = [0, 0], [0, 0]
+ end
+
+ def move!(dir)
+ delta = case dir
+ when ?L then [ 0, -1]
+ when ?U then [ 1, 0]
+ when ?R then [ 0, 1]
+ when ?D then [-1, 0]
+ else fail dir.inspect
+ end
+ @head = @head.zip(delta).map { _1 + _2 }
+
+ delta = @head.zip(@tail).map { _1 - _2 }
+ if delta.any? { _1.abs > 1 }
+ if delta.any?(&:zero?)
+ @tail = @tail.zip(delta.map { _1.clamp(-1, 1) }).map { _1 + _2 }
+ else
+ @tail = @tail.zip(delta.map { _1.clamp(-1, 1) }).map { _1 + _2 }
+ end
+ end
+ end
+end
+
+snake = Snake.new
+seen = Set.new
+
+motions.each do |dir, distance|
+ distance.times do
+ snake.move!(dir)
+ seen << snake.tail
+ end
+end
+
+p seen.size