diff --git a/2023/ruby/day_02.rb b/2023/ruby/day_02.rb
new file mode 100644
index 0000000..f93657e
--- /dev/null
+++ b/2023/ruby/day_02.rb
@@ -0,0 +1,39 @@
+input = ARGF.read
+
+def cubes(input)
+ input
+ .scan(/(\d+) (\w+)/)
+ .to_h(&:reverse)
+ .transform_values(&:to_i)
+end
+
+bag = cubes("12 red cubes, 13 green cubes, and 14 blue cubes")
+
+Game = Data.define(:id, :reveals) do
+ def possible?(bag)
+ reveals.all? {|reveal|
+ bag.all? {|color, n| n >= reveal.fetch(color, 0) }
+ }
+ end
+end
+
+games = input
+ .scan(/^Game (\d+): ((?~\n))/)
+ .map {|id, reveals|
+ Game.new(
+ id.to_i,
+ reveals.split(?;).map { cubes(_1) }
+ )
+ }
+
+# part one
+p games.select { _1.possible?(bag) }.sum(&:id)
+
+# part two
+p games
+ .map {|game|
+ game.reveals.inject {|bag, reveal|
+ bag.merge(reveal) { [_2, _3].max }
+ }
+ }
+ .sum {|bag| bag.values.inject(:*) }