toy-robot-simulator 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +0 -0
- data/lib/constants/application_constants.rb +6 -0
- data/lib/errors/command.rb +12 -0
- data/lib/errors/robot.rb +6 -0
- data/lib/errors/table.rb +6 -0
- data/lib/helpers/command_helper.rb +30 -0
- data/lib/helpers/command_parser_helper.rb +32 -0
- data/lib/helpers/command_set_loader.rb +22 -0
- data/lib/surface/table.rb +24 -0
- data/lib/surface/table_interface.rb +11 -0
- data/lib/toy_robot/robot.rb +97 -0
- data/lib/toy_robot_controller.rb +61 -0
- data/spec/command_helper_spec.rb +48 -0
- data/spec/command_parser_helper_spec.rb +44 -0
- data/spec/command_set_loader_spec.rb +34 -0
- data/spec/robot_spec.rb +217 -0
- data/spec/spec_helper.rb +108 -0
- data/spec/table_spec.rb +20 -0
- data/spec/toy_robot_controller_spec.rb +103 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2b4b3c0db1ec9db67b04a8c7956260295a9184f566d58226882d889034c27760
|
4
|
+
data.tar.gz: 5ed46e5643fec38a6114029cf43ce63d862c9a94b3c2f221ce3c7c41f8b67916
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 70e4ed4c2e4972bb367fa02e6b4469f1b15393c5250fee6999c1975b18e5f9984ce9c9e63840a581f1738a517e0992a803d612ac5f9101d42041197aab35900d
|
7
|
+
data.tar.gz: 48d8a2823e3c988416442f09f87b3adb14726c7cef432be4ab11e6edd07fa44c6e36916d5d9012829c9f6103f0333f7cafe97426c47b282f876430149fffdf4a
|
data/README.md
ADDED
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class InvalidOrEmptyCommands < StandardError; end
|
5
|
+
class InvalidCommandStreamType < StandardError; end
|
6
|
+
class InvalidCommandType < StandardError; end
|
7
|
+
class NoValidCommandsFound < StandardError; end
|
8
|
+
class PlaceCommandNotFound < StandardError; end
|
9
|
+
class EmptyLocationProvided < StandardError; end
|
10
|
+
class FormatNotSupported < StandardError; end
|
11
|
+
class SourceNotSupported < StandardError; end
|
12
|
+
end
|
data/lib/errors/robot.rb
ADDED
data/lib/errors/table.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../constants/application_constants'
|
4
|
+
# CommandHelper module
|
5
|
+
module CommandHelper
|
6
|
+
def a_valid_place_command?(command)
|
7
|
+
return false if command.match(PLACE_COMMAND_PATTERN).nil?
|
8
|
+
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def split_place_command(place_command)
|
13
|
+
result = place_command.match(PLACE_COMMAND_PATTERN)
|
14
|
+
result&.captures
|
15
|
+
end
|
16
|
+
|
17
|
+
def command_type(command)
|
18
|
+
return if command.nil?
|
19
|
+
return PLACE if command.include? PLACE
|
20
|
+
return command if COMMANDS.include? command
|
21
|
+
end
|
22
|
+
|
23
|
+
def directions
|
24
|
+
DIRECTIONS
|
25
|
+
end
|
26
|
+
|
27
|
+
def commands
|
28
|
+
COMMANDS
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../constants/application_constants'
|
4
|
+
require_relative 'command_helper'
|
5
|
+
# CommandParserHelper module
|
6
|
+
module CommandParserHelper
|
7
|
+
include CommandHelper
|
8
|
+
|
9
|
+
def parse_command(command)
|
10
|
+
type_of_command = command_type(command)
|
11
|
+
return if type_of_command.nil?
|
12
|
+
return if type_of_command == PLACE && a_valid_place_command?(command) == false
|
13
|
+
|
14
|
+
command_object(type_of_command, command_value(command))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def command_value(command)
|
20
|
+
return split_place_command(command) if place_command_and_valid?(command)
|
21
|
+
|
22
|
+
command
|
23
|
+
end
|
24
|
+
|
25
|
+
def command_object(type, value, status = '', error = '')
|
26
|
+
Struct.new(:type, :value, :status, :error).new(type, value, status, error)
|
27
|
+
end
|
28
|
+
|
29
|
+
def place_command_and_valid?(command)
|
30
|
+
command_type(command) == PLACE && a_valid_place_command?(command)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors/command'
|
4
|
+
# CommandSetLoader module
|
5
|
+
module CommandSetLoader
|
6
|
+
def read_commands(location = '', source = 'file', format = 'text')
|
7
|
+
raise Command::FormatNotSupported, 'Only text format is supported' unless format == 'text'
|
8
|
+
raise Command::SourceNotSupported, 'Only file as a source is supported' unless source == 'file'
|
9
|
+
raise Command::EmptyLocationProvided if location.strip == ''
|
10
|
+
|
11
|
+
@location = location
|
12
|
+
read_from_file
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def read_from_file
|
18
|
+
commands = []
|
19
|
+
File.readlines(@location).each { |line| commands << line.strip }
|
20
|
+
commands
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'table_interface'
|
4
|
+
require_relative '../errors/table'
|
5
|
+
module Surface
|
6
|
+
# Table class
|
7
|
+
class Table
|
8
|
+
include TableInterface
|
9
|
+
attr_accessor :rows, :columns, :grid
|
10
|
+
|
11
|
+
def initialize(rows = 5, columns = 5)
|
12
|
+
@grid = Array.new(rows) { Array.new(columns) { 0 } }
|
13
|
+
@rows = rows
|
14
|
+
@columns = columns
|
15
|
+
end
|
16
|
+
|
17
|
+
def can_be_placed?(x, y)
|
18
|
+
return false unless (0..(rows - 1)).include?(x)
|
19
|
+
return false unless (0..(columns - 1)).include?(y)
|
20
|
+
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors/command'
|
4
|
+
require_relative '../errors/robot'
|
5
|
+
module ToyRobot
|
6
|
+
# Robot class
|
7
|
+
class Robot
|
8
|
+
attr_accessor :x, :y, :facing, :placed, :table
|
9
|
+
|
10
|
+
def initialize(table = nil)
|
11
|
+
@placed = false
|
12
|
+
@table = table
|
13
|
+
@x = nil
|
14
|
+
@y = nil
|
15
|
+
@facing = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def place(x, y, facing)
|
19
|
+
return if @placed
|
20
|
+
raise Surface::TableIsNotSet if @table.nil?
|
21
|
+
raise Surface::TableOutOfBound unless @table.can_be_placed?(x, y)
|
22
|
+
raise ToyRobot::InvalidFacing unless DIRECTIONS.include?(facing)
|
23
|
+
|
24
|
+
@x = x
|
25
|
+
@y = y
|
26
|
+
@facing = facing
|
27
|
+
@placed = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def left
|
31
|
+
check_pre_conditions
|
32
|
+
@facing = next_facing('left')
|
33
|
+
end
|
34
|
+
|
35
|
+
def right
|
36
|
+
check_pre_conditions
|
37
|
+
@facing = next_facing('right')
|
38
|
+
end
|
39
|
+
|
40
|
+
def move
|
41
|
+
check_pre_conditions
|
42
|
+
result = calculate_potential_movement(facing)
|
43
|
+
|
44
|
+
x_should_be = result[:x].nil? ? @x : (@x + result[:x])
|
45
|
+
y_should_be = result[:y].nil? ? @y : (@y + result[:y])
|
46
|
+
|
47
|
+
raise Surface::TableOutOfBound unless @table.can_be_placed?(x_should_be, y_should_be)
|
48
|
+
|
49
|
+
@x = x_should_be
|
50
|
+
@y = y_should_be
|
51
|
+
end
|
52
|
+
|
53
|
+
def report(format = 'console')
|
54
|
+
raise ToyRobot::RobotIsNotPlaced unless @placed
|
55
|
+
|
56
|
+
case format
|
57
|
+
when 'console'
|
58
|
+
to_s
|
59
|
+
else
|
60
|
+
{ x: @x, y: @y, facing: @facing }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def calculate_potential_movement(facing)
|
65
|
+
case facing
|
66
|
+
when 'NORTH'
|
67
|
+
{ x: nil, y: 1 }
|
68
|
+
when 'WEST'
|
69
|
+
{ x: -1, y: nil }
|
70
|
+
when 'SOUTH'
|
71
|
+
{ x: nil, y: -1 }
|
72
|
+
when 'EAST'
|
73
|
+
{ x: 1, y: nil }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
"#{@x},#{@y},#{@facing}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def next_facing(arrow)
|
84
|
+
return DIRECTIONS.last if @facing == DIRECTIONS.first && arrow == 'left'
|
85
|
+
return DIRECTIONS.first if @facing == DIRECTIONS.last && arrow == 'right'
|
86
|
+
|
87
|
+
increment_factor = arrow == 'left' ? -1 : 1
|
88
|
+
new_index = DIRECTIONS.index(@facing) + increment_factor
|
89
|
+
DIRECTIONS[new_index]
|
90
|
+
end
|
91
|
+
|
92
|
+
def check_pre_conditions
|
93
|
+
raise Surface::TableIsNotSet if @table.nil?
|
94
|
+
raise ToyRobot::RobotIsNotPlaced unless @placed
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'toy_robot/robot'
|
4
|
+
require_relative 'surface/table'
|
5
|
+
require_relative 'helpers/command_parser_helper'
|
6
|
+
require_relative 'errors/command'
|
7
|
+
# ToyRobotController module and main entry of the program
|
8
|
+
module ToyRobotController
|
9
|
+
class << self
|
10
|
+
attr_accessor :robot, :commands
|
11
|
+
include CommandParserHelper
|
12
|
+
|
13
|
+
def init(commands_stream, table_size = 5)
|
14
|
+
validate_command_stream(commands_stream)
|
15
|
+
|
16
|
+
@commands_stream = commands_stream
|
17
|
+
@table = Surface::Table.new(table_size, table_size)
|
18
|
+
@robot = ToyRobot::Robot.new(@table)
|
19
|
+
@commands = []
|
20
|
+
populate_commands
|
21
|
+
end
|
22
|
+
|
23
|
+
def report(format = 'console')
|
24
|
+
@robot.report(format)
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute_commands
|
28
|
+
raise Command::NoValidCommandsFound if @commands.size.zero?
|
29
|
+
raise Command::PlaceCommandNotFound if @commands.select { |c| c.type == 'PLACE' }.size.zero?
|
30
|
+
|
31
|
+
@commands.each { |command| send_command_to_robot(command) }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def send_command_to_robot(command)
|
37
|
+
case command.type
|
38
|
+
when 'PLACE'
|
39
|
+
@robot.place(command.value[0].to_i, command.value[1].to_i, command.value[2])
|
40
|
+
when 'MOVE', 'RIGHT', 'LEFT', 'REPORT'
|
41
|
+
@robot.send(command.type.downcase)
|
42
|
+
end
|
43
|
+
rescue ToyRobot::RobotIsNotPlaced, Surface::TableOutOfBound => e
|
44
|
+
command.status = 'ignored'
|
45
|
+
command.error = e.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_command_stream(commands_stream)
|
49
|
+
raise Command::InvalidCommandStreamType unless commands_stream.is_a?(Array)
|
50
|
+
raise Command::InvalidOrEmptyCommands if commands_stream.size.zero?
|
51
|
+
raise Command::InvalidCommandType unless commands_stream.all? { |i| i.is_a? String }
|
52
|
+
end
|
53
|
+
|
54
|
+
def populate_commands
|
55
|
+
@commands_stream.each do |command|
|
56
|
+
current_command = parse_command(command)
|
57
|
+
@commands << current_command unless current_command.nil?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe CommandHelper do
|
4
|
+
include CommandHelper
|
5
|
+
|
6
|
+
describe '#command_type' do
|
7
|
+
it { expect(command_type('MOVE')).to eq('MOVE') }
|
8
|
+
it { expect(command_type('LEFT')).to eq('LEFT') }
|
9
|
+
it { expect(command_type('RIGHT')).to eq('RIGHT') }
|
10
|
+
it { expect(command_type('REPORT')).to eq('REPORT') }
|
11
|
+
it { expect(command_type('SOMETHING')).to eq(nil) }
|
12
|
+
it { expect(command_type('PLACE 0, 0, NORTH')).to eq('PLACE') }
|
13
|
+
it { expect(command_type('PLACE')).to eq('PLACE') }
|
14
|
+
it { expect(command_type(nil)).to eq(nil) }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#valid_place_command?' do
|
18
|
+
context 'when PLACE command is invalid' do
|
19
|
+
it { expect(a_valid_place_command?('RANDOM')).to eq(false) }
|
20
|
+
it { expect(a_valid_place_command?('A, B, PLACE, Y')).to eq(false) }
|
21
|
+
it { expect(a_valid_place_command?('A, PLACE,B, Y')).to eq(false) }
|
22
|
+
it { expect(a_valid_place_command?('A, B, PLACE, Y')).to eq(false) }
|
23
|
+
it { expect(a_valid_place_command?('PLACE')).to eq(false) }
|
24
|
+
it { expect(a_valid_place_command?('PLACE A, B')).to eq(false) }
|
25
|
+
it { expect(a_valid_place_command?('PLACE A, B, F, G')).to eq(false) }
|
26
|
+
it { expect(a_valid_place_command?('PLACE A, B, Y')).to eq(false) }
|
27
|
+
it { expect(a_valid_place_command?('PLACE A, 0, Y')).to eq(false) }
|
28
|
+
it { expect(a_valid_place_command?('PLACE 0, B, Y')).to eq(false) }
|
29
|
+
it { expect(a_valid_place_command?('PLACE 0, 0, Y')).to eq(false) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#split_place_command' do
|
34
|
+
context 'when the PLACE command is valid as per PLACE_COMMAND_PATTERN' do
|
35
|
+
it { expect(split_place_command('PLACE 0, 0, NORTH')).to match_array(%w[0 0 NORTH]) }
|
36
|
+
it { expect(split_place_command('PLACE 20, 0, NORTH')).to match_array(%w[20 0 NORTH]) }
|
37
|
+
it { expect(split_place_command('PLACE 0, 0, WEST')).to match_array(%w[0 0 WEST]) }
|
38
|
+
it { expect(split_place_command('PLACE 0, 0, SOUTH')).to match_array(%w[0 0 SOUTH]) }
|
39
|
+
it { expect(split_place_command('PLACE 100, 0, EAST')).to match_array(%w[100 0 EAST]) }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when the PLACE command is not according to the PLACE_COMMAND_PATTERN' do
|
43
|
+
it { expect(split_place_command('PLACE 0, 0, Y')).to eq(nil) }
|
44
|
+
it { expect(split_place_command('PLACE A, 0, NORTH')).to eq(nil) }
|
45
|
+
it { expect(split_place_command('PLACE B, 0, SOUTH')).to eq(nil) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe CommandParserHelper do
|
4
|
+
include CommandParserHelper
|
5
|
+
|
6
|
+
describe '#parse_command' do
|
7
|
+
let(:struct_blue_print) { { type: 'PLACE', value: %w[0 0 NORTH], status: '', error: '' } }
|
8
|
+
|
9
|
+
context 'when the command is a valid PLACE command' do
|
10
|
+
it 'will return an onject containing type, value, status and error' do
|
11
|
+
expect(parse_command('PLACE 0, 0, NORTH')).to have_attributes(struct_blue_print)
|
12
|
+
end
|
13
|
+
|
14
|
+
it { expect(parse_command('PLACE 0, 0, NORTH').type).to eq('PLACE') }
|
15
|
+
it { expect(parse_command('PLACE 0, 1, NORTH').value).to match_array(%w[0 1 NORTH]) }
|
16
|
+
it { expect(parse_command('PLACE 0, 0, NORTH').status).to eq('') }
|
17
|
+
it { expect(parse_command('PLACE 0, 0, NORTH').error).to eq('') }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when any other valid command is provided' do
|
21
|
+
it { expect(parse_command('RIGHT').type).to eq('RIGHT') }
|
22
|
+
it { expect(parse_command('RIGHT').value).to eq('RIGHT') }
|
23
|
+
it { expect(parse_command('LEFT').type).to eq('LEFT') }
|
24
|
+
it { expect(parse_command('MOVE').type).to eq('MOVE') }
|
25
|
+
it { expect(parse_command('REPORT').type).to eq('REPORT') }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when an invalid command is provided' do
|
29
|
+
it { expect(parse_command('REPORTME')).to eq(nil) }
|
30
|
+
it { expect(parse_command('RIGHTT')).to eq(nil) }
|
31
|
+
it { expect(parse_command('right')).to eq(nil) }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when an invalid PLACE command is provided' do
|
35
|
+
it { expect(parse_command('PLACE')).to eq(nil) }
|
36
|
+
it { expect(parse_command('PLACE A, B')).to eq(nil) }
|
37
|
+
it { expect(parse_command('PLACE A, B, F, G')).to eq(nil) }
|
38
|
+
it { expect(parse_command('PLACE A, B, Y')).to eq(nil) }
|
39
|
+
it { expect(parse_command('PLACE A, 0, Y')).to eq(nil) }
|
40
|
+
it { expect(parse_command('PLACE 0, B, Y')).to eq(nil) }
|
41
|
+
it { expect(parse_command('PLACE 0, 0, Y')).to eq(nil) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe CommandSetLoader do
|
4
|
+
include CommandSetLoader
|
5
|
+
|
6
|
+
describe '#read_commands' do
|
7
|
+
it 'will load the commands from test1.txt file and return array of commands' do
|
8
|
+
commands = read_commands('./test_data/test1.txt')
|
9
|
+
expect(commands).to match_array(['PLACE 0,0,NORTH', 'MOVE', 'REPORT'])
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'will load the commands from test2.txt file and return array of commands' do
|
13
|
+
commands = read_commands('./test_data/test2.txt')
|
14
|
+
expect(commands).to match_array(['PLACE 0,0,NORTH', 'LEFT', 'REPORT'])
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'will load the commands from test3.txt file and return array of commands' do
|
18
|
+
commands = read_commands('./test_data/test3.txt')
|
19
|
+
expect(commands).to match_array(['PLACE 1,2,EAST', 'MOVE', 'MOVE', 'LEFT', 'MOVE', 'REPORT'])
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'will return SourceNotSupported exception if source is not file' do
|
23
|
+
expect do
|
24
|
+
read_commands('./test_data/test3.txt', 'remote')
|
25
|
+
end.to raise_error(Command::SourceNotSupported)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'will return FormatNotSupported exception if source is not text' do
|
29
|
+
expect do
|
30
|
+
read_commands('./test_data/test3.txt', 'file', 'json')
|
31
|
+
end.to raise_error(Command::FormatNotSupported)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/spec/robot_spec.rb
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe ToyRobot::Robot do
|
4
|
+
let(:robot) { described_class.new }
|
5
|
+
let(:table) { Surface::Table.new }
|
6
|
+
let(:robot_with_table) { described_class.new(table) }
|
7
|
+
|
8
|
+
it 'should initialize and set the instance variables' do
|
9
|
+
expect(robot.placed).to eq(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#place' do
|
13
|
+
context 'when table size is 5x5 and x, y are within the table bounds along with valid facing position' do
|
14
|
+
it 'will successfully place the robot onto the table' do
|
15
|
+
robot_with_table.place(0, 0, 'SOUTH')
|
16
|
+
expect(robot_with_table.placed).to eq(true)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when facing value is not valid' do
|
21
|
+
it 'will raise InvalidFacing Exception' do
|
22
|
+
expect { robot_with_table.place(0, 0, 'RANDOM') }.to raise_error(ToyRobot::InvalidFacing)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'robot is already place' do
|
27
|
+
it 'will ignore any other place commands' do
|
28
|
+
robot_with_table.place(0, 0, 'SOUTH')
|
29
|
+
robot_with_table.place(1, 1, 'NORTH')
|
30
|
+
robot_with_table.place(1, 1, 'WEST')
|
31
|
+
robot_with_table.place(1, 1, 'EAST')
|
32
|
+
expect(robot_with_table.x).to eq(0)
|
33
|
+
expect(robot_with_table.y).to eq(0)
|
34
|
+
expect(robot_with_table.facing).to eq('SOUTH')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when coordinates are out of bounds' do
|
39
|
+
it 'will raise TableOutOfBound Exception' do
|
40
|
+
expect { robot_with_table.place(0, 50, 'SOUTH') }.to raise_error(Surface::TableOutOfBound)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when table is nil' do
|
45
|
+
it 'will raise TableIsNotSet Exception' do
|
46
|
+
robot_with_table.table = nil
|
47
|
+
expect { robot_with_table.place(0, 0, 'SOUTH') }.to raise_error(Surface::TableIsNotSet)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#left' do
|
53
|
+
context 'when robot is not placed on the table yet' do
|
54
|
+
it 'raises RobotIsNotPlaced exception' do
|
55
|
+
expect { robot_with_table.left }.to raise_error(ToyRobot::RobotIsNotPlaced)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when table is not set' do
|
60
|
+
it 'raises TableIsNotSet exception' do
|
61
|
+
expect { robot.left }.to raise_error(Surface::TableIsNotSet)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when robot is facing NORTH' do
|
66
|
+
it 'will rotate the robot towards WEST' do
|
67
|
+
robot_with_table.place(0, 0, 'NORTH')
|
68
|
+
robot_with_table.left
|
69
|
+
expect(robot_with_table.facing).to eq('WEST')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when robot is facing WEST' do
|
74
|
+
it 'will rotate the robot towards SOUTH' do
|
75
|
+
robot_with_table.place(0, 0, 'WEST')
|
76
|
+
robot_with_table.left
|
77
|
+
expect(robot_with_table.facing).to eq('SOUTH')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when robot is facing SOUTH' do
|
82
|
+
it 'will rotate the robot towards EAST' do
|
83
|
+
robot_with_table.place(0, 0, 'SOUTH')
|
84
|
+
robot_with_table.left
|
85
|
+
expect(robot_with_table.facing).to eq('EAST')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when robot is facing EAST' do
|
90
|
+
it 'will rotate the robot towards NORTH' do
|
91
|
+
robot_with_table.place(0, 0, 'EAST')
|
92
|
+
robot_with_table.left
|
93
|
+
expect(robot_with_table.facing).to eq('NORTH')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#right' do
|
99
|
+
context 'when robot is not placed on the table yet' do
|
100
|
+
it 'raises RobotIsNotPlaced exception' do
|
101
|
+
expect { robot_with_table.right }.to raise_error(ToyRobot::RobotIsNotPlaced)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when table is not set' do
|
106
|
+
it 'raises TableIsNotSet exception' do
|
107
|
+
expect { robot.right }.to raise_error(Surface::TableIsNotSet)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'when robot is facing WEST' do
|
112
|
+
it 'will rotate the robot towards NORTH' do
|
113
|
+
robot_with_table.place(0, 0, 'WEST')
|
114
|
+
robot_with_table.right
|
115
|
+
expect(robot_with_table.facing).to eq('NORTH')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'when robot is facing NORTH' do
|
120
|
+
it 'will rotate the robot towards EAST' do
|
121
|
+
robot_with_table.place(0, 0, 'NORTH')
|
122
|
+
robot_with_table.right
|
123
|
+
expect(robot_with_table.facing).to eq('EAST')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'when robot is facing EAST' do
|
128
|
+
it 'will rotate the robot towards SOUTH' do
|
129
|
+
robot_with_table.place(0, 0, 'EAST')
|
130
|
+
robot_with_table.right
|
131
|
+
expect(robot_with_table.facing).to eq('SOUTH')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'when robot is facing SOUTH' do
|
136
|
+
it 'will rotate the robot towards WEST' do
|
137
|
+
robot_with_table.place(0, 0, 'SOUTH')
|
138
|
+
robot_with_table.right
|
139
|
+
expect(robot_with_table.facing).to eq('WEST')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#move' do
|
145
|
+
context 'when the table is not set' do
|
146
|
+
it 'raises TableIsNotSet exception' do
|
147
|
+
expect { robot.move }.to raise_error(Surface::TableIsNotSet)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'when the robot is not placed' do
|
152
|
+
it 'raises TableIsNotSet exception' do
|
153
|
+
expect { robot_with_table.move }.to raise_error(ToyRobot::RobotIsNotPlaced)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'when robot is placed at 0,0 NORTH' do
|
158
|
+
it 'will result in 0, 1 NORTH' do
|
159
|
+
robot_with_table.place(0, 0, 'NORTH')
|
160
|
+
robot_with_table.move
|
161
|
+
expect(robot_with_table.x).to eq(0)
|
162
|
+
expect(robot_with_table.y).to eq(1)
|
163
|
+
expect(robot_with_table.facing).to eq('NORTH')
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'when robot is placed at 0,4 NORTH' do
|
168
|
+
it 'raises TableOutOfBound exception' do
|
169
|
+
robot_with_table.place(0, 4, 'NORTH')
|
170
|
+
expect { robot_with_table.move }.to raise_error(Surface::TableOutOfBound)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe '#report' do
|
176
|
+
it 'will report the current coordinates and facing of robot wherever it is placed' do
|
177
|
+
robot_with_table.place(0, 4, 'NORTH')
|
178
|
+
expect(robot_with_table.report).to eq('0,4,NORTH')
|
179
|
+
end
|
180
|
+
|
181
|
+
context 'When the robot is not placed onto the table' do
|
182
|
+
it 'will rais RobotIsNotPlaced exception' do
|
183
|
+
expect { robot_with_table.report }.to raise_error(ToyRobot::RobotIsNotPlaced)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe '#calculate_potential_movement' do
|
189
|
+
context 'when facing NORTH' do
|
190
|
+
it 'should do one step positive in y direction' do
|
191
|
+
result = robot_with_table.calculate_potential_movement('NORTH')
|
192
|
+
expect(result[:y]).to eq(1)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context 'when facing WEST' do
|
197
|
+
it 'should do one step positive in y direction' do
|
198
|
+
result = robot_with_table.calculate_potential_movement('WEST')
|
199
|
+
expect(result[:x]).to eq(-1)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'when facing SOUTH' do
|
204
|
+
it 'should do one step positive in y direction' do
|
205
|
+
result = robot_with_table.calculate_potential_movement('SOUTH')
|
206
|
+
expect(result[:y]).to eq(-1)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'when facing EAST' do
|
211
|
+
it 'should do one step positive in y direction' do
|
212
|
+
result = robot_with_table.calculate_potential_movement('EAST')
|
213
|
+
expect(result[:x]).to eq(1)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
4
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
5
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
6
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
7
|
+
# files.
|
8
|
+
#
|
9
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
10
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
11
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
12
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
13
|
+
# a separate helper file that requires the additional dependencies and performs
|
14
|
+
# the additional setup, and require it from the spec files that actually need
|
15
|
+
# it.
|
16
|
+
#
|
17
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
18
|
+
require_relative '../lib/toy_robot/robot'
|
19
|
+
require_relative '../lib/surface/table'
|
20
|
+
require_relative '../lib/toy_robot_controller'
|
21
|
+
require_relative '../lib/helpers/command_helper'
|
22
|
+
require_relative '../lib/helpers/command_parser_helper'
|
23
|
+
require_relative '../lib/helpers/command_set_loader'
|
24
|
+
require 'byebug'
|
25
|
+
# require 'byebug'
|
26
|
+
RSpec.configure do |config|
|
27
|
+
# rspec-expectations config goes here. You can use an alternate
|
28
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
29
|
+
# assertions if you prefer.
|
30
|
+
config.expect_with :rspec do |expectations|
|
31
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
32
|
+
# and `failure_message` of custom matchers include text for helper methods
|
33
|
+
# defined using `chain`, e.g.:
|
34
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
35
|
+
# # => "be bigger than 2 and smaller than 4"
|
36
|
+
# ...rather than:
|
37
|
+
# # => "be bigger than 2"
|
38
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
39
|
+
end
|
40
|
+
|
41
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
42
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
43
|
+
config.mock_with :rspec do |mocks|
|
44
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
45
|
+
# a real object. This is generally recommended, and will default to
|
46
|
+
# `true` in RSpec 4.
|
47
|
+
mocks.verify_partial_doubles = true
|
48
|
+
end
|
49
|
+
|
50
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
51
|
+
# have no way to turn it off -- the option exists only for backwards
|
52
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
53
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
54
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
55
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
56
|
+
|
57
|
+
# The settings below are suggested to provide a good initial experience
|
58
|
+
# with RSpec, but feel free to customize to your heart's content.
|
59
|
+
# # This allows you to limit a spec run to individual examples or groups
|
60
|
+
# # you care about by tagging them with `:focus` metadata. When nothing
|
61
|
+
# # is tagged with `:focus`, all examples get run. RSpec also provides
|
62
|
+
# # aliases for `it`, `describe`, and `context` that include `:focus`
|
63
|
+
# # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
64
|
+
# config.filter_run_when_matching :focus
|
65
|
+
#
|
66
|
+
# # Allows RSpec to persist some state between runs in order to support
|
67
|
+
# # the `--only-failures` and `--next-failure` CLI options. We recommend
|
68
|
+
# # you configure your source control system to ignore this file.
|
69
|
+
# config.example_status_persistence_file_path = "spec/examples.txt"
|
70
|
+
#
|
71
|
+
# # Limits the available syntax to the non-monkey patched syntax that is
|
72
|
+
# # recommended. For more details, see:
|
73
|
+
# # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
74
|
+
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
75
|
+
# # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
76
|
+
# config.disable_monkey_patching!
|
77
|
+
#
|
78
|
+
# # This setting enables warnings. It's recommended, but in some cases may
|
79
|
+
# # be too noisy due to issues in dependencies.
|
80
|
+
# config.warnings = true
|
81
|
+
#
|
82
|
+
# # Many RSpec users commonly either run the entire suite or an individual
|
83
|
+
# # file, and it's useful to allow more verbose output when running an
|
84
|
+
# # individual spec file.
|
85
|
+
# if config.files_to_run.one?
|
86
|
+
# # Use the documentation formatter for detailed output,
|
87
|
+
# # unless a formatter has already been configured
|
88
|
+
# # (e.g. via a command-line flag).
|
89
|
+
# config.default_formatter = "doc"
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# # Print the 10 slowest examples and example groups at the
|
93
|
+
# # end of the spec run, to help surface which specs are running
|
94
|
+
# # particularly slow.
|
95
|
+
# config.profile_examples = 10
|
96
|
+
#
|
97
|
+
# # Run specs in random order to surface order dependencies. If you find an
|
98
|
+
# # order dependency and want to debug it, you can fix the order by providing
|
99
|
+
# # the seed, which is printed after each run.
|
100
|
+
# # --seed 1234
|
101
|
+
# config.order = :random
|
102
|
+
#
|
103
|
+
# # Seed global randomization in this process using the `--seed` CLI option.
|
104
|
+
# # Setting this allows you to use `--seed` to deterministically reproduce
|
105
|
+
# # test failures related to randomization by passing the same `--seed` value
|
106
|
+
# # as the one that triggered the failure.
|
107
|
+
# Kernel.srand config.seed
|
108
|
+
end
|
data/spec/table_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Surface::Table do
|
4
|
+
let(:table) { described_class.new }
|
5
|
+
|
6
|
+
it 'should initialize and set the instance variables' do
|
7
|
+
expect(table.rows).to eq(5)
|
8
|
+
expect(table.columns).to eq(5)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#can_be_placed?' do
|
12
|
+
context 'when table is 5x5' do
|
13
|
+
it { expect(table.can_be_placed?(4, 4)).to eq(true) }
|
14
|
+
it { expect(table.can_be_placed?(0, 0)).to eq(true) }
|
15
|
+
it { expect(table.can_be_placed?(4, 5)).to eq(false) }
|
16
|
+
it { expect(table.can_be_placed?(5, 5)).to eq(false) }
|
17
|
+
it { expect(table.can_be_placed?(-5, 5)).to eq(false) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe ToyRobotController do
|
4
|
+
let(:controller) { described_class }
|
5
|
+
|
6
|
+
describe '.init' do
|
7
|
+
it 'will instanciate the robot' do
|
8
|
+
comamnds_stream = ['PLACE 0,0,NORTH', 'MOVE', 'REPORT']
|
9
|
+
controller.init(comamnds_stream)
|
10
|
+
expect(controller.robot).to be_an_instance_of(ToyRobot::Robot)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'will populate the commands with valid commands only' do
|
14
|
+
comamnds_stream = ['PLACE 0,0,NORTH', 'MOVE', 'REPORT', 'RANDOM Command']
|
15
|
+
controller.init(comamnds_stream)
|
16
|
+
expect(controller.commands.map(&:value)).to match_array([%w[0 0 NORTH], 'MOVE', 'REPORT'])
|
17
|
+
end
|
18
|
+
context 'when command steams type is not Array' do
|
19
|
+
it 'will raise InvalidCommandStreamType exception' do
|
20
|
+
expect { controller.init('') }.to raise_error(Command::InvalidCommandStreamType)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when command steams is empty' do
|
25
|
+
it 'will raise InvalidOrEmptyCommands exception' do
|
26
|
+
expect { controller.init([]) }.to raise_error(Command::InvalidOrEmptyCommands)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when command steams Array contains non String types' do
|
31
|
+
it 'will raise InvalidOrEmptyCommands exception' do
|
32
|
+
comamnds_stream = [1, 'PLACE 0,0,NORTH', 'REPORT']
|
33
|
+
expect { controller.init(comamnds_stream) }.to raise_error(Command::InvalidCommandType)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.execute_commands' do
|
39
|
+
context 'when all commands are invalid' do
|
40
|
+
it 'will raise NoValidCommandsFound exception' do
|
41
|
+
comamnds_stream = ['RANDOM Command', 'adfadfadf', 'adfrtrett']
|
42
|
+
controller.init(comamnds_stream)
|
43
|
+
expect { controller.execute_commands }.to raise_error(Command::NoValidCommandsFound)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'will ignore all commands before PLACE' do
|
48
|
+
controller.init(['MOVE', 'REPORT', 'PLACE 1,1,NORTH', 'MOVE', 'REPORT'])
|
49
|
+
controller.execute_commands
|
50
|
+
expect(controller.commands.select { |c| c.status == 'ignored' }.size).to eq(2)
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when PLACE command is not provided' do
|
54
|
+
it 'will raise PlaceCommandNotFound exception' do
|
55
|
+
controller.init(%w[MOVE MOVE LEFT MOVE REPORT])
|
56
|
+
expect { controller.execute_commands }.to raise_error(Command::PlaceCommandNotFound)
|
57
|
+
end
|
58
|
+
it 'will raise PlaceCommandNotFound exception with a PLACE command without X,Y,Facing' do
|
59
|
+
controller.init(%w[PLACE MOVE MOVE LEFT MOVE REPORT])
|
60
|
+
expect { controller.execute_commands }.to raise_error(Command::PlaceCommandNotFound)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '.report' do
|
66
|
+
context 'when command sequence is PLACE 0,0,NORTH MOVE REPORT ' do
|
67
|
+
it 'will move the robot to 0,1,NORTH' do
|
68
|
+
controller.init(['PLACE 0,0,NORTH', 'MOVE', 'REPORT'])
|
69
|
+
controller.execute_commands
|
70
|
+
expect(controller.report).to eq('0,1,NORTH')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when command sequence is PLACE 1,2,EAST MOVE MOVE LEFT MOVE REPORT' do
|
75
|
+
it 'will move the robot to 3,3,NORTH' do
|
76
|
+
controller.init(['PLACE 1,2,EAST', 'MOVE', 'MOVE', 'LEFT', 'MOVE', 'REPORT'])
|
77
|
+
controller.execute_commands
|
78
|
+
expect(controller.report).to eq('3,3,NORTH')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
context 'when command sequence is PLACE 1,2,WEST MOVE MOVE LEFT MOVE REPORT' do
|
82
|
+
it 'will move the robot to 0,1,SOUTH' do
|
83
|
+
controller.init(['PLACE 1,2,WEST', 'MOVE', 'MOVE', 'LEFT', 'MOVE', 'REPORT'])
|
84
|
+
controller.execute_commands
|
85
|
+
expect(controller.report).to eq('0,1,SOUTH')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when command sequence contains only PLACE command' do
|
90
|
+
it 'will report where the robot was PLACED' do
|
91
|
+
controller.init(['PLACE 0,0,SOUTH'])
|
92
|
+
controller.execute_commands
|
93
|
+
expect(controller.report).to eq('0,0,SOUTH')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
context 'when PLACE command is not provided' do
|
97
|
+
it 'will raise RobotIsNotPlaced exception' do
|
98
|
+
controller.init(%w[MOVE MOVE LEFT MOVE REPORT])
|
99
|
+
expect { controller.report }.to raise_error(ToyRobot::RobotIsNotPlaced)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: toy-robot-simulator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Salman Sohail
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-08-27 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Let the Toy Robot move onto the table without dying.
|
14
|
+
email: salmansohail@agilis-lab.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- lib/constants/application_constants.rb
|
21
|
+
- lib/errors/command.rb
|
22
|
+
- lib/errors/robot.rb
|
23
|
+
- lib/errors/table.rb
|
24
|
+
- lib/helpers/command_helper.rb
|
25
|
+
- lib/helpers/command_parser_helper.rb
|
26
|
+
- lib/helpers/command_set_loader.rb
|
27
|
+
- lib/surface/table.rb
|
28
|
+
- lib/surface/table_interface.rb
|
29
|
+
- lib/toy_robot/robot.rb
|
30
|
+
- lib/toy_robot_controller.rb
|
31
|
+
- spec/command_helper_spec.rb
|
32
|
+
- spec/command_parser_helper_spec.rb
|
33
|
+
- spec/command_set_loader_spec.rb
|
34
|
+
- spec/robot_spec.rb
|
35
|
+
- spec/spec_helper.rb
|
36
|
+
- spec/table_spec.rb
|
37
|
+
- spec/toy_robot_controller_spec.rb
|
38
|
+
homepage: https://github.com/saluminati/toy-robot
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata: {}
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 2.3.0
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubygems_version: 3.0.3
|
58
|
+
signing_key:
|
59
|
+
specification_version: 4
|
60
|
+
summary: Ruby gem for Toy Robot test
|
61
|
+
test_files:
|
62
|
+
- spec/robot_spec.rb
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
- spec/command_set_loader_spec.rb
|
65
|
+
- spec/toy_robot_controller_spec.rb
|
66
|
+
- spec/command_parser_helper_spec.rb
|
67
|
+
- spec/table_spec.rb
|
68
|
+
- spec/command_helper_spec.rb
|