# frozen_string_literal: true require "open-uri" require "socket" module GameOfLife module Generators # Generate a new Universe/Board from a parsed local file or remote URL/file module Input class << self # Generate a new Universe/Board from a parsed local file or remote URL/file # @param options [Hash] CLI options to generate initial conditions # @option options [String] "input" path to a local file or URL to open # @option options [Numeric] "width" (floored) and used as the width of the universe # @option options [Numeric] "height" (floored) and used as the height of the universe # @return [GameOfLife::Universe] populated with the parsed input # @see ::GameOfLife::Generators::Input.generate_input_data # @see ::GameOfLife::Generators::Input.populate def new(options) width = options["width"].to_i height = options["height"].to_i uri = options["input"] input = generate_input_data(uri: uri) populate(input: input, width: width, height: height) end # Generate the seeding data for populating the {GameOfLife::Universe} from input URI # @param uri [String] inputh path to local file or URL to open # @return [Array>] covering the defined size of universe # @see ::GameOfLife::Generators::Input.populate private def generate_input_data(uri:) # By design, it's intentional that we use URI.open to to allow seamless file and url access raw = URI.open(uri).read.split("\n") # rubocop:disable Security/Open # Parse the file and convert it to a boolean 2D array # where any alpha numeric character is considered a living cell (true) raw.map! { |row| row.chars.map { |char| char.match?(/[[:alnum:]]/) } } rescue ::OpenURI::HTTPError, ::SocketError raise GameOfLife::Error, "URL isn't avaialable, please check it again and make sure the URL is correct" rescue ::Errno::ENOENT raise GameOfLife::Error, "File isn't avaialable, please check it again and make sure the path to the input file is correct" end # Populate the {GameOfLife::Universe} with reference to the parsed input 2D array (True/False) # @param input [Array>] # @param width [Integer] width of the generated cyclic universe # @param height [Integer] height of the generated cyclic universe # @return [GameOfLife::Universe] populated with the parsed input # @see ::GameOfLife::Generators::Input.generate_input_data private def populate(input:, width:, height:) # Generate a universe based on the parsed raw file and fill it with the cells universe = GameOfLife::Universe.new(height: height, width: width) (0...height).each do |i| (0...width).each do |j| cell = { x: j, y: i, alive: input.fetch(i, nil)&.fetch(j, nil) } universe[i][j] = ::GameOfLife::Cell.new(cell) end end universe end end end end end