toyrobot 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +2 -2
  3. data/README.md +166 -10
  4. data/lib/toy_robot/application.rb +1 -13
  5. data/lib/toy_robot/board.rb +1 -1
  6. data/lib/toy_robot/command/parser.rb +6 -3
  7. data/lib/toy_robot/command/parser/base.rb +4 -25
  8. data/lib/toy_robot/command/parser/left.rb +12 -0
  9. data/lib/toy_robot/command/parser/move.rb +12 -0
  10. data/lib/toy_robot/command/parser/place.rb +3 -4
  11. data/lib/toy_robot/command/parser/report.rb +12 -0
  12. data/lib/toy_robot/command/parser/right.rb +12 -0
  13. data/lib/toy_robot/controller.rb +32 -0
  14. data/lib/toy_robot/factory.rb +6 -18
  15. data/lib/toy_robot/pose.rb +19 -5
  16. data/lib/toy_robot/robot.rb +11 -15
  17. data/lib/toy_robot/view.rb +10 -12
  18. data/test/board_double.rb +5 -0
  19. data/test/integration/test_factory.rb +2 -2
  20. data/test/integration/test_toy_robot.rb +1 -1
  21. data/test/pose_double.rb +23 -0
  22. data/test/test_board.rb +7 -14
  23. data/test/test_board_double.rb +13 -0
  24. data/test/test_board_interface.rb +15 -0
  25. data/test/{test_command_matcher.rb → test_command_parser.rb} +6 -6
  26. data/test/{test_command_matcher_interface.rb → test_command_parser_interface.rb} +5 -5
  27. data/test/test_command_parser_left.rb +20 -0
  28. data/test/{test_command_matcher_move.rb → test_command_parser_move.rb} +5 -5
  29. data/test/{test_command_matcher_place.rb → test_command_parser_place.rb} +10 -10
  30. data/test/{test_command_matcher_report.rb → test_command_parser_report.rb} +5 -5
  31. data/test/test_command_parser_right.rb +20 -0
  32. data/test/test_controller.rb +64 -0
  33. data/test/test_pose.rb +16 -20
  34. data/test/test_pose_double.rb +15 -0
  35. data/test/test_pose_interface.rb +26 -0
  36. data/test/test_reporter_interface.rb +13 -1
  37. data/test/test_robot.rb +70 -27
  38. data/toyrobot.gemspec +1 -1
  39. metadata +35 -26
  40. data/lib/toy_robot/command/base.rb +0 -5
  41. data/lib/toy_robot/command/token.rb +0 -11
  42. data/lib/toy_robot/placement.rb +0 -34
  43. data/lib/toy_robot/robot_controller.rb +0 -35
  44. data/test/test_command.rb +0 -22
  45. data/test/test_command_matcher_left.rb +0 -20
  46. data/test/test_command_matcher_right.rb +0 -20
  47. data/test/test_placement.rb +0 -94
  48. data/test/test_robot_controller.rb +0 -62
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 38160974bafdf9946e5e665c58b1bf00dccaf40e
4
- data.tar.gz: b9a29c8e8df15252a2453517cccda9057818502c
3
+ metadata.gz: b082d368cf8887b0daebff1875a1729bbbe8681d
4
+ data.tar.gz: 0a6ea773808f34d2b0bea8514533aea97782f2cd
5
5
  SHA512:
6
- metadata.gz: 05978ae19f8a401719dc7f825127c36575efe3c116bcd8ae5613aad01e74687869e8ca0f889ce79c2976bf122bd2196993f730d1479054299a734f2c86b582c7
7
- data.tar.gz: d24163a31a50ad221ebf1c00ca844d9a3bf08fcbb7cdb3a9f68b27f9070f0d6378c5bc3b5aa6eac3b43dad7119eae2a8dbef28c279199e25f6c3d785af13918a
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 dependancies versions:
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 necesary/epic:
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 toy_robot
11
+ $ gem install toyrobot
12
12
 
13
13
 
14
14
  ## Usage
15
15
 
16
16
  Start the simulator in interactive mode:
17
17
 
18
- $ toy_robot
18
+ $ toyrobot
19
19
 
20
- Or pipe it a file with commands:
20
+ Or pipe in a file with commands:
21
21
 
22
- $ toy_robot < path/to/file
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
- ## Dependancies
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 dependancies:
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 toy_robot
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 enviroments
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
- This piece of software is over-engineered. Over engineering does not feel good.
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.input(command) if command
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
@@ -6,7 +6,7 @@ module ToyRobot
6
6
  end
7
7
 
8
8
  def valid_pose?(pose)
9
- pose && (0..x_size).include?(pose.x) && (0..y_size).include?(pose.y)
9
+ pose && (0..x_size).include?(pose[:x]) && (0..y_size).include?(pose[:y])
10
10
  end
11
11
 
12
12
  private
@@ -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 |matcher|
9
- command = matcher.build_with_match(string)
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
- require_relative '../token'
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
- @token = args[:token]
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, :token, :args_extractor
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
- Command::Base.new(token, extract_args(string)) if match?(string)
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
@@ -0,0 +1,12 @@
1
+ require_relative 'base'
2
+
3
+ module ToyRobot
4
+ module Command
5
+ module Parser
6
+ Left = Base.new(
7
+ msg: :left,
8
+ regex: /\ALEFT\z/
9
+ )
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'base'
2
+
3
+ module ToyRobot
4
+ module Command
5
+ module Parser
6
+ Move = Base.new(
7
+ msg: :move,
8
+ regex: /\AMOVE\z/
9
+ )
10
+ end
11
+ end
12
+ 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
- Pose.new(
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
- token: Command::Token::PLACE,
30
+ msg: :place,
32
31
  regex: /\APLACE \d+,\d+,(NORTH|SOUTH|EAST|WEST)\z/,
33
32
  args_extractor: place_args_extractor
34
33
  )
@@ -0,0 +1,12 @@
1
+ require_relative 'base'
2
+
3
+ module ToyRobot
4
+ module Command
5
+ module Parser
6
+ Report = Base.new(
7
+ msg: :report,
8
+ regex: /\AREPORT\z/
9
+ )
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'base'
2
+
3
+ module ToyRobot
4
+ module Command
5
+ module Parser
6
+ Right = Base.new(
7
+ msg: :right,
8
+ regex: /\ARIGHT\z/
9
+ )
10
+ end
11
+ end
12
+ end