summaryrefslogtreecommitdiffstats
path: root/CLI/ruby/lib/game_of_life/generators/input.rb
blob: fbe32782741ab6b55db76fb145f1f4c21f38afd2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 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<Array<True|False>>] 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<Array<True|False>>]
        # @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