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