aoc_omni/ruby/2016/11/problem.rb
2025-11-30 21:22:21 -05:00

248 lines
4.8 KiB
Ruby

class Column
attr_reader :floors, :items, :elevator, :history
def self.for(lines)
floors = lines.each_with_index.map { |line, index| Floor.for line, index + 1 }
items = floors.map(&:items).flatten
elevator = 1
new floors, items, elevator
end
def initialize(floors, items, elevator, history=[])
@floors = floors
@items = items
@elevator = elevator
@history = history
end
def to_s
@to_s ||= floors.reverse.map do |floor|
floor_num = "F#{floor.number}"
elevator_indicator = elevator == floor.number ? "E " : ". "
item_string = items.map do |item|
floor.has_item?(item) ? item.abbr : ". "
end.join(" ")
"#{floor_num} #{elevator_indicator} #{item_string}"
end.join("\n")
end
def valid?
floors.all? &:valid?
end
def repeat?
history.include? to_s
end
def apply(move)
from_floor = floors.find { |floor| move.from == floor.number }
to_floor = floors.find { |floor| move.to == floor.number }
other_floors = floors.find_all { |floor| ![move.from, move.to].include?(floor.number) }
new_from_floor = Floor.new(from_floor.items - move.with, from_floor.number)
new_to_floor = Floor.new(to_floor.items + move.with, to_floor.number)
self.class.new [new_from_floor, new_to_floor, *other_floors].sort_by(&:number), items, move.to, self.history + [to_s]
end
def complete?
(items - floors.last.items).empty?
end
def current_floor
floors.find { |floor| elevator == floor.number }
end
def generate_moves
current_floor.generate_moves
end
def next_states
generate_moves.map { |move| apply(move) }.select(&:valid?).reject(&:repeat?)
end
end
class Floor
attr_reader :items, :number
def initialize(items, number)
@items = items
@number = number
end
def self.for(line, number)
strings = line.gsub(" and", "").gsub(",", "").gsub(".", "").split(" a ")[1..]
new strings.map { |string| Item.for string }, number
end
def has_item?(item)
items.include? item
end
def valid?
items.all? { |item| item.valid_on? self }
end
def generators
items.select &:generator?
end
def microchips
items.select &:microchip?
end
def has_matching_generator?(element)
generators.any? { |gen| gen.is_element? element }
end
def generate_moves
[*one_item_moves, *two_item_moves]
end
def one_item_moves
[*one_item_up_moves, *one_item_down_moves]
end
def one_item_up_moves
return [] if number == 4
items.map do |item|
Move.new number, number + 1, [item]
end
end
def one_item_down_moves
return [] if number == 1
items.map do |item|
Move.new number, number - 1, [item]
end
end
def two_item_moves
[*two_item_up_moves, *two_item_down_moves]
end
def two_item_up_moves
return [] if number == 4
items.each_with_index.map do |item_one, i1|
items.each_with_index.map do |item_two, i2|
next if item_one == item_two
next if i1 < i2
Move.new number, number + 1, [item_one, item_two]
end
end.flatten.compact
end
def two_item_down_moves
return [] if number == 1
items.each_with_index.map do |item_one, i1|
items.each_with_index.map do |item_two, i2|
next if item_one == item_two
next if i1 < i2
Move.new number, number - 1, [item_one, item_two]
end
end.flatten.compact
end
end
class Item
attr_reader :element
def initialize(element)
@element = element
end
def self.for(string)
case string.split[1]
when "microchip"
Microchip.new string.split[0].split("-")[0]
when "generator"
Generator.new string.split[0]
end
end
def element_abbr
element[0..1].upcase
end
def abbr
"#{element_abbr}#{object_abbr}"
end
def generator?
false
end
def microchip?
false
end
def is_element?(other_element)
element == other_element
end
end
class Microchip < Item
def object_abbr
"M"
end
def valid_on?(floor)
floor.has_matching_generator?(element) || floor.generators.empty?
end
def microchip?
true
end
def to_s
@to_s ||= "#{element} microchip"
end
end
class Generator < Item
def object_abbr
"G"
end
def valid_on?(_)
true
end
def generator?
true
end
def to_s
@to_s ||= "#{element} generator"
end
end
class Move
attr_reader :from, :to, :with
def initialize(from, to, with)
@from = from
@to = to
@with = with
end
def to_s
"From #{from} to #{to} with #{with.map(&:to_s)}"
end
end
input = STDIN.read.chomp
lines = input.split("\n")
column = Column.for(lines)
states = [column]
until states.any?(&:complete?)
states = states.map(&:next_states).flatten.uniq(&:to_s)
puts "next"
end
part_1 = states.find(&:complete?).history.count
puts "Part 1: #{part_1}"