toyrobot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/CONTRIBUTING.md +28 -0
  4. data/LICENSE +24 -0
  5. data/README.md +148 -0
  6. data/Rakefile +21 -0
  7. data/bin/toyrobot +5 -0
  8. data/data/example_input_a.txt +3 -0
  9. data/data/example_input_b.txt +3 -0
  10. data/data/example_input_c.txt +6 -0
  11. data/lib/toy_robot/application.rb +38 -0
  12. data/lib/toy_robot/board.rb +16 -0
  13. data/lib/toy_robot/command/base.rb +5 -0
  14. data/lib/toy_robot/command/parser/base.rb +58 -0
  15. data/lib/toy_robot/command/parser/place.rb +37 -0
  16. data/lib/toy_robot/command/parser.rb +20 -0
  17. data/lib/toy_robot/command/token.rb +11 -0
  18. data/lib/toy_robot/factory.rb +83 -0
  19. data/lib/toy_robot/placement.rb +34 -0
  20. data/lib/toy_robot/pose.rb +79 -0
  21. data/lib/toy_robot/robot.rb +41 -0
  22. data/lib/toy_robot/robot_controller.rb +35 -0
  23. data/lib/toy_robot/view.rb +22 -0
  24. data/lib/toy_robot.rb +1 -0
  25. data/test/integration/test_application.rb +24 -0
  26. data/test/integration/test_factory.rb +18 -0
  27. data/test/integration/test_toy_robot.rb +9 -0
  28. data/test/test_board.rb +31 -0
  29. data/test/test_command.rb +22 -0
  30. data/test/test_command_matcher.rb +32 -0
  31. data/test/test_command_matcher_interface.rb +31 -0
  32. data/test/test_command_matcher_left.rb +20 -0
  33. data/test/test_command_matcher_move.rb +21 -0
  34. data/test/test_command_matcher_place.rb +56 -0
  35. data/test/test_command_matcher_report.rb +21 -0
  36. data/test/test_command_matcher_right.rb +20 -0
  37. data/test/test_placement.rb +94 -0
  38. data/test/test_pose.rb +146 -0
  39. data/test/test_reporter_interface.rb +7 -0
  40. data/test/test_robot.rb +70 -0
  41. data/test/test_robot_controller.rb +62 -0
  42. data/test/test_view.rb +32 -0
  43. data/toyrobot.gemspec +20 -0
  44. metadata +139 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 38160974bafdf9946e5e665c58b1bf00dccaf40e
