require 'debug' # Generic FileHandler class FileHandler def initialize(file_path) @file_path = file_path end # Usage: # FileHandler.new('/path_to_file').read_lines do |line| # # process line # end def read_lines File.foreach(@file_path, chomp: true).map do |line| yield(line) end end # Usage: # file_content = FileHandler.new('/path_to_file').read_file( def read_file File.read(@file_path) end end class PuzzleSolver def initialize(file_handler, debug: false) @handler = file_handler @debug = debug end def debug(message) puts message if @debug end def print_debug(message) print message if @debug end def solve raise NotImplementedError, 'Please implement this method in subclasses.' end end class Space # [Symbol] :empty (.), :beam (|), :splitter (^), :start (S) attr_reader :type MAPPING = { start: 'S', splitter: '^', empty: '.', beam: '|' } # @param [String] space either '.', '^', or 'S' def initialize(space) type = MAPPING.key(space) raise "Unknown type #{space.inspect}" if type.nil? @type = type end def is_empty? @type == :empty end def is_splitter? @type == :splitter end def is_start? @type == :start end def is_beam? @type == :beam end def make_beam raise "Type #{@type} cannot be converted to beam" unless is_empty? || is_beam? @type = :beam end def to_s MAPPING[@type] end end class Manifold attr_reader :spaces, :num_rows, :num_columns # @param [<>] an array of arrays of of strings ('S', '.' or '^'), e.g. [['.', 'S', '.'], ['.', '.', '^']] def initialize(manifold) raise "Invalid manifold: 0 rows in #{manifold}" if manifold.length == 0 raise "Invalid manifold: 0 columns in #{manifold}" if manifold[0].length == 0 raise "Invalid manifold: inconsistent number of columns in #{manifold}" unless manifold.all? {|column| column.length == manifold[0].length} @num_rows = manifold.length @num_columns = manifold[0].length @spaces = manifold.map {|row| row.map {|space| Space.new(space)}} end # @return [Grid] def self.from_file(file_path) manifold = [] FileHandler.new(file_path).read_lines do |line| next if line.nil? || line.empty? manifold << line.split('') end Manifold.new(manifold) end # @return [x, y] coordinates of start space def start_coordinates @spaces.each_with_index do |row, x| row.each_with_index do |space, y| return [x, y] if space.is_start? end end raise "No start found in #{@spaces}" end # @retrun [Integer] num_splits # side effect: manifold contains beams def send_beam start_x, _ = start_coordinates num_splits = 0 # fill in the beams of the following rows (start_x + 1...@num_rows).each do |x| row = @spaces[x] row.each_with_index do |space, y| # we only need to do something with current space if the space above is a beam or start, and space is empty above_space = @spaces[x - 1][y] next if space.is_beam? next unless above_space.is_start? || above_space.is_beam? if space.is_empty? # beam passes through space.make_beam next end raise "Unexpected space #{space} at (#{x}, #{y})" unless space.is_splitter? # split beam, so beam to the left and to the right become beams num_splits += 1 @spaces[x][y - 1].make_beam unless y - 1 < 0 @spaces[x][y + 1].make_beam unless y + 1 > @num_columns end end num_splits end def to_s @spaces.map {|row| row.map(&:to_s).join}.join("\n") end end class ManifoldSolver < PuzzleSolver def initialize(file_path, debug: false) @file_path = file_path file_handler = FileHandler.new(@file_path) super(file_handler, debug:) end def solve manifold = Manifold.from_file(@file_path) manifold.send_beam end end if ARGV[0].nil? || ARGV[0].empty? puts "Usage: ruby #{__FILE__} [debug]" exit 1 end file_path = ARGV[0] debug = (ARGV[1] == "debug") # file_handler = FileHandler.new(file_path) ## Puzzle-specific cls = ManifoldSolver ## puts "The answer is: #{cls.new(file_path, debug:).solve}"