Something went wrong. Try again.
Code for the Advent of Code event
aoc advent-of-code
Something went wrong. Try again.
5.6 kB · 294 lines
Ruby
at rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295#!/usr/bin/env ruby# frozen_string_literal: true
require 'colorize'require 'matrix'
require_relative '../../../lib/aoc/intcode/cpu'
DEBUG = ENV.fetch('DEBUG', nil)
class Vector def x; self[0]; end def y; self[1]; endend
def term_clear print "\e[H"end
def term_setpos(x, y) print "\e[#{y},#{x}H"end
PATH = 'input'DRAW = !ARGV.empty?
ORIGIN = Vector[0, 0]
DIRECTIONS = { north: 1, south: 2, west: 3, east: 4}.freeze
TRANSFORMS = { north: Vector[0, -1], south: Vector[0, 1], west: Vector[-1, 0], east: Vector[1, 0]}.freeze
STATUSES = { 0 => :wall, 1 => :empty, 2 => :oxygen}.freeze
TILES = { wall: '#', oxygen: 'O', empty: '.', droid: 'D', unknown: ' '}.freeze
SIMPLE_TILES = { origin: '██'.magenta, wall: '██', oxygen: '██'.green, empty: ' ', droid: 'DD', deadend: '██'.red, deadpath: '██'.yellow, path: '██'.light_black, unknown: '██'}.freeze
# grid = Hash.new :unknown
def set_status(text) # rubocop:disable Naming/AccessorMethodName Curses.setpos 0, 0 Curses.clrtoeol Curses.addstr textend
class Bot attr_reader :position
def initialize(cpu, position = Vector[0, 0]) @cpu = cpu @position = position @active = true end
def dup Bot.new @cpu.dup, @position end
def move!(direction) return unless @active value = DIRECTIONS[direction] @cpu.input! value @cpu.run! status = STATUSES[@cpu.output[-1]] @cpu.clear_output! @position += TRANSFORMS[direction] @active = false if status == :wall status end
def active? @active end
def kill! @active = false endend
Bounds = Struct.new :top, :bottom, :left, :right, :offset
class Map attr_reader :oxygen_position, :oxygen_distance, :fill_time
def initialize(base_cpu) @base_cpu = base_cpu @grid = Hash.new :unknown @bots = Set.new first_bot = Bot.new base_cpu.dup @bots.add first_bot end
def step! new_bots = [] deleted_bots = [] @bots.each do |bot| DIRECTIONS.each_key do |direction| clone = bot.dup status = clone.move! direction
if @grid[clone.position] == :unknown @grid[clone.position] = status new_bots << clone unless status == :wall end end
deleted_bots << bot end
@bots.subtract deleted_bots @bots.merge new_bots
draw_simple end
def process! empty_poses = @grid.select { |_, v| v == :empty }.map(&:first)
empty_poses.each do |pos| if pos == ORIGIN @grid[pos] = :origin elsif deadend? pos @grid[pos] = :deadend end end
draw_simple
# Calculate deadend paths deadends = @grid.select { |_, v| v == :deadend }.map(&:first) deadends.each do |de| process_deadend! de draw_simple end
path_cells = @grid.select { |_, v| v == :empty }.map(&:first) path_cells.each do |pos| @grid[pos] = :path end
@oxygen_position = calc_oxygen_position @oxygen_distance = calc_oxygen_distance @processed = true fill! end
def fill! process! unless @processed
time = process_fill! @oxygen_position, 0 draw_simple
@fill_time = time end
def process_deadend!(pos) others = surroundings pos return if others[:empty] > 1 @grid[pos] = :deadpath if @grid[pos] == :empty
dirs = [ pos + TRANSFORMS[:north], pos + TRANSFORMS[:south], pos + TRANSFORMS[:west], pos + TRANSFORMS[:east] ]
pos = dirs.find { |p| @grid[p] == :empty } process_deadend! pos end
def process_fill!(pos, time) return time if @grid[pos] == :wall return time if @grid[pos] == :oxygen && pos != @oxygen_position others = surroundings pos @grid[pos] = :oxygen return time if others[:oxygen] + others[:wall] == 4
draw_simple
dirs = [ pos + TRANSFORMS[:north], pos + TRANSFORMS[:south], pos + TRANSFORMS[:west], pos + TRANSFORMS[:east] ]
dirs.map { |dir| process_fill! dir, time + 1 }.max end
def done? @bots.empty? end
def calc_oxygen_position @grid.find { |_, v| v == :oxygen }.first end
def calc_oxygen_distance @grid.count { |_, v| v == :path } + 1 end
def draw_simple(only_if_debug: true) return unless DRAW return if only_if_debug && !DEBUG term_clear bounds = calc_bounds (bounds.top..bounds.bottom).each do |y| (bounds.left..bounds.right).each do |x| pos = Vector[x, y] type = @grid[pos] print SIMPLE_TILES[type] end puts end end
private
def surroundings(pos) others = [ @grid[pos + TRANSFORMS[:north]], @grid[pos + TRANSFORMS[:south]], @grid[pos + TRANSFORMS[:west]], @grid[pos + TRANSFORMS[:east]] ]
Hash.new(0).tap { |h| others.each { |t| h[t] += 1 } } end
def calc_bounds xs = @grid.keys.map(&:x) ys = @grid.keys.map(&:y) min_x = xs.min max_x = xs.max min_y = ys.min max_y = ys.max x_offset = min_x < 0 ? min_x.abs : 0 y_offset = min_y < 0 ? min_y.abs : 0 Bounds.new min_y, max_y, min_x, max_x, Vector[x_offset, y_offset] end
def deadend?(pos) surroundings(pos)[:wall] == 3 endend
base_cpu = AoC::Intcode::CPU.new.load!(PATH).print_output!(false)
bots = Set.new# cpus = Set.new
bots.add Bot.new base_cpu.dup
map = Map.new base_cpu
map.step! until map.done?
map.process!map.draw_simple(false)
puts "Part 1: #{map.oxygen_distance}"puts "Part 2: #{map.fill_time}"