maux_robot 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|