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
|