diff --git a/2024/ruby/day_15.rb b/2024/ruby/day_15.rb
new file mode 100644
index 0000000..10e2d53
--- /dev/null
+++ b/2024/ruby/day_15.rb
@@ -0,0 +1,57 @@
+input = DATA.read.split("\n\n")
+
+map = {}
+input.shift.split("\n").each.with_index do |row, y|
+ row.chars.each.with_index do |pos, x|
+ map[[y,x]] = pos unless pos == ?.
+ end
+end
+robot = map.find { _2 == ?@ }.first
+
+bounds = [
+ (0..map.keys.map(&:first).max),
+ (0..map.keys.map(&:last).max),
+]
+map_s = ->() {
+ bounds[0].map { |y|
+ bounds[1].map { |x|
+ map.fetch([y,x], ?.)
+ }.join
+ }.join("\n")
+}
+
+Nope = Class.new(Exception)
+
+def push(map, pos, move)
+ delta = DELTAS.fetch(move)
+ pospos = pos.zip(delta).map { _1 + _2 }
+
+ case map.fetch(pospos, nil)
+ when ?#
+ raise Nope
+ when ?O
+ push(map, pospos, move)
+ when nil
+ else
+ fail
+ end
+
+ map[pospos] = map.fetch(pos)
+ map.delete(pos)
+
+ pospos
+end
+
+DELTAS = %w[ ^ v < > ].zip([[-1,0], [1,0], [0,-1], [0, 1]]).to_h
+movements = input.shift.gsub("\n", "").chars
+
+movements.each do |move|
+ begin
+ robot = push(map, robot, move)
+ rescue Nope
+ end
+end
+
+pp map.select { _2 == ?O }.sum {|(y,x),_| 100 * y + x }
+
+__END__