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.
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