maux_robot 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift("#{__dir__}/../lib")
4
+
5
+ require 'maux_robot'
6
+
7
+ cli = MauxRobot::CLI.new
8
+ cli.run
data/lib/maux_robot.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'maux_robot/version'
2
+ require 'maux_robot/cli'
3
+ require 'maux_robot/position'
4
+ require 'maux_robot/table'
5
+ require 'maux_robot/robot'
@@ -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