192 lines
4.2 KiB
Ruby
192 lines
4.2 KiB
Ruby
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 [<<String>>] 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__} <file_name> [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}"
|