toyrobot 0.0.1

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