4
+ data.tar.gz: b9a29c8e8df15252a2453517cccda9057818502c
5
+ SHA512:
6
+ metadata.gz: 05978ae19f8a401719dc7f825127c36575efe3c116bcd8ae5613aad01e74687869e8ca0f889ce79c2976bf122bd2196993f730d1479054299a734f2c86b582c7
7
+ data.tar.gz: d24163a31a50ad221ebf1c00ca844d9a3bf08fcbb7cdb3a9f68b27f9070f0d6378c5bc3b5aa6eac3b43dad7119eae2a8dbef28c279199e25f6c3d785af13918a
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,28 @@
1
+ ## How to contribute
2
+
3
+ Fork, then clone the repo:
4
+
5
+ git clone git@github.com:your-username/toy-robot-simulator.git
6
+
7
+ Check you ruby and development dependancies versions:
8
+
9
+ $ ruby -v
10
+
11
+ $ gem list
12
+
13
+ Make sure the tests pass:
14
+
15
+ rake test:all
16
+
17
+ Make your change. Add tests for your change. Make the tests pass:
18
+
19
+ rake test:all
20
+
21
+ Push to your fork and submit a pull request. Some things that are equal parts necesary/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).
28
+
data/LICENSE ADDED
@@ -0,0 +1,24 @@
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>
data/README.md ADDED
@@ -0,0 +1,148 @@
1
+ toy-robot
2
+ ===================
3
+
4
+ A simulation of a toy robot moving on a square tabletop.
5
+
6
+
7
+ ## Installation
8
+
9
+ via RubyGems:
10
+
11
+ $ gem install toy_robot
12
+
13
+
14
+ ## Usage
15
+
16
+ Start the simulator in interactive mode:
17
+
18
+ $ toy_robot
19
+
20
+ Or pipe it a file with commands:
21
+
22
+ $ toy_robot < path/to/file
23
+
24
+ An example file with commands:
25
+
26
+ PLACE 1,2,EAST
27
+ MOVE
28
+ MOVE
29
+ LEFT
30
+ MOVE
31
+ REPORT
32
+
33
+ To which the simulator will respond:
34
+
35
+ 3,3,NORTH
36
+
37
+ ### Simulation Commands
38
+
39
+ Simulation commands are case-sensitive.
40
+
41
+ #### PLACE
42
+
43
+ Puts the robot on the table in position X,Y and facing `NORTH`, `SOUTH`, `EAST` or `WEST`. The origin (0,0) is considered to be the SOUTH WEST most corner. Usage:
44
+
45
+ PLACE 2,1,WEST
46
+
47
+ #### MOVE
48
+
49
+ Move the robot one unit forward in the direction it is currently facing. Usage:
50
+
51
+ MOVE
52
+
53
+ #### LEFT
54
+
55
+ Rotate the robot 90 degrees counter-clockwise. It does not affect its position. Usage:
56
+
57
+ LEFT
58
+
59
+ #### RIGHT
60
+
61
+ Rotates the robot 90 degrees clockwise. It does not affect its position. Usage:
62
+
63
+ RIGHT
64
+
65
+ #### REPORT
66
+
67
+ Announce the X,Y and F of the robot. Usage:
68
+
69
+ REPORT
70
+
71
+ Response:
72
+
73
+ X,Y,FACING
74
+
75
+ ### Simulation Constraints
76
+
77
+ * The toy robot is moving on a square tabletop of dimensions 5 units x 5 units.
78
+
79
+ * There are no other obstructions on the table surface.
80
+
81
+ * The robot is free to roam around the surface of the table, but is prevented from falling to destruction.
82
+
83
+ * Any movement that would result in the robot falling from the table is prevented, however further valid movement commands are still allowed.
84
+
85
+ * The first valid command to the robot is a PLACE command, after that, any sequence of commands may be issued, in any order, including another PLACE command. The application discards all commands in the sequence until a valid PLACE command has been executed.
86
+
87
+ * A robot that is not on the table ignores the MOVE, LEFT, RIGHT and REPORT commands.
88
+
89
+ * The application does not provide any graphical output showing the movement of the toy robot.
90
+
91
+
92
+ ## Dependancies
93
+
94
+ ruby version ~> 2.1.0p0
95
+
96
+ To check your version run:
97
+
98
+ $ ruby -v
99
+
100
+ To learn how to install ruby visit [ruby-lang.org/en/installation/](https://www.ruby-lang.org/en/installation/)
101
+
102
+
103
+ ## Troubleshooting
104
+
105
+ ### Development environment
106
+
107
+ * OSX 10.8.5, ruby 2.1.2p95
108
+
109
+ Development dependancies:
110
+
111
+ rake ~> 10.3
112
+ minitest ~> 4.7.5
113
+
114
+ To install them along the gem:
115
+
116
+ $ gem install --dev toy_robot
117
+
118
+ ### Compatible environments
119
+
120
+ * Ubuntu 14.04 x64, ruby 2.1.0p0
121
+
122
+ * Ubuntu 12.04 x32, ruby 2.1.0p0
123
+
124
+ ### Incompatible enviroments
125
+
126
+ * ruby < 2.1.0
127
+
128
+ ### Tests
129
+
130
+ To run unit tests:
131
+
132
+ $ rake test
133
+
134
+ To run integration tests:
135
+
136
+ $ rake test:integration
137
+
138
+ To run all tests:
139
+
140
+ $ rake test:all
141
+
142
+ ## Discussion
143
+
144
+ This piece of software is over-engineered. Over engineering does not feel good.
145
+
146
+ ## Contributing
147
+
148
+ View [CONTRIBUTING.md](https://github.com/matiasanaya/toy-robot-simulator/blob/master/CONTRIBUTING.md).
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList['test/test*.rb']
6
+ t.verbose = true
7
+ end
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << "test"
11
+ t.name = "test:integration"
12
+ t.test_files = FileList['test/integration/test*.rb']
13
+ t.verbose = true
14
+ end
15
+
16
+ Rake::TestTask.new do |t|
17
+ t.libs << "test"
18
+ t.name = "test:all"
19
+ t.test_files = FileList['test/**/test*.rb']
20
+ t.verbose = true
21
+ end
data/bin/toyrobot ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'toy_robot'
4
+
5
+ ToyRobot::Application.new(ARGV).run
@@ -0,0 +1,3 @@
1
+ PLACE 0,0,NORTH
2
+ MOVE
3
+ REPORT
@@ -0,0 +1,3 @@
1
+ PLACE 0,0,NORTH
2
+ LEFT
3
+ REPORT
@@ -0,0 +1,6 @@
1
+ PLACE 1,2,EAST
2
+ MOVE
3
+ MOVE
4
+ LEFT
5
+ MOVE
6
+ REPORT
@@ -0,0 +1,38 @@
1
+ require_relative 'factory'
2
+ require_relative 'command/parser'
3
+
4
+ module ToyRobot
5
+ class Application
6
+ def initialize(argv = [], args = {})
7
+ @input = args[:input] || $stdin
8
+ @parser = Command::Parser
9
+ @controller = Factory.create(:controller)
10
+ end
11
+
12
+ def run(options = nil)
13
+ loop do
14
+ raw_input = input.gets
15
+ break unless raw_input
16
+ raw_input.chomp!
17
+ debug(raw_input)
18
+ command = parser.parse(raw_input)
19
+ controller.input(command) if command
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ 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
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module ToyRobot
2
+ class Board
3
+ def initialize(x_size, y_size)
4
+ @x_size = x_size
5
+ @y_size = y_size
6
+ end
7
+
8
+ def valid_pose?(pose)
9
+ pose && (0..x_size).include?(pose.x) && (0..y_size).include?(pose.y)
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :x_size, :y_size
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module ToyRobot
2
+ module Command
3
+ Base = Struct.new(:token, :args)
4
+ end
5
+ end
@@ -0,0 +1,58 @@
1
+ require_relative '../token'
2
+ require_relative '../base'
3
+
4
+ module ToyRobot
5
+ module Command
6
+ module Parser
7
+ class Base
8
+ def initialize(args)
9
+ @regex = args[:regex]
10
+ @token = args[:token]
11
+ @args_extractor = args[:args_extractor]
12
+ end
13
+
14
+ def build_with_match(string)
15
+ build_command(string) if match?(string)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :regex, :token, :args_extractor
21
+
22
+ def match?(string)
23
+ string =~ regex
24
+ end
25
+
26
+ def extract_args(string)
27
+ if args_extractor
28
+ args_extractor.call(string)
29
+ end
30
+ end
31
+
32
+ def build_command(string)
33
+ Command::Base.new(token, extract_args(string)) if match?(string)
34
+ end
35
+ 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
+ end
57
+ end
58
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'base'
2
+ require_relative '../../pose'
3
+
4
+ module ToyRobot
5
+ module Command
6
+ module Parser
7
+ constantize_orientation = lambda do |string|
8
+ case string
9
+ when 'EAST'
10
+ Pose::Orientation::EAST
11
+ when 'NORTH'
12
+ Pose::Orientation::NORTH
13
+ when 'WEST'
14
+ Pose::Orientation::WEST
15
+ when 'SOUTH'
16
+ Pose::Orientation::SOUTH
17
+ end
18
+ end
19
+
20
+ place_args_extractor = lambda do |raw_command|
21
+ x, y, f = raw_command.split.pop.split(',')
22
+
23
+ Pose.new(
24
+ x: x.to_i,
25
+ y: y.to_i,
26
+ orientation: constantize_orientation.call(f)
27
+ )
28
+ end
29
+
30
+ Place = Base.new(
31
+ token: Command::Token::PLACE,
32
+ regex: /\APLACE \d+,\d+,(NORTH|SOUTH|EAST|WEST)\z/,
33
+ args_extractor: place_args_extractor
34
+ )
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'parser/base'
2
+ require_relative 'parser/place'
3
+
4
+ module ToyRobot
5
+ module Command
6
+ module Parser
7
+ def self.parse(string)
8
+ all.each do |matcher|
9
+ command = matcher.build_with_match(string)
10
+ return command if command
11
+ end
12
+ nil
13
+ end
14
+
15
+ def self.all
16
+ constants(false).reject! { |const| const == :Base }.map { |const| const_get(const) }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ module ToyRobot
2
+ module Command
3
+ module Token
4
+ PLACE = :PLACE
5
+ MOVE = :MOVE
6
+ RIGHT = :RIGHT
7
+ LEFT = :LEFT
8
+ REPORT = :REPORT
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,83 @@
1
+ require_relative 'robot_controller'
2
+ require_relative 'robot'
3
+ require_relative 'placement'
4
+ require_relative 'board'
5
+ require_relative 'pose'
6
+ require_relative 'view'
7
+
8
+ module ToyRobot
9
+ module Factory
10
+ module_function
11
+
12
+ def create(identifier, opts = {})
13
+ case identifier
14
+ when :controller
15
+ RobotControllerFactory.create(opts)
16
+ end
17
+ end
18
+
19
+ module RobotControllerFactory
20
+ module_function
21
+
22
+ def create(opts = {})
23
+ robot = opts[:robot] || RobotFactory.create
24
+ view = opts[:view] || ViewFactory.create(robot: robot)
25
+
26
+ ToyRobot::RobotController.new(
27
+ robot: robot,
28
+ view: view
29
+ )
30
+ end
31
+ end
32
+
33
+ module ViewFactory
34
+ module_function
35
+
36
+ def create(opts = {})
37
+ robot = opts[:robot] || RobotFactory.create
38
+ output = opts[:output] || $stdout
39
+
40
+ ToyRobot::View.new(
41
+ robot: robot,
42
+ output: output
43
+ )
44
+ end
45
+ end
46
+
47
+ module RobotFactory
48
+ module_function
49
+
50
+ def create(opts = {})
51
+ placement = opts[:placement] || PlacementFactory.create
52
+
53
+ ToyRobot::Robot.new(
54
+ placement: placement
55
+ )
56
+ end
57
+ end
58
+
59
+ module PlacementFactory
60
+ module_function
61
+
62
+ def create(opts = {})
63
+ board = opts[:board] || BoardFactory.create
64
+ pose = opts[:pose] || ToyRobot::Pose.new
65
+ ToyRobot::Placement.new(
66
+ board: board,
67
+ pose: pose
68
+ )
69
+ end
70
+ end
71
+
72
+ module BoardFactory
73
+ module_function
74
+
75
+ def create(opts = {})
76
+ x = opts[:x] || 5
77
+ y = opts[:y] || 5
78
+
79
+ ToyRobot::Board.new(x, y)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,34 @@
1
+ require 'forwardable'
2
+
3
+ module ToyRobot
4
+ class Placement
5
+ extend Forwardable
6
+
7
+ def_delegator :pose, :report
8
+ def_delegator :pose, :rotate!, :rotate
9
+
10
+ def initialize(args)
11
+ @board = args[:board]
12
+ @pose = args[:pose]
13
+ end
14
+
15
+ def on_board?
16
+ board && board.valid_pose?(pose)
17
+ end
18
+
19
+ def update(a_new_pose)
20
+ if board.valid_pose?(a_new_pose)
21
+ self.pose = a_new_pose
22
+ end
23
+ end
24
+
25
+ def advance
26
+ update(pose.adjacent)
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :board
32
+ attr_accessor :pose
33
+ end
34
+ end
@@ -0,0 +1,79 @@
1
+ module ToyRobot
2
+ class Pose
3
+ module Orientation
4
+ EAST = :east
5
+ NORTH = :north
6
+ WEST = :west
7
+ SOUTH = :south
8
+ end
9
+
10
+ attr_accessor :x, :y, :orientation
11
+
12
+ def initialize(args = {})
13
+ @x = args[:x]
14
+ @y = args[:y]
15
+ @orientation = args[:orientation]
16
+ end
17
+
18
+ def adjacent
19
+ dup.send(:adjacent!)
20
+ end
21
+
22
+ def rotate!(degrees)
23
+ step = (degrees % 90) + (degrees/degrees.abs)
24
+ self.orientation = step_orientation(step)
25
+ end
26
+
27
+ def report
28
+ {
29
+ x: x,
30
+ y: y,
31
+ orientation: orientation
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ def step_orientation(by = 1)
38
+ orientations = [
39
+ Pose::Orientation::NORTH,
40
+ Pose::Orientation::EAST,
41
+ Pose::Orientation::SOUTH,
42
+ Pose::Orientation::WEST
43
+ ]
44
+
45
+ orientations[(orientations.index(orientation) + by) % 4]
46
+ end
47
+
48
+ def adjacent!
49
+ case orientation
50
+ when Pose::Orientation::EAST
51
+ increment!(:x)
52
+ when Pose::Orientation::NORTH
53
+ increment!(:y)
54
+ when Pose::Orientation::WEST
55
+ decrement!(:x)
56
+ when Pose::Orientation::SOUTH
57
+ decrement!(:y)
58
+ end
59
+ self
60
+ end
61
+
62
+ def increment!(coordinate, by = 1)
63
+ update!(coordinate, by)
64
+ end
65
+
66
+ def decrement!(coordinate, by = -1)
67
+ update!(coordinate, by)
68
+ end
69
+
70
+ def update!(coordinate, by)
71
+ case coordinate
72
+ when :x
73
+ self.x = x + by
74
+ when :y
75
+ self.y = y + by
76
+ end
77
+ end
78
+ end
79
+ end