diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..f621a5c
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "2022/bqn/lib"]
+ path = 2022/bqn/lib
+ url = https://github.com/mlochbaum/bqn-libs.git
diff --git a/2021/j/day_01.j b/2021/j/day_01.j
new file mode 100755
index 0000000..30e9560
--- /dev/null
+++ b/2021/j/day_01.j
@@ -0,0 +1,4 @@
+#!/usr/bin/env jconsole
+
+echo stdin
+exit''
diff --git a/2021/ruby/day_19.rb b/2021/ruby/day_19.rb
new file mode 100644
index 0000000..1cc64e2
--- /dev/null
+++ b/2021/ruby/day_19.rb
@@ -0,0 +1,52 @@
+require "matrix"
+require "set"
+
+scanners = ARGF.read.strip.split("\n\n").to_h {|scanner|
+ id, *rest = scanner.split("\n")
+ id = id.scan(/\d+/)[0].to_i
+ coords = rest.map { _1.split(?,).map(&:to_i) }
+ [id, coords.map { Matrix.column_vector(_1) }]
+}
+
+first_scanner = scanners.shift
+origin_beacon = first_scanner[1].first
+origin_beacon = Matrix.column_vector([-618,-824,-621])
+
+known_scanners = {
+ first_scanner[0] => [ origin_beacon.map { -_1 }, Matrix.identity(3) ],
+}
+known_beacons = Set.new(first_scanner[1].map {|beacon| beacon - origin_beacon })
+
+rot_x = Matrix[ [1, 0, 0], [0, 0, -1], [0, 1, 0] ]
+rot_y = Matrix[ [0, 0, 1], [0, 1, 0], [-1, 0, 0] ]
+rot_z = Matrix[ [0, -1, 0], [1, 0, 0], [0, 0, 1] ]
+id = Matrix.identity(3)
+
+ROTATIONS = Set.new(
+ Array.new(4) { rot_x ** _1 }.flat_map {|x|
+ Array.new(4) { rot_y ** _1 }.flat_map {|y|
+ Array.new(4) { rot_z ** _1 }.map {|z| x * y * z }}}
+)
+
+def find_overlapping_scanner(known_beacons, scanners)
+ scanners.filter_map {|id, beacons|
+ haystack = ROTATIONS.flat_map {|r| beacons.map {|b| [ r*b, r ] }}
+ # haystack.select { _2 == Matrix.identity(3) }.map(&:first).each { p _1 }
+ haystack.find {|position, rotation|
+ translated_beacons = beacons.map { (rotation * _1) - position }
+ (known_beacons & translated_beacons).size >= 12
+ }&.then {|p,o| [id, p, o] }
+ }.first
+end
+
+until scanners.empty?
+ id, position, orientation = find_overlapping_scanner(known_beacons, scanners)
+
+ p [id, position, orientation]
+
+ known_scanners[id] = [position, orientation]
+ translated_beacons = scanners[id].map {|b| b.zip(position).map { _2 - _1 }.zip(orientation).map { _1 * _2 }}
+ known_beacons.merge(translated_beacons)
+ scanners.delete(id)
+end
+
diff --git a/2021/ruby/day_23.rb b/2021/ruby/day_23.rb
new file mode 100644
index 0000000..10e624a
--- /dev/null
+++ b/2021/ruby/day_23.rb
@@ -0,0 +1,35 @@
+#############
+#...........#
+###B#C#B#D###
+ #A#D#C#A#
+ #########
+
+#############
+#...........#
+###D#D#A#A###
+ #C#C#B#B#
+ #########
+
+State = Struct.new(:spaces, :energy) do
+ ENERGY = { A: 1, B: 10, C: 100, D: 1000 }.transform_keys(&:to_s)
+ ROOMS = [2, 4, 6, 8]
+
+ def rooms = ROOMS.to_h { [_1, spaces.fetch(_1)] }
+
+ def valid_moves
+
+ end
+end
+
+spaces = Array.new(11) { [] }
+# input = "DC DC AB AB"
+input = "BA CD BC DA"
+input.split(" ").map(&:chars).each.with_index do |amphipods, i|
+ spaces[(i+1) * 2] = amphipods
+end
+
+start = State.new(spaces, 0)
+
+p start
+p start.rooms
+p start.valid_moves
diff --git a/2021/ruby/day_24.rb b/2021/ruby/day_24.rb
new file mode 100644
index 0000000..0d78cc7
--- /dev/null
+++ b/2021/ruby/day_24.rb
@@ -0,0 +1,58 @@
+INSTRUCTIONS = ARGF.read.split("\n").map { _1.split(/\s+/) }
+
+# INSTRUCTIONS.each do |instruction, *args|
+# a, b = *args
+# case instruction
+# when "inp"
+# puts "#{a} = input.shift"
+# when "add"
+# puts "#{a} += #{b}"
+# when "mul"
+# puts "#{a} *= #{b}"
+# when "div"
+# puts "#{a} = (#{a} / #{b}.to_f).floor"
+# when "mod"
+# puts "#{a} %= #{b}"
+# when "eql"
+# puts "#{a} = #{a} == #{b} ? 1 : 0"
+# else
+# fail
+# end
+# end
+# exit
+
+VARS = %w[ w x y z ]
+def run(input)
+ vars = Hash.new(0)
+
+ INSTRUCTIONS.each do |instruction, *args|
+ a, b = *args
+ b = VARS.include?(b) ? vars[b] : b.to_i
+ case instruction
+ when "inp"
+ vars[a] = input.shift
+ when "add"
+ vars[a] += b
+ when "mul"
+ vars[a] *= b
+ when "div"
+ vars[a] = (vars[a] / b.to_f).floor
+ when "mod"
+ vars[a] %= b
+ when "eql"
+ vars[a] = vars[a] == b ? 1 : 0
+ else
+ fail
+ end
+ end
+
+ vars
+end
+
+99999999999999.downto(11111111111111).lazy.map(&:to_s).reject { _1.include?(?0) }.each do |input|
+ vars = run(input.chars.map(&:to_i))
+ if vars[?z].zero?
+ puts input
+ exit
+ end
+end
diff --git a/2021/ruby/day_24.x.rb b/2021/ruby/day_24.x.rb
new file mode 100644
index 0000000..2f0ea2d
--- /dev/null
+++ b/2021/ruby/day_24.x.rb
@@ -0,0 +1,262 @@
+def run(input)
+ w, x, y, z = 0, 0, 0, 0
+
+ # w = input.shift
+ # x = z % 26
+ # z = (z / 1.to_f).floor
+ # x += 13
+ # x = x == w ? 0 : 1
+ # y = 25 * x + 1 # either y = 26 or y = 1
+ # z *= y # 0
+ # # p [w, x, y, z]
+
+ # y = (w + 13) * x
+ # z += y
+ # # p [w, x, y, z]
+ # # puts
+
+ # w = input.shift
+ # x = z % 26
+ # z = (z / 1.to_f).floor
+ # x += 11
+ # x = x == w ? 0 : 1
+ # y = 25 * x + 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y = (w + 10) * x
+ # z += y
+ # # p [w, x, y, z]
+ # # puts
+
+ # w = input.shift
+ # x = z % 26
+ # z = (z / 1.to_f).floor
+ # x += 15
+ # x = x == w ? 0 : 1
+ # y = (25 * x) + 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y = (w + 5) * x
+ # z += y
+ # # p [w, x, y, z]
+ # # puts
+
+ # w = input.shift
+ # x = z % 26
+ # z = (z / 26.to_f).floor
+ # x += -11
+ # x = x == w ? 0 : 1
+ # y = (25 * x) + 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y = (w + 14) * x
+ # z += y
+ # # p [w, x, y, z]
+ # # puts
+
+ # return [w, x, y, z]
+
+ # w = input.shift
+ # x = z % 26
+ # z = (z / 1.to_f).floor
+ # x += 14
+ # x = x == w ? 0 : 1
+ # y = 25 * x + 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y = (w + 5) * x
+ # z += y
+ # # p [w, x, y, z]
+ # # puts
+ # # puts
+
+ # w = input.shift
+ # x = z % 26
+ # z = (z / 26.to_f).floor
+ # x += 0
+ # x = x == w ? 0 : 1
+ # y = 25 * x + 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y = (w + 15) * x
+ # z += y
+ # # p [w, x, y, z]
+ # # puts
+
+ # return [w, x, y, z]
+
+ # w = input.shift
+ # x = z % 26
+ # z = (z / 1.to_f).floor
+ # x += 12
+ # x = x == w ? 0 : 1
+ # y = 25 * x + 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y *= 0
+ # y += w
+ # y += 4
+ # y *= x
+ # z += y
+ # # p [w, x, y, z]
+
+ # w = input.shift
+ # x *= 0
+ # x += z
+ # x %= 26
+ # z = (z / 1.to_f).floor
+ # x += 12
+ # x = x == w ? 1 : 0
+ # x = x == 0 ? 1 : 0
+ # y *= 0
+ # y += 25
+ # y *= x
+ # y += 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y *= 0
+ # y += w
+ # y += 11
+ # y *= x
+ # z += y
+ # # p [w, x, y, z]
+
+ # w = input.shift
+ # x *= 0
+ # x += z
+ # x %= 26
+ # z = (z / 1.to_f).floor
+ # x += 14
+ # x = x == w ? 1 : 0
+ # x = x == 0 ? 1 : 0
+ # y *= 0
+ # y += 25
+ # y *= x
+ # y += 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y *= 0
+ # y += w
+ # y += 1
+ # y *= x
+ # z += y
+ # # p [w, x, y, z]
+ # # puts
+ # # puts
+
+ # w = input.shift
+ # x *= 0
+ # x += z
+ # x %= 26
+ # z = (z / 26.to_f).floor
+ # x += -6
+ # x = x == w ? 1 : 0
+ # x = x == 0 ? 1 : 0
+ # y *= 0
+ # y += 25
+ # y *= x
+ # y += 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y *= 0
+ # y += w
+ # y += 15
+ # y *= x
+ # z += y
+ # # p [w, x, y, z]
+
+ # return [w, x, y, z]
+
+ # w = input.shift
+ # x *= 0
+ # x += z
+ # x %= 26
+ # z = (z / 26.to_f).floor
+ # x += -10
+ # x = x == w ? 1 : 0
+ # x = x == 0 ? 1 : 0
+ # y *= 0
+ # y += 25
+ # y *= x
+ # y += 1
+ # z *= y
+ # # p [w, x, y, z]
+
+ # y *= 0
+ # y += w
+ # y += 12
+ # y *= x
+ # z += y
+ # # p [w, x, y, z]
+
+ # return [w, x, y, z]
+
+ w = input.shift
+ x *= 0
+ x += z
+ x %= 26
+ z = (z / 26.to_f).floor
+ x += -12
+ x = x == w ? 1 : 0
+ x = x == 0 ? 1 : 0
+ y *= 0
+ y += 25
+ y *= x
+ y += 1
+ z *= y
+ # p [w, x, y, z]
+
+ y *= 0
+ y += w
+ y += 8
+ y *= x
+ z += y
+ # p [w, x, y, z]
+
+ return [w, x, y, z]
+
+ w = input.shift
+ x = z % 26
+ z = (z / 26.to_f).floor
+ x += -3
+ x = x == w ? 0 : 1
+ y = 25 * x + 1
+ z *= y
+ # p [w, x, y, z]
+
+ y = (w + 14) * x
+ z += y
+ # p [w, x, y, z]
+
+ w = input.shift
+ x = z % 26 # z - 5 must be w
+ z = (z / 26.to_f).floor # z must be < 26
+ x += -5 # w must be x - 5
+ x = x == w ? 0 : 1 # x must be w
+ y = 25 * x + 1
+ z *= y # z must be 0
+ # p [w, x, y, z]
+
+ y = (w + 9) * x # x must be 0
+ z += y # y must be 0
+
+ [w, x, y, z]
+end
+
+# 99999999999999.downto(11111111111111).lazy.map(&:to_s).reject { _1.include?(?0) }.each do |input|
+ vars = run(input.chars.map(&:to_i))
+ p [input, vars[3]] if vars[3] < 400
+ if vars[3].zero?
+ puts input
+ exit
+ end
+end
diff --git a/2022/bqn/lib b/2022/bqn/lib
new file mode 160000
index 0000000..b90e26c
--- /dev/null
+++ b/2022/bqn/lib
@@ -0,0 +1 @@
+Subproject commit b90e26c29b1742bf164b6121275b5e1ee8a56365
diff --git a/2022/fnl/day_01.fnl b/2022/fnl/day_01.fnl
new file mode 100644
index 0000000..252a095
--- /dev/null
+++ b/2022/fnl/day_01.fnl
@@ -0,0 +1,19 @@
+(fn dbg [tbl]
+ (each [_ chunk (ipairs tbl)]
+ (print (accumulate [x "" _ i (ipairs chunk)]
+ (.. x ", " i)))))
+
+(local input (accumulate [input [[]] line (io.lines :../day_01.txt)]
+ (do
+ (if (= (length line) 0)
+ (table.insert input [])
+ (table.insert (. input (length input)) (tonumber line)))
+ input)))
+
+(fn sum [l]
+ (accumulate [sum 0 _ n (ipairs l)]
+ (+ sum n)))
+
+(local l (icollect [_ l (ipairs input)] (sum l)))
+(table.sort l)
+(print (. l (length l)))
diff --git a/2022/ruby/.ruby-version b/2022/ruby/.ruby-version
index 8c50098..a3ec5a4 100644
--- a/2022/ruby/.ruby-version
+++ b/2022/ruby/.ruby-version
@@ -1 +1 @@
-3.1
+3.2
diff --git a/2022/ruby/day_17.rb b/2022/ruby/day_17.rb
new file mode 100644
index 0000000..6609b7c
--- /dev/null
+++ b/2022/ruby/day_17.rb
@@ -0,0 +1,89 @@
+require "set"
+
+rocks = <<~ROCKS.split("\n\n")
+ ####
+
+ .#.
+ ###
+ .#.
+
+ ..#
+ ..#
+ ###
+
+ #
+ #
+ #
+ #
+
+ ##
+ ##
+ROCKS
+rocks = rocks
+ .map {|rock| rock.lines(chomp: true).reverse.map(&:chars) }
+ .map {|rock|
+ rock.each.with_index.with_object(Set.new) {|(row, y), rocks|
+ row.each.with_index do |c, x|
+ rocks << [x, y] if c == ?#
+ end
+ }
+ }
+rocks = rocks.cycle
+
+jet_pattern = ARGF.read.strip.chars
+jet_pattern = jet_pattern.cycle
+
+chamber = Hash.new {|_,(x,y)| !(0...7).cover?(x) || y.negative? }
+
+def move_rock(chamber, rock, dx, dy)
+ next_rock = rock.map {|x,y| [x+dx, y+dy] }
+
+ if next_rock.any? {|x,y| chamber[[x, y]] }
+ rock
+ else
+ next_rock
+ end
+end
+
+def debug(chamber, rock)
+ max_y = [chamber.keys.map(&:last).max || 0, rock.map(&:last).max || 0].max
+
+ puts
+ puts max_y.downto(0).map {|y|
+ (0...7).map {|x|
+ (chamber[[x,y]] || rock.include?([x,y])) ? ?# : ?.
+ }.join
+ }.join("\n")
+end
+
+2022.times do
+ rock = rocks.next
+
+ y = chamber.empty? ? 3 : chamber.keys.map(&:last).max + 4
+ x = 2
+ rock = move_rock(chamber, rock, x, y)
+
+ loop do
+ rock = case jet = jet_pattern.next
+ when ?< then move_rock(chamber, rock, -1, 0)
+ when ?> then move_rock(chamber, rock, 1, 0)
+ else fail "invalid jet pattern: #{jet}"
+ end
+
+ next_rock = move_rock(chamber, rock, 0, -1)
+ break if rock == next_rock
+ rock = next_rock
+ end
+
+ chamber.merge!(rock.to_h { [_1, true] })
+end
+
+# debug(chamber, Set.new)
+max_y = chamber.keys.map(&:last).max + 1
+p max_y
+
+candidates = (0..max_y).select {|y| (0...7).count {|x| chamber[[x,y]] } == 6 }
+# max_delta = candidates.each_cons(2).map { _2 - _1 }.max
+candidates.
+p candidates
+p max_delta
diff --git a/2022/ruby/day_19.rb b/2022/ruby/day_19.rb
new file mode 100644
index 0000000..f9d0e92
--- /dev/null
+++ b/2022/ruby/day_19.rb
@@ -0,0 +1,81 @@
+SEEN = []
+
+class Factory
+ attr_reader :time, :robots, :resources
+
+ def initialize(
+ blueprint,
+ time=0,
+ robots={ ore: 1, clay: 0, obsidian: 0, geode: 0 },
+ resources={ ore: 0, clay: 0, obsidian: 0, geode: 0 }
+ )
+ @blueprint = blueprint
+ @time = time
+ @robots = robots
+ @resources = resources
+ end
+
+ def max_geodes
+ if @time == 20
+ pp self if @robots.values_at(:ore, :clay, :obsidian, :geode) == [1, 4, 2, 1]
+ return @resources.fetch(:geode)
+ end
+
+ resources = @resources.merge(@robots) { _2 + _3 }
+ branches = @blueprint.filter_map {|robot, cost|
+ if cost.all? {|res, qty| @resources.fetch(res) >= qty }
+ Factory.new(
+ @blueprint,
+ @time + 1,
+ @robots.merge({robot => 1}) { _2 + _3 },
+ resources.merge(cost) { _2 - _3 },
+ )
+ else
+ nil
+ end
+ }
+
+ if @blueprint.any? {|_, cost| cost.any? {|res, qty| @robots.fetch(res) > 0 && @resources.fetch(res) < qty }}
+ pp self
+ branches << Factory.new(@blueprint, @time + 1, @robots, resources)
+ end
+
+ branches = branches.reject {|factory|
+ SEEN.any? {|other|
+ %i[ ore clay obsidian geode ].all? {|res|
+ factory.robots.fetch(res) <= other.robots.fetch(res) &&
+ factory.resources.fetch(res) <= other.resources.fetch(res) &&
+ factory.time >= other.time
+ }
+ }
+ }
+ SEEN.concat(branches)
+
+ return @resources.fetch(:geode) if branches.empty?
+
+ branches.map(&:max_geodes).max
+ end
+
+ def to_s
+ "<time: #@time robots: #{@robots.values} resources: #{@resources.values}>"
+ end
+ alias_method :inspect, :to_s
+end
+
+blueprints = ARGF.read
+ .split("\n\n").map { _1.lines(chomp: true).join }
+ .map { _1.scan(/\d+/).map(&:to_i) }
+ .map {
+ _1.shift # id
+ {
+ ore: {ore: _1.shift },
+ clay: {ore: _1.shift },
+ obsidian: {ore: _1.shift, clay: _1.shift },
+ geode: {ore: _1.shift, obsidian: _1.shift },
+ }
+ }
+
+p Factory.new(blueprints[0]).max_geodes
+p SEEN.count
+
+__END__
diff --git a/2022/ruby/day_22.rb b/2022/ruby/day_22.rb
new file mode 100644
index 0000000..faa42b4
--- /dev/null
+++ b/2022/ruby/day_22.rb
@@ -0,0 +1,78 @@
+require "strscan"
+
+include Math
+
+map, path = ARGF.read.chomp.split("\n\n")
+
+map = map.split("\n").each.with_index.with_object({}) {|(row,y),map|
+ row.chars.each.with_index do |tile,x|
+ map[[y,x]] = tile if tile != " "
+ end
+}
+max_y = map.keys.map(&:first).max
+max_x = map.keys.map(&:last).max
+
+start_x = map.keys.select { _1.first.zero? }.map(&:last).min
+
+current = [0, start_x]
+facing = 0
+
+ss = StringScanner.new(path)
+until ss.eos?
+ case
+ when n = ss.scan(/\d+/)
+ n = n.to_i
+ n.times do
+ y, x = current.zip([-sin(facing),cos(facing)]).map { _1 + _2 }.map(&:to_i)
+ next_tile = map[[y,x]]
+
+ unless next_tile
+ puts
+ p [y,x, facing]
+ y, x = case facing
+ when 0 then [y, 0]
+ when PI/2 then [max_y, x]
+ when PI then [y, max_x]
+ when 3*PI/2 then [0, x]
+ else fail "unexpected facing: #{facing}"
+ end
+ next_tile = map[[y,x]]
+ while next_tile.nil?
+ y, x = [y, x].zip([-sin(facing),cos(facing)]).map { _1 + _2 }.map(&:to_i)
+ next_tile = map[[y,x]]
+ end
+
+ p [y,x,next_tile]
+ end
+
+ case next_tile
+ when ?.
+ current = [y,x]
+ when ?#
+ break
+ else
+ fail "unexpected tile: #{next_tile}"
+ end
+ end
+ when dir = ss.scan(/R|L/)
+ facing += case dir
+ when ?R then -PI/2
+ when ?L then PI/2
+ else fail "unexpected dir: #{dir}"
+ end
+ facing %= 2*PI
+ else
+ fail ss.rest
+ end
+end
+
+row, col = current.map { _1 + 1 }
+facing = case facing
+ when 0 then 0
+ when PI/2 then 3
+ when PI then 2
+ when 3*PI/2 then 2
+ else
+ fail "unexpected facing: #{facing}"
+ end
+p [1000, 4, 1].zip([row, col, facing]).sum { _1 * _2 }
diff --git a/2022/ruby/day_24.rb b/2022/ruby/day_24.rb
new file mode 100644
index 0000000..d13a393
--- /dev/null
+++ b/2022/ruby/day_24.rb
@@ -0,0 +1,93 @@
+require "set"
+
+walls = Set.new
+blizzards = Hash.new {|h,k| h[k] = Set.new }
+clear_ground = Set.new
+
+ARGF.read.lines(chomp: true).each.with_index do |row, y|
+ row.chars.each.with_index do |pos,x|
+ case pos
+ when ?#
+ walls.add([y,x])
+ when /[v^<>]/
+ blizzards[pos].add([y,x])
+ when ?.
+ clear_ground.add([y,x])
+ else
+ fail pos
+ end
+ end
+end
+
+x_range = Range.new(*walls.map(&:last).minmax)
+y_range = Range.new(*walls.map(&:first).minmax)
+
+debug = ->() do
+ puts
+ puts y_range.map {|y|
+ x_range.map {|x|
+ if walls.include?([y,x])
+ ?#
+ elsif blizzards.any? {|_,pos| pos.include?([y,x])}
+ b = blizzards.select {|_,pos| pos.include?([y,x])}
+ (b.size == 1) ? b.keys[0] : b.size
+ else
+ ?.
+ end
+ }.join
+ }.join("\n")
+end
+
+start = [0, clear_ground.find {|y,x| y == 0 }.last]
+goal = [y_range.end, clear_ground.find {|y,x| y == y_range.end }.last]
+
+dir_deltas = {
+ ?^ => [-1, 0],
+ ?v => [ 1, 0],
+ ?< => [ 0, -1],
+ ?> => [ 0, 1],
+}
+moves = [*dir_deltas.values, [0, 0]]
+
+frontier = [start]
+(1..).each do |time|
+ if (time % 1).zero?
+ puts time
+ # p frontier
+ # debug.()
+ end
+
+ blizzards = blizzards.to_h {|dir,positions|
+ delta = dir_deltas.fetch(dir)
+ edges, positions = positions
+ .map {|pos| pos.zip(delta).map { _1 + _2 }}
+ .partition { walls.include?(_1) }
+ positions.concat(edges.map {|(y,x)|
+ case dir
+ when ?^ then [y_range.end - 1, x]
+ when ?v then [y_range.begin + 1, x]
+ when ?< then [y, x_range.end - 1]
+ when ?> then [y, x_range.end + 1]
+ else fail dir
+ end
+ })
+ [dir, positions]
+ }
+
+ clear_ground = y_range.map {|y| x_range.map {|x| [y, x] }}.flatten(1)
+ .reject {|pos| walls.include?(pos) || blizzards.any? { _2.include?(pos) }}
+
+ options = frontier.map {|current|
+ moves.filter_map {|delta|
+ pos = current.zip(delta).map { _1 + _2 }
+ clear_ground.include?(pos) && pos
+ }
+ }.flatten(1)
+
+ frontier = options.uniq
+
+ if frontier.include?(goal)
+ puts time + 1
+ exit
+ end
+end
diff --git a/etc/total-time.rb b/etc/total-time.rb
new file mode 100644
index 0000000..0193583
--- /dev/null
+++ b/etc/total-time.rb
@@ -0,0 +1,12 @@
+seconds = ARGF.read.strip.lines(chomp: true)
+ .sum {|line|
+ line.strip.split(/\s+/).fetch(4)
+ .split(?:).map(&:to_i)
+ .zip([60*60, 60, 1])
+ .sum { _1 * _2 }
+ }
+
+hours, seconds = seconds.divmod(60 * 60)
+minutes, seconds = seconds.divmod(60)
+
+puts "#{hours} hours, #{minutes} minutes, #{seconds} seconds"