toyrobot 0.0.1 → 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 +4 -4
- data/CONTRIBUTING.md +2 -2
- data/README.md +166 -10
- data/lib/toy_robot/application.rb +1 -13
- data/lib/toy_robot/board.rb +1 -1
- data/lib/toy_robot/command/parser.rb +6 -3
- data/lib/toy_robot/command/parser/base.rb +4 -25
- data/lib/toy_robot/command/parser/left.rb +12 -0
- data/lib/toy_robot/command/parser/move.rb +12 -0
- data/lib/toy_robot/command/parser/place.rb +3 -4
- data/lib/toy_robot/command/parser/report.rb +12 -0
- data/lib/toy_robot/command/parser/right.rb +12 -0
- data/lib/toy_robot/controller.rb +32 -0
- data/lib/toy_robot/factory.rb +6 -18
- data/lib/toy_robot/pose.rb +19 -5
- data/lib/toy_robot/robot.rb +11 -15
- data/lib/toy_robot/view.rb +10 -12
- data/test/board_double.rb +5 -0
- data/test/integration/test_factory.rb +2 -2
- data/test/integration/test_toy_robot.rb +1 -1
- data/test/pose_double.rb +23 -0
- data/test/test_board.rb +7 -14
- data/test/test_board_double.rb +13 -0
- data/test/test_board_interface.rb +15 -0
- data/test/{test_command_matcher.rb → test_command_parser.rb} +6 -6
- data/test/{test_command_matcher_interface.rb → test_command_parser_interface.rb} +5 -5
- data/test/test_command_parser_left.rb +20 -0
- data/test/{test_command_matcher_move.rb → test_command_parser_move.rb} +5 -5
- data/test/{test_command_matcher_place.rb → test_command_parser_place.rb} +10 -10
- data/test/{test_command_matcher_report.rb → test_command_parser_report.rb} +5 -5
- data/test/test_command_parser_right.rb +20 -0
- data/test/test_controller.rb +64 -0
- data/test/test_pose.rb +16 -20
- data/test/test_pose_double.rb +15 -0
- data/test/test_pose_interface.rb +26 -0
- data/test/test_reporter_interface.rb +13 -1
- data/test/test_robot.rb +70 -27
- data/toyrobot.gemspec +1 -1
- metadata +35 -26
- data/lib/toy_robot/command/base.rb +0 -5
- data/lib/toy_robot/command/token.rb +0 -11
- data/lib/toy_robot/placement.rb +0 -34
- data/lib/toy_robot/robot_controller.rb +0 -35
- data/test/test_command.rb +0 -22
- data/test/test_command_matcher_left.rb +0 -20
- data/test/test_command_matcher_right.rb +0 -20
- data/test/test_placement.rb +0 -94
- data/test/test_robot_controller.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b082d368cf8887b0daebff1875a1729bbbe8681d
|
4
|
+
data.tar.gz: 0a6ea773808f34d2b0bea8514533aea97782f2cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8de85cff36714f10481d3bfc98ec23908bf84361a91e41e4f2d2e93b091b54c41345298dcb47d750a9b9a27fad796e7db3dc91e0cf57619f4737d1a889fa25af
|
7
|
+
data.tar.gz: 1534b36b3916fbaa236d5cbf3484cf2e814bb1709ec505dca46cceb76a81cd240262467f0befbc20e65a162e88755b985497175e37147b2da8909b171410bce7
|
data/CONTRIBUTING.md
CHANGED
@@ -4,7 +4,7 @@ Fork, then clone the repo:
|
|
4
4
|
|
5
5
|
git clone git@github.com:your-username/toy-robot-simulator.git
|
6
6
|
|
7
|
-
Check you ruby and development
|
7
|
+
Check you ruby and development dependencies versions:
|
8
8
|
|
9
9
|
$ ruby -v
|
10
10
|
|
@@ -18,7 +18,7 @@ Make your change. Add tests for your change. Make the tests pass:
|
|
18
18
|
|
19
19
|
rake test:all
|
20
20
|
|
21
|
-
Push to your fork and submit a pull request. Some things that are equal parts
|
21
|
+
Push to your fork and submit a pull request. Some things that are equal parts necessary/epic:
|
22
22
|
|
23
23
|
* Write tests.
|
24
24
|
|
data/README.md
CHANGED
@@ -8,18 +8,18 @@ A simulation of a toy robot moving on a square tabletop.
|
|
8
8
|
|
9
9
|
via RubyGems:
|
10
10
|
|
11
|
-
$ gem install
|
11
|
+
$ gem install toyrobot
|
12
12
|
|
13
13
|
|
14
14
|
## Usage
|
15
15
|
|
16
16
|
Start the simulator in interactive mode:
|
17
17
|
|
18
|
-
$
|
18
|
+
$ toyrobot
|
19
19
|
|
20
|
-
Or pipe
|
20
|
+
Or pipe in a file with commands:
|
21
21
|
|
22
|
-
$
|
22
|
+
$ toyrobot < path/to/file
|
23
23
|
|
24
24
|
An example file with commands:
|
25
25
|
|
@@ -89,7 +89,7 @@ Response:
|
|
89
89
|
* The application does not provide any graphical output showing the movement of the toy robot.
|
90
90
|
|
91
91
|
|
92
|
-
##
|
92
|
+
## Dependencies
|
93
93
|
|
94
94
|
ruby version ~> 2.1.0p0
|
95
95
|
|
@@ -106,14 +106,14 @@ To learn how to install ruby visit [ruby-lang.org/en/installation/](https://www.
|
|
106
106
|
|
107
107
|
* OSX 10.8.5, ruby 2.1.2p95
|
108
108
|
|
109
|
-
Development
|
109
|
+
Development dependencies:
|
110
110
|
|
111
111
|
rake ~> 10.3
|
112
112
|
minitest ~> 4.7.5
|
113
113
|
|
114
114
|
To install them along the gem:
|
115
115
|
|
116
|
-
$ gem install --dev
|
116
|
+
$ gem install --dev toyrobot
|
117
117
|
|
118
118
|
### Compatible environments
|
119
119
|
|
@@ -121,7 +121,7 @@ To install them along the gem:
|
|
121
121
|
|
122
122
|
* Ubuntu 12.04 x32, ruby 2.1.0p0
|
123
123
|
|
124
|
-
### Incompatible
|
124
|
+
### Incompatible environments
|
125
125
|
|
126
126
|
* ruby < 2.1.0
|
127
127
|
|
@@ -139,10 +139,166 @@ To run all tests:
|
|
139
139
|
|
140
140
|
$ rake test:all
|
141
141
|
|
142
|
+
## Design
|
143
|
+
|
144
|
+
The application flows in the following way:
|
145
|
+
|
146
|
+
#### 1 Launch
|
147
|
+
|
148
|
+
$ toyrobot < path/to/file
|
149
|
+
|
150
|
+
##### 1.1 bin/toyrobot
|
151
|
+
|
152
|
+
The `toyrobot` in the command is a ruby executable in your load path. This executable contains the following lines:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
require 'toy_robot'
|
156
|
+
ToyRobot::Application.new(ARGV).run
|
157
|
+
```
|
158
|
+
|
159
|
+
The first line requires the file `./lib/toy_robot.rb` which loads the library. The second line creates and instance of the application and passes the command-line arguments to it.
|
160
|
+
|
161
|
+
##### 1.2 lib/toy_robot/application.rb
|
162
|
+
|
163
|
+
The `app.run()` method reads in robot_commands from the input source (defaults to `$stdin`) and passes them to the parser. The parser returns (might) a command_like_object which is used to message the controller.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
def run(options = nil)
|
167
|
+
loop do
|
168
|
+
raw_input = input.gets
|
169
|
+
break unless raw_input
|
170
|
+
raw_input.chomp!
|
171
|
+
command = parser.parse(raw_input)
|
172
|
+
controller.send(command.msg, command.args) if command
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
##### 1.2.1 lib/toy_robot/command/parser.rb
|
178
|
+
|
179
|
+
`parser.parse()` delegates the job of matching robot_commands and extracting arguments to the `Command::Parser::(Place/Move/Right/Left/Report)` objects. Each will receive the robot_command and try to match it. If it can, it will build a command_like_object and return it. The `parser.parse()` method will return the command_like_object as soon as a match is found.
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
def self.parse(string)
|
183
|
+
all.each do |parser|
|
184
|
+
command = parser.build_with_match(string)
|
185
|
+
return command if command
|
186
|
+
end
|
187
|
+
nil
|
188
|
+
end
|
189
|
+
```
|
190
|
+
|
191
|
+
##### 1.2.2 lib/toy_robot/controller.rb
|
192
|
+
|
193
|
+
The controller will receive a valid message (`place`,`move`,`left`,`right`,`report`) and delegate it to the correct object (`robot`/`view`)
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
def place(*args)
|
197
|
+
robot.place(*args)
|
198
|
+
end
|
199
|
+
|
200
|
+
def move(*_)
|
201
|
+
robot.move
|
202
|
+
end
|
203
|
+
|
204
|
+
def left(*_)
|
205
|
+
robot.left
|
206
|
+
end
|
207
|
+
|
208
|
+
def right(*_)
|
209
|
+
robot.right
|
210
|
+
end
|
211
|
+
|
212
|
+
def report(*_)
|
213
|
+
view.report
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
##### 1.2.2.1 lib/toy_robot/robot.rb
|
218
|
+
|
219
|
+
This is the entry point for all robot_commands in the system. Suffice it to say that from here on, every command will interact with a combination of the following objects:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
ToyRobot::Robot
|
223
|
+
```
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
ToyRobot::Pose
|
227
|
+
```
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
ToyRobot::Board
|
231
|
+
```
|
232
|
+
|
233
|
+
##### 1.2.2.2 lib/toy_robot/view.rb
|
234
|
+
|
235
|
+
This is the output point of all robot_commands. The application only displays feedback for the report command, so the code in here is very basic, and by default writes to `$stdout`.
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
def report
|
239
|
+
output.puts format_report(robot.report)
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
def format_report(report)
|
245
|
+
"#{report[:x]},#{report[:y]},#{report[:orientation].upcase}" if report
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
#### 2 Exit
|
250
|
+
|
251
|
+
After all the commands are read, parsed and executed, the application exits.
|
252
|
+
|
142
253
|
## Discussion
|
143
254
|
|
144
|
-
|
255
|
+
Over-engineered. On the one hand it attempts to solve a static, straight-forward problem. On the other hand it tries to demonstrate coding and object-oriented design skills. This generates tensions which I feel are incompatible and creep into the code base.
|
256
|
+
|
257
|
+
Most of the files/classes/modules/methods/lines in the source code are sensible to the argument that they are overkill for the problem in question.
|
258
|
+
|
259
|
+
In the end I tried to keep it simple and apply OOD, while feeling like the person to whom 'every problem seems like a nail'.
|
260
|
+
|
261
|
+
### The Not So Good Parts
|
262
|
+
|
263
|
+
#### Code
|
264
|
+
|
265
|
+
##### `Command::Parser#parse`
|
266
|
+
|
267
|
+
This method/module has too many hidden dependency to work (aka magic) and is not clear enough how you would add more parsers to it.
|
268
|
+
|
269
|
+
The parser is intended to be extended by adding constants to the `Command::Parser` name space, and each constant should be an instance of the Command::Parser::Base class. This is not self evident and thus bad.
|
270
|
+
|
271
|
+
On the other hand, these constants need to be require by the application, which either means including them in the `/lib/toy_robot/command/parser.rb` or having a automatic loading of files under the `lib/toy_robot` directory.
|
272
|
+
|
273
|
+
##### `Pose`
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
EAST = :east
|
277
|
+
NORTH = :north
|
278
|
+
WEST = :west
|
279
|
+
SOUTH = :south
|
280
|
+
```
|
281
|
+
|
282
|
+
These constants seem unnecessary for a small lib and a straight-forward concept. Maybe integer would suffice and make calculations easier.
|
283
|
+
|
284
|
+
```
|
285
|
+
Pose#rotate!
|
286
|
+
```
|
287
|
+
|
288
|
+
This method takes `(degrees)` as arguments which makes sense from a physical perspective, but none in the context of this application. Input comes in the form of four strings, which are the only valid states for `@orientation`. The whole application is converting back and forth between degrees and valid orientations. This complexity is not necessary.
|
289
|
+
|
290
|
+
On the flip side, converting the whole application to degrees would make `#rotate!` much simpler.
|
291
|
+
|
292
|
+
#### Naming
|
293
|
+
|
294
|
+
`Command::Parser::Base` is not specific, maybe `Command::Parser::BaseMatcher` would be better.
|
295
|
+
|
296
|
+
#### Testing
|
297
|
+
|
298
|
+
`ToyRobot::Application` is only integration tested.
|
299
|
+
|
300
|
+
`bin/toyrobot` is not tested.
|
145
301
|
|
146
302
|
## Contributing
|
147
303
|
|
148
|
-
View [CONTRIBUTING.md](https://github.com/matiasanaya/toy-robot-simulator/blob/master/CONTRIBUTING.md)
|
304
|
+
View [CONTRIBUTING.md](https://github.com/matiasanaya/toy-robot-simulator/blob/master/CONTRIBUTING.md)
|
@@ -14,25 +14,13 @@ module ToyRobot
|
|
14
14
|
raw_input = input.gets
|
15
15
|
break unless raw_input
|
16
16
|
raw_input.chomp!
|
17
|
-
debug(raw_input)
|
18
17
|
command = parser.parse(raw_input)
|
19
|
-
controller.
|
18
|
+
controller.send(command.msg, command.args) if command
|
20
19
|
end
|
21
20
|
end
|
22
21
|
|
23
22
|
private
|
24
23
|
|
25
24
|
attr_reader :input, :parser, :controller
|
26
|
-
|
27
|
-
def debug(raw_input)
|
28
|
-
case raw_input
|
29
|
-
when /\AR(EPORT_DEBUG)*\z/
|
30
|
-
puts controller.instance_variable_get(:@robot).inspect
|
31
|
-
when /\AE(XIT)*\z/
|
32
|
-
puts 'Have a nice day, bye :)'
|
33
|
-
puts '...'
|
34
|
-
exit(false)
|
35
|
-
end
|
36
|
-
end
|
37
25
|
end
|
38
26
|
end
|
data/lib/toy_robot/board.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
-
require_relative 'parser/base'
|
2
1
|
require_relative 'parser/place'
|
2
|
+
require_relative 'parser/move'
|
3
|
+
require_relative 'parser/right'
|
4
|
+
require_relative 'parser/left'
|
5
|
+
require_relative 'parser/report'
|
3
6
|
|
4
7
|
module ToyRobot
|
5
8
|
module Command
|
6
9
|
module Parser
|
7
10
|
def self.parse(string)
|
8
|
-
all.each do |
|
9
|
-
command =
|
11
|
+
all.each do |parser|
|
12
|
+
command = parser.build_with_match(string)
|
10
13
|
return command if command
|
11
14
|
end
|
12
15
|
nil
|
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require_relative '../base'
|
1
|
+
require 'ostruct'
|
3
2
|
|
4
3
|
module ToyRobot
|
5
4
|
module Command
|
@@ -7,7 +6,7 @@ module ToyRobot
|
|
7
6
|
class Base
|
8
7
|
def initialize(args)
|
9
8
|
@regex = args[:regex]
|
10
|
-
@
|
9
|
+
@msg = args[:msg]
|
11
10
|
@args_extractor = args[:args_extractor]
|
12
11
|
end
|
13
12
|
|
@@ -17,7 +16,7 @@ module ToyRobot
|
|
17
16
|
|
18
17
|
private
|
19
18
|
|
20
|
-
attr_reader :regex, :
|
19
|
+
attr_reader :regex, :msg, :args_extractor
|
21
20
|
|
22
21
|
def match?(string)
|
23
22
|
string =~ regex
|
@@ -30,29 +29,9 @@ module ToyRobot
|
|
30
29
|
end
|
31
30
|
|
32
31
|
def build_command(string)
|
33
|
-
|
32
|
+
OpenStruct.new(msg: msg, args:extract_args(string)) if match?(string)
|
34
33
|
end
|
35
34
|
end
|
36
|
-
|
37
|
-
Move = Base.new(
|
38
|
-
token: Command::Token::MOVE,
|
39
|
-
regex: /\AMOVE\z/
|
40
|
-
)
|
41
|
-
|
42
|
-
Right = Base.new(
|
43
|
-
token: Command::Token::RIGHT,
|
44
|
-
regex: /\ARIGHT\z/
|
45
|
-
)
|
46
|
-
|
47
|
-
Left = Base.new(
|
48
|
-
token: Command::Token::LEFT,
|
49
|
-
regex: /\ALEFT\z/
|
50
|
-
)
|
51
|
-
|
52
|
-
Report = Base.new(
|
53
|
-
token: Command::Token::REPORT,
|
54
|
-
regex: /\AREPORT\z/
|
55
|
-
)
|
56
35
|
end
|
57
36
|
end
|
58
37
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require_relative 'base'
|
2
|
-
require_relative '../../pose'
|
3
2
|
|
4
3
|
module ToyRobot
|
5
4
|
module Command
|
@@ -20,15 +19,15 @@ module ToyRobot
|
|
20
19
|
place_args_extractor = lambda do |raw_command|
|
21
20
|
x, y, f = raw_command.split.pop.split(',')
|
22
21
|
|
23
|
-
|
22
|
+
{
|
24
23
|
x: x.to_i,
|
25
24
|
y: y.to_i,
|
26
25
|
orientation: constantize_orientation.call(f)
|
27
|
-
|
26
|
+
}
|
28
27
|
end
|
29
28
|
|
30
29
|
Place = Base.new(
|
31
|
-
|
30
|
+
msg: :place,
|
32
31
|
regex: /\APLACE \d+,\d+,(NORTH|SOUTH|EAST|WEST)\z/,
|
33
32
|
args_extractor: place_args_extractor
|
34
33
|
)
|