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