Watch
1
0
Fork
You've already forked advent-of-code
0

2019: Day 17 Part 2

This commit is contained in:
Jeremy Kaplan 2019-12-20 16:41:46 -08:00
commit 4d1121f914

325
2019/day17/part2.rb Normal file
View file

@ -0,0 +1,325 @@
# frozen_string_literal: true
require_relative 'intcode'
Cell = Struct.new(:r, :c) do
def neighbors
[
Cell.new(r + 1, c),
Cell.new(r - 1, c),
Cell.new(r, c + 1),
Cell.new(r, c - 1)
]
end
def neighbor(dir)
case dir
when :up
Cell.new(r - 1, c)
when :down
Cell.new(r + 1, c)
when :left
Cell.new(r, c - 1)
when :right
Cell.new(r, c + 1)
end
end
def can_move?(dir, scaffold)
scaffold.include? neighbor(dir)
end
def move(dir)
neighbor dir
end
end
State = Struct.new(:pos, :dir)
def bfs(start, get_neighbors, is_goal)
queue = [[start]]
visited = Set.new
until queue.empty?
path = queue.shift
state = path[-1]
next if visited.include? state
visited << state
return path if is_goal.call(state)
children = get_neighbors.call(state).reject { |cell| visited.include? cell }
children.each do |child|
queue << path + [child]
end
end
nil
end
def turn(old, new)
dirs = %i[up right down left]
i = dirs.index old
j = dirs.index new
lefts = ((i - j) % 4).abs
rights = ((j - i) % 4).abs
if lefts < rights
['L'] * lefts
else
['R'] * rights
end
end
def valid_turns(dir)
case dir
when :up, :down
%i[left right]
when :left, :right
%i[up down]
end
end
def follow_path(start_cell, start_dir, scaffold)
path = []
cell = start_cell
dir = start_dir
loop do
while cell.can_move?(dir, scaffold)
cell = cell.move dir
path << 1
end
old_dir = dir
dir = valid_turns(old_dir).find do |d|
cell.can_move?(d, scaffold)
end
break if dir.nil?
path.concat turn(old_dir, dir)
end
path
end
def condense(path)
condense_forward(path).each_slice(2)
end
def condense_forward(path)
new_path = [0]
path.each do |move|
both_numeric = move.is_a?(Numeric) && new_path.last.is_a?(Numeric)
new_path << (both_numeric ? new_path.pop + move : move)
end
new_path.shift if new_path.first.zero?
new_path
end
def create_functions(actions)
valid = proc { |p| p.join(',').size < 20 }
(1..5).reverse.each do |a_length|
actions.uniq.repeated_permutation(a_length).select(&valid).each do |_a|
(1..5).reverse.each do |b_length|
actions.uniq.repeated_permutation(b_length).select(&valid).each do |_b|
(1..5).reverse.each do |c_length|
actions.uniq.repeated_permutation(c_length).select(&valid).each do |_c|
Plan.new
end
end
end
end
end
end
end
Plan = Struct.new(:main, :a, :b, :c) do
def expand
main
.gsub(/A/, a)
.gsub(/B/, b)
.gsub(/C/, c)
end
def validate!
errors = []
errors << 'Main is too long' unless main.size < 20
errors << 'A is too long' unless a.size < 20
errors << 'B is too long' unless b.size < 20
errors << 'C is too long' unless c.size < 20
errors << 'Main has invalid characters' unless main.chars.all? { |c| ['A', 'B', 'C', ','].include? c }
errors.each { |error| puts error }
raise StandardError, 'Plan failed to validate!' unless errors.empty?
end
end
MEMORY_LIMIT = 20 # characters
class World
def initialize
@cells = {}
@max_r = -1
@max_c = -1
end
def set(cell, contents)
@cells[cell] = contents
@max_r = [@max_r, cell.r].max
@max_c = [@max_c, cell.c].max
end
ROBOTS = %i[up down left right].freeze
def plan
# From my input, I know that my robot starts on one end of the scaffolding.
# Also from my input, I know that following the path directly (continuing
# "forward" at every intersection) gets the robot to the end. I'm going to
# hope that I can write a program that takes every loop the "right" way
# first.
start = @cells.find do |_cell, contents|
ROBOTS.include? contents
end
start_cell, start_dir = start
scaffold = @cells.keys.select { |cell| scaffold?(cell) }
path = follow_path(start_cell, start_dir, scaffold)
condense(path)
end
def intersections
@cells.keys.select { |cell| intersection? cell }
end
def intersection?(cell)
scaffold?(cell) && cell.neighbors.all? { |neighbor| scaffold?(neighbor) }
end
SCAFFOLDS = %i[scaffold up down left right].freeze
def scaffold?(cell)
SCAFFOLDS.include? @cells[cell]
end
def to_s
s = String.new
(0..@max_r).each do |r|
(0..@max_c).each do |c|
s << char(@cells[Cell.new(r, c)])
end
s << "\n"
end
s
end
CHAR_TO_CONTENTS = {
'#' => :scaffold,
'.' => :open,
'^' => :up,
'v' => :down,
'<' => :left,
'>' => :right,
'x' => :tumbling
}.freeze
CONTENTS_TO_CHAR = {
scaffold: '#',
open: '.',
up: '^',
down: 'v',
left: '<',
right: '>',
tumbling: 'x'
}.freeze
def self.parse(text)
r = c = 0
world = new
text.each_char do |char|
if char == "\n"
r += 1
c = 0
next
end
contents = CHAR_TO_CONTENTS.fetch(char)
world.set(Cell.new(r, c), contents)
c += 1
end
world
end
private
def char(contents)
CONTENTS_TO_CHAR.fetch(contents)
end
end
class Calibrator
attr_reader :world
def initialize(program)
@computer = Intcode::Computer.new(program.as_memory)
@world = nil
end
def calibrate
text = ''
while (i = tick)
text += i.chr
end
@world = World.parse(text)
alignment(@world.intersections)
end
def alignment(intersections)
intersections.sum do |cell|
cell.r * cell.c
end
end
def tick
@computer.next_output
end
end
class Robot
def initialize(program, input_queue)
@computer = Intcode::Computer.new(program.as_memory) { on_input }
@input_queue = input_queue.clone
end
def on_input
@input_queue.shift
end
def tick
@computer.next_output
end
end
main = 'A,B,A,C,B,C,A,C,B,C'
a = 'L,8,R,10,L,10'
b = 'R,10,L,8,L,8,L,10'
c = 'L,4,L,6,L,8,L,8'
plan = Plan.new(main, a, b, c)
plan.validate!
def to_input(function)
function.chars.map(&:ord) + ["\n".ord]
end
video_feed = 'n'
input_queue = [plan.main, plan.a, plan.b, plan.c].flat_map { |fn| to_input(fn) }
input_queue.concat "#{video_feed}\n".chars.map(&:ord)
program = Intcode.read(File.join(__dir__, 'input2'))
robot = Robot.new(program, input_queue)
outputs = []
while (out = robot.tick)
outputs << out
end
outputs[0...-1].each do |out|
print out.chr
end
puts outputs.last