maux_robot 0.0.2
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/LICENSE +674 -0
- data/README.md +104 -0
- data/bin/maux_robot +8 -0
- data/lib/maux_robot.rb +5 -0
- data/lib/maux_robot/cli.rb +61 -0
- data/lib/maux_robot/position.rb +49 -0
- data/lib/maux_robot/robot.rb +41 -0
- data/lib/maux_robot/table.rb +17 -0
- data/lib/maux_robot/version.rb +19 -0
- data/spec/maux_robot/cli_spec.rb +71 -0
- data/spec/maux_robot/position_spec.rb +144 -0
- data/spec/maux_robot/robot_spec.rb +97 -0
- data/spec/maux_robot/table_spec.rb +19 -0
- metadata +84 -0
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# maux_robot
|
2
|
+
|
3
|
+
Maux version of a Toy Robot Simulator in Ruby
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
* The application is a simulation of a toy robot moving
|
8
|
+
on a square tabletop, of dimensions 5 units x 5 units.
|
9
|
+
* There are no other obstructions on the table surface.
|
10
|
+
* The robot is free to roam around the surface of the table,
|
11
|
+
but must be prevented from falling to destruction.
|
12
|
+
Any movement that would result in the robot falling
|
13
|
+
from the table must be prevented, however further
|
14
|
+
valid movement commands must still be allowed.
|
15
|
+
|
16
|
+
## Task
|
17
|
+
|
18
|
+
This is an application that can read in commands of the following form:
|
19
|
+
|
20
|
+
```
|
21
|
+
PLACE X,Y,F
|
22
|
+
MOVE
|
23
|
+
LEFT
|
24
|
+
RIGHT
|
25
|
+
REPORT
|
26
|
+
```
|
27
|
+
|
28
|
+
* `PLACE` will put the toy robot on the table in position X,Y
|
29
|
+
and facing `NORTH`, `SOUTH`, `EAST` or `WEST`.
|
30
|
+
* The origin (0,0) can be considered to be the `SOUTH WEST` most corner.
|
31
|
+
* The first valid command to the robot is a `PLACE` command,
|
32
|
+
after that, any sequence of commands may be issued, in any order,
|
33
|
+
including another `PLACE` command.
|
34
|
+
The application should discard all commands in the sequence
|
35
|
+
until a valid `PLACE `command has been executed.
|
36
|
+
* `MOVE` will move the toy robot one unit forward
|
37
|
+
in the direction it is currently facing.
|
38
|
+
* `LEFT` and `RIGHT` will rotate the robot 90 degrees
|
39
|
+
in the specified direction
|
40
|
+
without changing the position of the robot.
|
41
|
+
* `REPORT` will announce the X,Y and F of the robot.
|
42
|
+
|
43
|
+
* A robot that is not on the table ignores the `MOVE`, `LEFT`, `RIGHT` and `REPORT` commands.
|
44
|
+
|
45
|
+
## Constraints
|
46
|
+
|
47
|
+
The toy robot does not fall off the table during movement.
|
48
|
+
This also includes the initial placement of the toy robot.
|
49
|
+
Any move that would cause the robot to fall is ignored.
|
50
|
+
|
51
|
+
## Installation
|
52
|
+
|
53
|
+
```
|
54
|
+
gem install maux_robot
|
55
|
+
```
|
56
|
+
|
57
|
+
## Execution
|
58
|
+
|
59
|
+
Example Input and Output:
|
60
|
+
|
61
|
+
```
|
62
|
+
# Example a
|
63
|
+
echo "PLACE 0,0,NORTH
|
64
|
+
MOVE
|
65
|
+
REPORT" | maux_robot
|
66
|
+
# Output: 0,1,NORTH
|
67
|
+
```
|
68
|
+
|
69
|
+
|
70
|
+
```
|
71
|
+
# Example b
|
72
|
+
echo "PLACE 0,0,NORTH
|
73
|
+
LEFT
|
74
|
+
REPORT" | maux_robot
|
75
|
+
# Output: 0,0,WEST
|
76
|
+
```
|
77
|
+
|
78
|
+
|
79
|
+
```
|
80
|
+
# Example c
|
81
|
+
echo "PLACE 1,2,EAST
|
82
|
+
MOVE
|
83
|
+
MOVE
|
84
|
+
LEFT
|
85
|
+
MOVE
|
86
|
+
REPORT" | maux_robot
|
87
|
+
# Output: 3,3,NORTH
|
88
|
+
```
|
89
|
+
|
90
|
+
```
|
91
|
+
# Example d
|
92
|
+
cat script_file | maux_robot
|
93
|
+
```
|
94
|
+
|
95
|
+
## Acknowledgement
|
96
|
+
|
97
|
+
The Toy Robot Challenge was originally formulated by [Jon Eaves](https://twitter.com/joneaves)
|
98
|
+
|
99
|
+
## Author
|
100
|
+
|
101
|
+
**Mauricio Vieira (mauriciovieira)**
|
102
|
+
+ <http://mauriciovieira.net>
|
103
|
+
+ <https://twitter.com/mauriciovieira>
|
104
|
+
+ <https://github.com/mauriciovieira>
|
data/bin/maux_robot
ADDED
data/lib/maux_robot.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MauxRobot
|
4
|
+
|
5
|
+
# The CLI is a class responsible of handling command line interface
|
6
|
+
class CLI
|
7
|
+
ALLOWED_ORDERS = %i[left right move place report].freeze
|
8
|
+
|
9
|
+
attr_reader :robot
|
10
|
+
def initialize
|
11
|
+
@robot = Robot.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Receives everything from standard input and executes
|
15
|
+
def run
|
16
|
+
run_script(STDIN.read)
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_script(all_input)
|
20
|
+
all_input.split("\n").each do |line_input|
|
21
|
+
command = parse(line_input)
|
22
|
+
execute(command) if command
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(line_input)
|
27
|
+
clean_input = line_input.strip.squeeze(' ').split(' ')
|
28
|
+
order = sanitize_order(clean_input.shift)
|
29
|
+
return unless order
|
30
|
+
|
31
|
+
command = { order: order }
|
32
|
+
|
33
|
+
if clean_input.any?
|
34
|
+
arguments = sanitize_arguments(clean_input.join(''))
|
35
|
+
command[:arguments] = arguments
|
36
|
+
end
|
37
|
+
|
38
|
+
command
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute(command)
|
42
|
+
if command[:arguments].nil?
|
43
|
+
@robot.send(command[:order])
|
44
|
+
else
|
45
|
+
@robot.send(command[:order], command[:arguments])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def sanitize_order(input_string)
|
52
|
+
order = input_string.downcase.to_sym
|
53
|
+
order if ALLOWED_ORDERS.include?(order)
|
54
|
+
end
|
55
|
+
|
56
|
+
def sanitize_arguments(arguments)
|
57
|
+
arguments = arguments.delete(' ').split(',')
|
58
|
+
{ x: arguments[0], y: arguments[1], face: arguments[2] }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'maux_robot'
|
4
|
+
|
5
|
+
module MauxRobot
|
6
|
+
|
7
|
+
# This class represents the Robot position on the table
|
8
|
+
class Position
|
9
|
+
attr_reader :x, :y, :face
|
10
|
+
|
11
|
+
POSSIBLE_DIRECTIONS = [:north, :west, :south, :east]
|
12
|
+
POSSIBLE_MOVEMENTS = {
|
13
|
+
north: { x: 0, y: 1 },
|
14
|
+
west: { x: -1, y: 0 },
|
15
|
+
south: { x: 0, y: -1 },
|
16
|
+
east: { x: 1, y: 0 },
|
17
|
+
}
|
18
|
+
|
19
|
+
def initialize(x, y, face)
|
20
|
+
@x = x.to_i
|
21
|
+
@y = y.to_i
|
22
|
+
@face = face&.downcase&.to_sym || :invalid
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid_direction?
|
26
|
+
POSSIBLE_DIRECTIONS.include?(@face)
|
27
|
+
end
|
28
|
+
|
29
|
+
def turn_left
|
30
|
+
next_direction_index = ( POSSIBLE_DIRECTIONS.index(@face) + 1 ) % 4
|
31
|
+
@face = POSSIBLE_DIRECTIONS[next_direction_index]
|
32
|
+
end
|
33
|
+
|
34
|
+
def turn_right
|
35
|
+
next_direction_index = ( POSSIBLE_DIRECTIONS.index(@face) - 1 )
|
36
|
+
@face = POSSIBLE_DIRECTIONS[next_direction_index]
|
37
|
+
end
|
38
|
+
|
39
|
+
def forward_position
|
40
|
+
x = @x + POSSIBLE_MOVEMENTS[@face][:x]
|
41
|
+
y = @y + POSSIBLE_MOVEMENTS[@face][:y]
|
42
|
+
Position.new(x, y, @face)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
"#{@x},#{@y},#{@face.upcase.to_s}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MauxRobot
|
4
|
+
|
5
|
+
# The main class. It executes the actions using its table and position
|
6
|
+
class Robot
|
7
|
+
attr_reader :position
|
8
|
+
|
9
|
+
def initialize(table=MauxRobot::Table.new)
|
10
|
+
@table = table
|
11
|
+
end
|
12
|
+
|
13
|
+
def place(x:, y:, face:)
|
14
|
+
position = MauxRobot::Position.new(x, y, face)
|
15
|
+
|
16
|
+
if position.valid_direction? and @table.contains?(position)
|
17
|
+
@position = position
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def left
|
22
|
+
@position&.turn_left
|
23
|
+
end
|
24
|
+
|
25
|
+
def right
|
26
|
+
@position&.turn_right
|
27
|
+
end
|
28
|
+
|
29
|
+
def move
|
30
|
+
next_position = @position.forward_position
|
31
|
+
|
32
|
+
if @table.contains?(next_position)
|
33
|
+
@position = next_position
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def report
|
38
|
+
puts @position if @position
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MauxRobot
|
4
|
+
|
5
|
+
# This class represents a table according to simulation rules
|
6
|
+
class Table
|
7
|
+
def initialize(x: [0, 4], y: [0, 4])
|
8
|
+
@x = x
|
9
|
+
@y = y
|
10
|
+
end
|
11
|
+
|
12
|
+
def contains?(position)
|
13
|
+
position.x.between?(@x[0], @x[1]) &&
|
14
|
+
position.y.between?(@y[0], @y[1])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MauxRobot
|
4
|
+
# This module holds the MauxRobot version information.
|
5
|
+
module Version
|
6
|
+
STRING = '0.0.2'.freeze
|
7
|
+
|
8
|
+
MSG = '%s (using Parser %s, running on %s %s %s)'.freeze
|
9
|
+
|
10
|
+
def self.version(debug = false)
|
11
|
+
if debug
|
12
|
+
format(MSG, STRING, Parser::VERSION,
|
13
|
+
RUBY_ENGINE, RUBY_VERSION, RUBY_PLATFORM)
|
14
|
+
else
|
15
|
+
STRING
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'maux_robot'
|
4
|
+
|
5
|
+
describe MauxRobot::CLI do
|
6
|
+
describe '#parse' do
|
7
|
+
context 'valid orders' do
|
8
|
+
it '#report' do
|
9
|
+
expect(subject.parse('REPORT')).to eq(order: :report)
|
10
|
+
end
|
11
|
+
|
12
|
+
it '#move' do
|
13
|
+
expect(subject.parse('MOVE')).to eq(order: :move)
|
14
|
+
end
|
15
|
+
|
16
|
+
it '#left' do
|
17
|
+
expect(subject.parse('LEFT')).to eq(order: :left)
|
18
|
+
end
|
19
|
+
|
20
|
+
it '#right' do
|
21
|
+
expect(subject.parse('RIGHT')).to eq(order: :right)
|
22
|
+
end
|
23
|
+
|
24
|
+
it '#place' do
|
25
|
+
expect(subject.parse('PLACE 0,3,WEST')).to eq(order: :place, arguments: { x: '0', y: '3', face: 'WEST' })
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'orders with spaces and downcase' do
|
30
|
+
it '#report' do
|
31
|
+
expect(subject.parse(' repoRT')).to eq(order: :report)
|
32
|
+
end
|
33
|
+
|
34
|
+
it '#move' do
|
35
|
+
expect(subject.parse('move ')).to eq(order: :move)
|
36
|
+
end
|
37
|
+
|
38
|
+
it '#left' do
|
39
|
+
expect(subject.parse(' LefT ')).to eq(order: :left)
|
40
|
+
end
|
41
|
+
|
42
|
+
it '#right' do
|
43
|
+
expect(subject.parse(' rigHt')).to eq(order: :right)
|
44
|
+
end
|
45
|
+
|
46
|
+
it '#place' do
|
47
|
+
expect(subject.parse('PLACE 2 , 1 , NOrth')).to eq(order: :place, arguments: { x: '2', y: '1', face: 'NOrth' })
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'ignores anything else' do
|
52
|
+
expect(subject.parse('blablabla balbla')).to eq(order: nil)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#execute' do
|
57
|
+
context 'commands without arguments' do
|
58
|
+
it 'sends proper message to robot' do
|
59
|
+
expect(subject.robot).to receive(:move)
|
60
|
+
subject.execute(order: :move)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'place' do
|
65
|
+
it 'send place command along with arguments' do
|
66
|
+
expect(subject.robot).to receive(:place).with(x: '0', y: '2', face: 'EAST')
|
67
|
+
subject.execute(order: :place, arguments: { x: '0', y: '2', face: 'EAST' })
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'maux_robot'
|
4
|
+
|
5
|
+
describe MauxRobot::Position do
|
6
|
+
describe '#initialize' do
|
7
|
+
context 'given text input' do
|
8
|
+
it 'should normalize arguments' do
|
9
|
+
position = MauxRobot::Position.new('0', '1', 'SOUTH')
|
10
|
+
|
11
|
+
expect(position.x).to eq(0)
|
12
|
+
expect(position.y).to eq(1)
|
13
|
+
expect(position.face).to eq(:south)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#valid_direction?' do
|
19
|
+
context 'given a possible diretion' do
|
20
|
+
it 'should be valid' do
|
21
|
+
position = MauxRobot::Position.new(5, 0, :south)
|
22
|
+
|
23
|
+
expect(position.valid_direction?).to eq(true)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'given a not possible direction' do
|
28
|
+
it 'should not be valid ' do
|
29
|
+
position = MauxRobot::Position.new(1, 1, :south_west)
|
30
|
+
|
31
|
+
expect(position.valid_direction?).to eq(false)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'rotation' do
|
37
|
+
let(:position) { MauxRobot::Position.new(2, 2, :north) }
|
38
|
+
|
39
|
+
context '#turn_left' do
|
40
|
+
it 'should rotate counter-clockwise' do
|
41
|
+
position.turn_left
|
42
|
+
expect(position.face).to eq(:west)
|
43
|
+
|
44
|
+
position.turn_left
|
45
|
+
expect(position.face).to eq(:south)
|
46
|
+
|
47
|
+
position.turn_left
|
48
|
+
expect(position.face).to eq(:east)
|
49
|
+
|
50
|
+
position.turn_left
|
51
|
+
expect(position.face).to eq(:north)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context '#turn_right' do
|
56
|
+
it 'should rotate clockwise' do
|
57
|
+
position.turn_right
|
58
|
+
expect(position.face).to eq(:east)
|
59
|
+
|
60
|
+
position.turn_right
|
61
|
+
expect(position.face).to eq(:south)
|
62
|
+
|
63
|
+
position.turn_right
|
64
|
+
expect(position.face).to eq(:west)
|
65
|
+
|
66
|
+
position.turn_right
|
67
|
+
expect(position.face).to eq(:north)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#forward_position' do
|
73
|
+
context 'facing north' do
|
74
|
+
let(:position) { MauxRobot::Position.new(5, 0, :north) }
|
75
|
+
|
76
|
+
it 'should return a new position 1 step upwards' do
|
77
|
+
new_position = position.forward_position
|
78
|
+
expect(position.x).to eq(5)
|
79
|
+
expect(position.y).to eq(0)
|
80
|
+
expect(new_position.x).to eq(5)
|
81
|
+
expect(new_position.y).to eq(1)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'facing west' do
|
86
|
+
let(:position) { MauxRobot::Position.new(2, 1, :west) }
|
87
|
+
|
88
|
+
it 'should return a new position 1 step leftwards' do
|
89
|
+
new_position = position.forward_position
|
90
|
+
expect(position.x).to eq(2)
|
91
|
+
expect(position.y).to eq(1)
|
92
|
+
expect(new_position.x).to eq(1)
|
93
|
+
expect(new_position.y).to eq(1)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'facing south' do
|
98
|
+
let(:position) { MauxRobot::Position.new(1, 3, :south) }
|
99
|
+
|
100
|
+
it 'should return a new position 1 step downwards' do
|
101
|
+
new_position = position.forward_position
|
102
|
+
expect(position.x).to eq(1)
|
103
|
+
expect(position.y).to eq(3)
|
104
|
+
expect(new_position.x).to eq(1)
|
105
|
+
expect(new_position.y).to eq(2)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'facing east' do
|
110
|
+
let(:position) { MauxRobot::Position.new(2, 0, :east) }
|
111
|
+
|
112
|
+
it 'should return a new position 1 step rightwards' do
|
113
|
+
new_position = position.forward_position
|
114
|
+
expect(position.x).to eq(2)
|
115
|
+
expect(position.y).to eq(0)
|
116
|
+
expect(new_position.x).to eq(3)
|
117
|
+
expect(new_position.y).to eq(0)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#to_s' do
|
123
|
+
context 'if x, y and face are defined' do
|
124
|
+
let(:position) { MauxRobot::Position.new(5, 0, :north) }
|
125
|
+
it 'returns X,Y,FACE' do
|
126
|
+
expect(position.to_s).to eq('5,0,NORTH')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'if only face is defined' do
|
131
|
+
let(:position) { MauxRobot::Position.new(nil, nil, 'SOUTH') }
|
132
|
+
it 'returns 0,0,FACE' do
|
133
|
+
expect(position.to_s).to eq('0,0,SOUTH')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'if face is not defined' do
|
138
|
+
let(:position) { MauxRobot::Position.new(5, 0, nil) }
|
139
|
+
it 'returns X,Y,INVALID' do
|
140
|
+
expect(position.to_s).to eq('5,0,INVALID')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|