248 lines
4.8 KiB
Ruby
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}"
|