robodog 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/CONTRIBUTING.md +27 -0
- data/LICENSE +25 -0
- data/README.md +302 -0
- data/bin/robodog +5 -0
- data/data/fail_input_a.txt +5 -0
- data/data/valid_input_a.txt +5 -0
- data/lib/robo_dog.rb +1 -0
- data/lib/robo_dog/application.rb +37 -0
- data/lib/robo_dog/paddock.rb +31 -0
- data/lib/robo_dog/parser.rb +53 -0
- data/lib/robo_dog/pose.rb +97 -0
- data/lib/robo_dog/pose/orientation.rb +38 -0
- data/lib/robo_dog/robot.rb +88 -0
- data/lib/robo_dog/simulation.rb +60 -0
- data/robodog.gemspec +19 -0
- data/spec/application_spec.rb +24 -0
- data/spec/features/robo_dog_spec.rb +107 -0
- data/spec/orientation_spec.rb +37 -0
- data/spec/paddock_spec.rb +50 -0
- data/spec/parser_spec.rb +74 -0
- data/spec/pose_spec.rb +164 -0
- data/spec/robot_spec.rb +166 -0
- data/spec/simulation_spec.rb +131 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1c903f6841736d12811c90affeee2c238b4cbf20
|
4
|
+
data.tar.gz: 291ddf2774970bf08f543ddbbc3114f9609884a8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 371c4f9d545f5fabe6f583c03fe02840250385f28ddecbf2234dd0af0fe36138230fecc39c877b78c0aabb21460a5e22f0017dc4ed924b25d583cd283af655b4
|
7
|
+
data.tar.gz: f8f6b7a7be9ce41fb3a77d44acce0d523c48aba2042fe6e5f998fb9814337643a58793e67e4079ad17cfd6a7bd4dc4147eb4763798c083fa4a634ab49ed651bf
|
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
# Gemfile.lock
|
30
|
+
# .ruby-version
|
31
|
+
# .ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
## How to contribute
|
2
|
+
|
3
|
+
Fork, then clone the repo:
|
4
|
+
|
5
|
+
git clone git@github.com:your-username/robo-dog.git
|
6
|
+
|
7
|
+
Check you ruby and development dependencies versions:
|
8
|
+
|
9
|
+
$ ruby -v
|
10
|
+
|
11
|
+
$ gem list
|
12
|
+
|
13
|
+
Make sure the tests pass:
|
14
|
+
|
15
|
+
rspec
|
16
|
+
|
17
|
+
Make your change. Add tests for your change. Make the tests pass:
|
18
|
+
|
19
|
+
rspec
|
20
|
+
|
21
|
+
Push to your fork and submit a pull request. Some things that are equal parts necessary/epic:
|
22
|
+
|
23
|
+
* Write tests.
|
24
|
+
|
25
|
+
* Follow a sensible style guide. My pick is [this one](https://github.com/bbatsov/ruby-style-guide).
|
26
|
+
|
27
|
+
* Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org>
|
25
|
+
|
data/README.md
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
robo-dog
|
2
|
+
=========
|
3
|
+
|
4
|
+
Fredwina the Farmer's robotic sheep dog simulator.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
via RubyGems:
|
9
|
+
|
10
|
+
$ gem install robodog
|
11
|
+
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Pipe in a file with simulation parameters:
|
16
|
+
|
17
|
+
$ robodog < path/to/file
|
18
|
+
|
19
|
+
An example file:
|
20
|
+
|
21
|
+
5 5
|
22
|
+
1 2 N
|
23
|
+
LMLMLMLMM
|
24
|
+
3 3 E
|
25
|
+
MMRMMRMRRM
|
26
|
+
|
27
|
+
To which the application prints:
|
28
|
+
|
29
|
+
1 3 N
|
30
|
+
5 1 E
|
31
|
+
|
32
|
+
More example files can be found at `data/`
|
33
|
+
|
34
|
+
## Dependencies
|
35
|
+
|
36
|
+
ruby version ~> 2.1.0p0
|
37
|
+
|
38
|
+
To check your version run:
|
39
|
+
|
40
|
+
$ ruby -v
|
41
|
+
|
42
|
+
To learn how to install ruby visit [ruby-lang.org/en/installation/](https://www.ruby-lang.org/en/installation/)
|
43
|
+
|
44
|
+
## Troubleshooting
|
45
|
+
|
46
|
+
### Development environment
|
47
|
+
|
48
|
+
* OSX 10.8.5, ruby 2.1.2p95
|
49
|
+
|
50
|
+
Development dependencies:
|
51
|
+
|
52
|
+
rspec ~> 3.1
|
53
|
+
|
54
|
+
To install them along the gem:
|
55
|
+
|
56
|
+
$ gem install --dev robodog
|
57
|
+
|
58
|
+
### Compatible environments
|
59
|
+
|
60
|
+
* Ubuntu 12.04 x32, ruby 2.1.0p0
|
61
|
+
|
62
|
+
### Incompatible environments
|
63
|
+
|
64
|
+
* ruby < 2.1.0
|
65
|
+
|
66
|
+
### Tests
|
67
|
+
|
68
|
+
To run the specs:
|
69
|
+
|
70
|
+
$ rspec
|
71
|
+
|
72
|
+
To run just the integration tests:
|
73
|
+
|
74
|
+
$ rspec spec/features
|
75
|
+
|
76
|
+
## Overview
|
77
|
+
|
78
|
+
The application is designed to read simulation parameters from `$stdin` and print the output to `$stdout`.
|
79
|
+
|
80
|
+
### Input Format
|
81
|
+
|
82
|
+
The application expects input containing a description of the paddock on which the robotic sheep dogs are to move, and a description of each robotic sheep dogs. The description of each robotic sheep dog has two parts in itself. It must contain a starting position and a sequence of commands to execute.
|
83
|
+
|
84
|
+
#### Paddock
|
85
|
+
|
86
|
+
The paddock description must contain two integers separated by a space, as follows:
|
87
|
+
|
88
|
+
<x_size> <y_size>
|
89
|
+
|
90
|
+
These two integers represent the dimensions of a square paddock.
|
91
|
+
|
92
|
+
Example:
|
93
|
+
|
94
|
+
5 5
|
95
|
+
|
96
|
+
Only one paddock description is allowed per input.
|
97
|
+
|
98
|
+
#### Robot
|
99
|
+
|
100
|
+
The robot part of the input is expected to have a starting position, and a sequence of commands it will execute. There is no limit as to the amount of robots accepted.
|
101
|
+
|
102
|
+
##### Position
|
103
|
+
|
104
|
+
This piece of the input is expected to contain two space-separated integers, plus a space-separated cardinal orientation represented by the strings `N`, `E`, `S` and `W`. The general structure is as follows:
|
105
|
+
|
106
|
+
<x_coordinate> <y_coordinate> <cardinal_orientation>
|
107
|
+
|
108
|
+
* The spaces are validated by the application and thus required.
|
109
|
+
* The `<cardinal_orientation>` is case sensitive.
|
110
|
+
|
111
|
+
Example:
|
112
|
+
|
113
|
+
1 2 N
|
114
|
+
|
115
|
+
##### Commands
|
116
|
+
|
117
|
+
The commands represent a sequence of instructions that the robot will follow in order. The valid commands are `M`, `R` and `L`:
|
118
|
+
|
119
|
+
M # stands for Move and will make the robot advance one position in the direction its facing
|
120
|
+
|
121
|
+
R # stands for Right and will make the robot rotate its orientation 90 degrees clockwise
|
122
|
+
|
123
|
+
L # stands for Left and will make the robot rotate its orientation 90 degrees counter-clockwise
|
124
|
+
|
125
|
+
* The commands should be concatenated into a single string
|
126
|
+
* The commands are case sensitive
|
127
|
+
* Non-valid commands will make the application fail.
|
128
|
+
|
129
|
+
Example:
|
130
|
+
|
131
|
+
LMRLMRLMRLR
|
132
|
+
|
133
|
+
### Output Format
|
134
|
+
|
135
|
+
The simulation's final state will be printed as a results of the application running. The state is represented by the end position of each of the robots. A line is printed per robot and this line follows the same format as the input position line `<x_coordinate> <y_coordinate> <cardinal_orientation>`.
|
136
|
+
|
137
|
+
Example:
|
138
|
+
|
139
|
+
1 3 N
|
140
|
+
5 1 E
|
141
|
+
|
142
|
+
### Constraints
|
143
|
+
|
144
|
+
Sheepdogs are not permitted to bump into each other or run each other over. The program fails at runtime if this happens. Specifically the program will fail if:
|
145
|
+
|
146
|
+
* Two robots are placed on the same starting position.
|
147
|
+
* A robot moves into another robot's position.
|
148
|
+
* A robot tries to move beyond the paddock's limit.
|
149
|
+
|
150
|
+
In case the simulation fails, the following message will be printed:
|
151
|
+
|
152
|
+
Invalid coordinates. This means two robots collided or a robot hit the border of the paddock. (RuntimeError)
|
153
|
+
|
154
|
+
## Design
|
155
|
+
|
156
|
+
The application flows in the following way:
|
157
|
+
|
158
|
+
#### 1 Launch
|
159
|
+
|
160
|
+
$ robodog < data/valid_example_input_a.txt
|
161
|
+
|
162
|
+
##### 1.1 bin/robodog
|
163
|
+
|
164
|
+
The `robodog` in the command is a ruby executable in your load path. This executable contains the following lines:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
require 'robo_dog'
|
168
|
+
RoboDog::Application.build.run
|
169
|
+
```
|
170
|
+
|
171
|
+
The first line requires the file `./lib/robo_dog.rb` which loads the library. The second line creates and instance of the application and runs it.
|
172
|
+
|
173
|
+
##### 1.2 lib/robo_dog/application.rb
|
174
|
+
|
175
|
+
The `#run` method starts by delegating the input parsing to the `parser` object. The `input` by default is `$stdin`.
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
def run(mode = nil)
|
179
|
+
simulation_attrs = parser.parse(input)
|
180
|
+
simulation = simulation_class.new(simulation_attrs)
|
181
|
+
|
182
|
+
simulation.run(mode)
|
183
|
+
puts format(simulation.report)
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
The parser line is:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
simulation_attrs = parser.parse(input)
|
191
|
+
```
|
192
|
+
|
193
|
+
##### 1.2.1 lib/robo_dog/parser.rb
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
def parse(input)
|
197
|
+
paddock = @paddock_parser.call(input)
|
198
|
+
robots = []
|
199
|
+
loop do
|
200
|
+
robot_attrs = @robot_extractor.call(input)
|
201
|
+
break unless robot_attrs
|
202
|
+
robots << @robot_factory.build(robot_attrs)
|
203
|
+
end
|
204
|
+
{
|
205
|
+
paddock: paddock,
|
206
|
+
robots: robots
|
207
|
+
}
|
208
|
+
end
|
209
|
+
```
|
210
|
+
|
211
|
+
The parser knows how to extract the paddock and robot attributes for the simulation. It actually delegates the building of these objects to the `Paddock` and `Robot` class themselves, but extracts the string from the input stream. The first delegation occurs at:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
robots << @robot_factory.build(robot_attrs)
|
215
|
+
```
|
216
|
+
|
217
|
+
The `@robot_factory` object is actually the `Robot` class which knows how to build a instance of itself from the `robot_attrs`.
|
218
|
+
|
219
|
+
After all the parsing is done, this function returns a hash with the correct objects in `:paddock` and `:robots` (an Array of robots)
|
220
|
+
|
221
|
+
##### 1.2.2 lib/robo_dog/simulation.rb
|
222
|
+
|
223
|
+
Next in application.rb is:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
simulation.run(mode)
|
227
|
+
```
|
228
|
+
|
229
|
+
The `#run` message to `Simulation` is responsible for coordinating the robots on the paddock. It has two run modes: `:sequential` and `:turns`.
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
def run(mode = :sequential)
|
233
|
+
mode ||= :sequential
|
234
|
+
warm_up
|
235
|
+
case mode
|
236
|
+
when :sequential
|
237
|
+
robots.each { |r| r.execute(:all) }
|
238
|
+
when :turns
|
239
|
+
run_in_turns
|
240
|
+
end
|
241
|
+
end
|
242
|
+
```
|
243
|
+
|
244
|
+
* In `:sequential` mode each robot will execute all of its commands at once.
|
245
|
+
* In `:turns` mode robots will take turns executing one command at a time.
|
246
|
+
|
247
|
+
This method messages each `Robot` to `#execute`. This guide will not cover how `#execute` is implemented. Just bear in mind that robots can move and rotate, while these movements need to be coordinated to avoid robots running over each other.
|
248
|
+
|
249
|
+
##### 1.2 lib/robo_dog/application.rb
|
250
|
+
|
251
|
+
Once the simulation has run, all that remains is to print the output. This output is formated by the `Application` class and then printed to `$stdout`.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
puts format(simulation.report)
|
255
|
+
```
|
256
|
+
|
257
|
+
#### 2 Exit
|
258
|
+
|
259
|
+
After the output is printed, the application exits.
|
260
|
+
|
261
|
+
## Discussion
|
262
|
+
|
263
|
+
My main design decisions / concerns in no specific order are the following.
|
264
|
+
|
265
|
+
#### Placing
|
266
|
+
|
267
|
+
There is no specification as to the order of the placing of the robots on the paddock. This application assumes that all robots are placed first, failing if they are placed on the same coordinates, and then the commands are executed according to the selected run mode.
|
268
|
+
|
269
|
+
#### Run Mode
|
270
|
+
|
271
|
+
The application has `:sequential` and `:turns` (beta, no command-line support) mode. Should the robots run the commands in turns or all at once?
|
272
|
+
|
273
|
+
This is a relevant question to ask since for the same input file, one run mode might fail while the other one might be successful. There are a couple of examples of these circumstance in `spec/features/robo_dog_spec.rb`
|
274
|
+
|
275
|
+
This issue addresses specifically the case when you order a robot to chase another one. If run sequentially, the first robot will run over the second one. If run in turns, the intended outcome will happen.
|
276
|
+
|
277
|
+
#### Failing
|
278
|
+
|
279
|
+
While the original spec specifies that the application should fail if robots run over each other, it does not specify how or when. In this implementation the application will fail when robots run the following line:
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
self.pose = adj_pose if coordinators.all? { |c| c.valid_coordinates?(adj_pose.coordinates) }
|
283
|
+
```
|
284
|
+
|
285
|
+
since `#valid_coordinates?` is:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
def valid_coordinates?(coordinates)
|
289
|
+
coordinates &&
|
290
|
+
paddock.valid_coordinates?(coordinates) &&
|
291
|
+
robots.all? { |r| r.coordinates != coordinates } ||
|
292
|
+
fail_appropriately
|
293
|
+
end
|
294
|
+
```
|
295
|
+
|
296
|
+
My concern about this decision is that `#valid_coordinates?` failing is not clear enough, this method should return either `true` or `false`. The alternative here is to simply advance the pose, and let know the observer (aka the `Simulation`) of what happened, and the application should fail there.
|
297
|
+
|
298
|
+
In the end the existing approach seemed more sensible because it stops the application in a pre-fail state which can be inspected, and is more flexible in case a decision is made in the future by which robots should just ignore commands that would send them into a invalid coordinate.
|
299
|
+
|
300
|
+
## Contributing
|
301
|
+
|
302
|
+
View [CONTRIBUTING.md](https://github.com/matiasanaya/robo-dog/blob/master/CONTRIBUTING.md)
|
data/bin/robodog
ADDED
data/lib/robo_dog.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'robo_dog/application'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative 'parser'
|
2
|
+
require_relative 'simulation'
|
3
|
+
|
4
|
+
module RoboDog
|
5
|
+
class Application
|
6
|
+
def self.build(input = nil)
|
7
|
+
new(input: input, parser: Parser, simulation_class: Simulation)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(args = {})
|
11
|
+
@input = args[:input] || $stdin
|
12
|
+
@parser = args[:parser]
|
13
|
+
@simulation_class = args[:simulation_class]
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(mode = nil)
|
17
|
+
simulation_attrs = parser.parse(input)
|
18
|
+
simulation = simulation_class.new(simulation_attrs)
|
19
|
+
|
20
|
+
simulation.run(mode)
|
21
|
+
puts format(simulation.report)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :input, :parser, :simulation_class
|
27
|
+
|
28
|
+
def format(report)
|
29
|
+
str = ''
|
30
|
+
report[:robots].each do |r|
|
31
|
+
str << "#{r[:x]} #{r[:y]} #{r[:orientation]}"
|
32
|
+
str << "\n"
|
33
|
+
end
|
34
|
+
str
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|