diff --git a/2021/ruby/day_10.rb b/2021/ruby/day_10.rb
new file mode 100644
index 0000000..bd1e075
--- /dev/null
+++ b/2021/ruby/day_10.rb
@@ -0,0 +1,74 @@
+require "strscan"
+
+input = ARGF.read.split("\n")
+
+class IllegalCharacter < StandardError
+ attr_reader :char
+ def initialize(char)
+ @char = char
+ end
+end
+
+def parse(line)
+ ss = StringScanner.new(line)
+ stack = []
+
+ until ss.eos?
+ case
+ when ss.scan(/\(/)
+ stack << ?(
+ when ss.scan(/\[/)
+ stack << ?[
+ when ss.scan(/\{/)
+ stack << ?{
+ when ss.scan(/</)
+ stack << ?<
+ when ss.scan(/\)/)
+ raise IllegalCharacter.new(?)) unless stack.pop == ?(
+ when ss.scan(/\]/)
+ raise IllegalCharacter.new(?]) unless stack.pop == ?[
+ when ss.scan(/\}/)
+ raise IllegalCharacter.new(?}) unless stack.pop == ?{
+ when ss.scan(/>/)
+ raise IllegalCharacter.new(?>) unless stack.pop == ?<
+ else
+ fail
+ end
+ end
+
+ stack
+end
+
+# points = {
+# ?) => 3,
+# ?] => 57,
+# ?} => 1197,
+# ?> => 25137,
+# }
+
+# p input.sum {|line|
+# begin
+# parse(line)
+# 0
+# rescue IllegalCharacter => e
+# points.fetch(e.char)
+# end
+# }
+
+POINTS = ")]}>".chars.map.with_index.to_h.transform_values { _1 + 1 }
+def score(closing)
+ total = 0
+ closing.each do |char|
+ total *= 5
+ total += POINTS.fetch(char)
+ end
+ total
+end
+
+complements = "() [] {} <>".split(" ").map(&:chars).to_h
+scores = input
+ .map { parse(_1) rescue [] }
+ .reject(&:empty?)
+ .map {|remaining| remaining.reverse.map { complements.fetch(_1) }}
+ .map { score(_1) }
+p scores.sort.fetch(scores.length / 2)