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
@@ -0,0 +1,41 @@
1
+ require 'forwardable'
2
+
3
+ module ToyRobot
4
+ class Robot
5
+ extend Forwardable
6
+
7
+ def initialize(args)
8
+ @placement = args[:placement]
9
+ end
10
+
11
+ def place(*args)
12
+ placement.update(*args)
13
+ end
14
+
15
+ def move
16
+ with_placement { placement.advance }
17
+ end
18
+
19
+ def right
20
+ with_placement { placement.rotate(90) }
21
+ end
22
+
23
+ def left
24
+ with_placement { placement.rotate(-90) }
25
+ end
26
+
27
+ def report
28
+ with_placement { placement.report }
29
+ end
30
+
31
+ private
32
+
33
+ attr_accessor :placement
34
+
35
+ def with_placement
36
+ if placement.on_board?
37
+ yield
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'command/token'
2
+ require 'forwardable'
3
+
4
+ module ToyRobot
5
+ class RobotController
6
+ extend Forwardable
7
+
8
+ def_delegators :robot, :place, :move, :left, :right
9
+ def_delegator :view, :report
10
+
11
+ def initialize(args)
12
+ @robot = args[:robot]
13
+ @view = args[:view]
14
+ end
15
+
16
+ def input(command)
17
+ case command.token
18
+ when Command::Token::PLACE
19
+ place(command.args)
20
+ when Command::Token::MOVE
21
+ move
22
+ when Command::Token::RIGHT
23
+ right
24
+ when Command::Token::LEFT
25
+ left
26
+ when Command::Token::REPORT
27
+ report
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :robot, :view
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ module ToyRobot
2
+ View = Struct.new(:args) do
3
+ def report
4
+ report = robot.report
5
+ output.puts report_string(report) if report
6
+ end
7
+
8
+ private
9
+
10
+ def report_string(report)
11
+ "#{report[:x]},#{report[:y]},#{report[:orientation].upcase}"
12
+ end
13
+
14
+ def robot
15
+ args[:robot]
16
+ end
17
+
18
+ def output
19
+ args[:output] || $stdout
20
+ end
21
+ end
22
+ end
data/lib/toy_robot.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'toy_robot/application'
@@ -0,0 +1,24 @@
1
+ require_relative '../../lib/toy_robot'
2
+ require 'minitest/autorun'
3
+
4
+ module ToyRobot
5
+ class ToyRobotTest < MiniTest::Unit::TestCase
6
+ def setup
7
+ @app = lambda { |f| ToyRobot::Application.new(nil, input: f).run }
8
+ end
9
+ def test_example_input_a
10
+ file = File.open('data/example_input_a.txt', 'r')
11
+ assert_output(/\A0,1,NORTH\n\z/) { @app.call(file) }
12
+ end
13
+
14
+ def test_example_input_b
15
+ file = File.open('data/example_input_b.txt', 'r')
16
+ assert_output(/\A0,0,WEST\n\z/) { @app.call(file) }
17
+ end
18
+
19
+ def test_example_input_c
20
+ file = File.open('data/example_input_c.txt', 'r')
21
+ assert_output(/\A3,3,NORTH\n\z/) { @app.call(file) }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ require_relative '../../lib/toy_robot/factory'
2
+ require 'minitest/autorun'
3
+
4
+ module ToyRobot
5
+ class FactoryTest < MiniTest::Unit::TestCase
6
+ def setup
7
+ @object = Factory
8
+ end
9
+
10
+ def test_public_interface
11
+ assert_respond_to @object, :create
12
+ end
13
+
14
+ def test_that_create_returns_a_robot_controller
15
+ assert_instance_of RobotController, @object.create(:controller)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ require 'minitest/autorun'
2
+
3
+ module ToyRobot
4
+ class ToyRobotTest < MiniTest::Unit::TestCase
5
+ def test_that_it_is_executable
6
+ assert system('[ -x bin/toyrobots ]')
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../lib/toy_robot/board'
2
+ require 'minitest/autorun'
3
+
4
+ module ToyRobot
5
+ class BoardTest < MiniTest::Unit::TestCase
6
+
7
+ PoseDouble = Struct.new(:x, :y)
8
+
9
+ def setup
10
+ @board = Board.new(5,5)
11
+ end
12
+
13
+ def test_that_knows_a_valid_pose
14
+ pose_double = PoseDouble.new(0,0)
15
+
16
+ assert @board.valid_pose?(pose_double)
17
+ end
18
+
19
+ def test_that_knows_an_invalid_pose
20
+ [[-1,0], [0,-1], [6,0], [0,6]].each do |out_of_range|
21
+ pose_double = PoseDouble.new(*out_of_range)
22
+
23
+ refute @board.valid_pose?(pose_double), "Pose #{pose_double} should be invalid"
24
+ end
25
+ end
26
+
27
+ def test_that_a_nil_pose_is_invalid
28
+ refute @board.valid_pose?(nil)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ require_relative '../lib/toy_robot/command/token'
2
+ require 'minitest/autorun'
3
+
4
+ module ToyRobot
5
+ class CommandTest < MiniTest::Unit::TestCase
6
+ def test_tokens_are_defined
7
+ assert defined?(Command::Token::PLACE)
8
+
9
+ assert defined?(Command::Token::MOVE)
10
+
11
+ assert defined?(Command::Token::RIGHT)
12
+
13
+ assert defined?(Command::Token::LEFT)
14
+
15
+ assert defined?(Command::Token::REPORT)
16
+ end
17
+
18
+ def test_base_is_defined
19
+ assert defined?(Command::Base)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ require_relative '../lib/toy_robot/command/parser'
2
+ require 'minitest/autorun'
3
+
4
+ module ToyRobot
5
+ class CommandMatcherTest < MiniTest::Unit::TestCase
6
+ def setup
7
+ @matcher = Command::Parser
8
+ end
9
+
10
+ def test_that_it_can_parse
11
+ refute_equal nil, @matcher.parse('PLACE 0,0,NORTH')
12
+ end
13
+
14
+ def test_that_parsing_can_fail
15
+ assert_equal nil, @matcher.parse('PLACE 0,0,NORTHISH')
16
+ end
17
+
18
+ def test_that_it_knows_all_matchers
19
+ all = @matcher.all
20
+
21
+ assert_includes all, Command::Parser::Place
22
+
23
+ assert_includes all, Command::Parser::Move
24
+
25
+ assert_includes all, Command::Parser::Left
26
+
27
+ assert_includes all, Command::Parser::Right
28
+
29
+ assert_includes all, Command::Parser::Report
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ module ToyRobot
2
+ module CommandMatcherInterfaceTest
3
+ def test_it_implement_the_command_matcher_interface
4
+ assert_respond_to @object, :build_with_match
5
+ end
6
+
7
+ def test_that_it_matches_a_valid_string
8
+ assert @object.build_with_match(@object_valid_str)
9
+ end
10
+
11
+ def test_that_it_ignores_invalid_strings
12
+ @object_invalid_strs.each do |invalid|
13
+ refute @object.build_with_match(invalid), "It should ignore '#{invalid}'"
14
+ end
15
+ end
16
+
17
+ def test_that_response_knows_token
18
+ assert_equal @object_token,
19
+ @object.build_with_match(@object_valid_str).token
20
+ end
21
+
22
+ def test_that_response_knows_args
23
+ assert_respond_to @object.build_with_match(@object_valid_str),
24
+ :args
25
+ end
26
+
27
+ def test_that_response_is_nil
28
+ assert_equal nil, @object.build_with_match(@object_invalid_strs.sample)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ require_relative '../lib/toy_robot/command/parser/base'
2
+ require_relative 'test_command_matcher_interface'
3
+ require 'minitest/autorun'
4
+
5
+ module ToyRobot
6
+ class CommandMatcherLeftTest < MiniTest::Unit::TestCase
7
+ include CommandMatcherInterfaceTest
8
+
9
+ def setup
10
+ @object = Command::Parser::Left
11
+ @object_token = Command::Token::LEFT
12
+ @object_valid_str = 'LEFT'
13
+ @object_invalid_strs = [
14
+ 'LEFT ',
15
+ 'LEFTY',
16
+ 'LEFT 180'
17
+ ]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../lib/toy_robot/command/parser/base'
2
+ require_relative 'test_command_matcher_interface'
3
+ require 'minitest/autorun'
4
+
5
+ module ToyRobot
6
+ class CommandMatcherMoveTest < MiniTest::Unit::TestCase
7
+ include CommandMatcherInterfaceTest
8
+
9
+ def setup
10
+ @object = Command::Parser::Move
11
+ @object_token = Command::Token::MOVE
12
+ @object_valid_str = 'MOVE'
13
+ @object_invalid_strs = [
14
+ 'MOVE ',
15
+ 'MOVER',
16
+ 'MOVE 180',
17
+ 'move'
18
+ ]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ require_relative '../lib/toy_robot/command/parser/place'
2
+ require_relative 'test_command_matcher_interface'
3
+ require 'minitest/autorun'
4
+
5
+ module ToyRobot
6
+ class CommandMatcherPlaceTest < MiniTest::Unit::TestCase
7
+ include CommandMatcherInterfaceTest
8
+
9
+ def setup
10
+ @object = Command::Parser::Place
11
+ @object_token = Command::Token::PLACE
12
+ @object_valid_str = 'PLACE 0,0,NORTH'
13
+ @object_invalid_strs = [
14
+ 'PLACE',
15
+ 'PLACE ',
16
+ 'PLACE X,Y,Z,F',
17
+ 'PLACE 0,0,OTHER',
18
+ 'PLACE 0,0',
19
+ 'PLACE 0,a,NORTH',
20
+ 'PLACE a,99,NORTH',
21
+ 'PLACE a,NORHT',
22
+ 'PLACE 0,NORTH'
23
+ ]
24
+ end
25
+
26
+ def test_that_response_args_know_x
27
+ assert_equal 123,
28
+ @object.build_with_match('PLACE 123,0,NORTH').args.x
29
+ end
30
+
31
+ def test_that_response_args_know_y
32
+ assert_equal 123,
33
+ @object.build_with_match('PLACE 0,123,NORTH').args.y
34
+ end
35
+
36
+ def test_that_response_args_knows_north_orientation
37
+ assert_equal Pose::Orientation::NORTH,
38
+ @object.build_with_match('PLACE 0,123,NORTH').args.orientation
39
+ end
40
+
41
+ def test_that_response_args_knows_south_orientation
42
+ assert_equal Pose::Orientation::SOUTH,
43
+ @object.build_with_match('PLACE 0,123,SOUTH').args.orientation
44
+ end
45
+
46
+ def test_that_response_args_knows_east_orientation
47
+ assert_equal Pose::Orientation::EAST,
48
+ @object.build_with_match('PLACE 0,123,EAST').args.orientation
49
+ end
50
+
51
+ def test_that_response_args_knows_west_orientation
52
+ assert_equal Pose::Orientation::WEST,
53
+ @object.build_with_match('PLACE 0,123,WEST').args.orientation
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../lib/toy_robot/command/parser/base'
2
+ require_relative 'test_command_matcher_interface'
3
+ require 'minitest/autorun'
4
+
5
+ module ToyRobot
6
+ class CommandMatcherReportTest < MiniTest::Unit::TestCase
7
+ include CommandMatcherInterfaceTest
8
+
9
+ def setup
10
+ @object = Command::Parser::Report
11
+ @object_token = Command::Token::REPORT
12
+ @object_valid_str = 'REPORT'
13
+ @object_invalid_strs = [
14
+ 'REPORT ',
15
+ 'REPORTNOW',
16
+ 'REP',
17
+ 'R'
18
+ ]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ require_relative '../lib/toy_robot/command/parser/base'
2
+ require_relative 'test_command_matcher_interface'
3
+ require 'minitest/autorun'
4
+
5
+ module ToyRobot
6
+ class CommandMatcherRightTest < MiniTest::Unit::TestCase
7
+ include CommandMatcherInterfaceTest
8
+
9
+ def setup
10
+ @object = Command::Parser::Right
11
+ @object_token = Command::Token::RIGHT
12
+ @object_valid_str = 'RIGHT'
13
+ @object_invalid_strs = [
14
+ 'RIGHT ',
15
+ 'RIGHTY',
16
+ 'RIGHT 180'
17
+ ]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,94 @@
1
+ require_relative '../lib/toy_robot/placement'
2
+ require_relative 'test_reporter_interface'
3
+ require 'minitest/autorun'
4
+
5
+ module ToyRobot
6
+ class PlacementTest < MiniTest::Unit::TestCase
7
+ include ReporterInterfaceTest
8
+
9
+ SimpleDouble = Struct.new(:stub) do
10
+ def valid_pose?(_)
11
+ stub.fetch(:valid_pose?)
12
+ end
13
+
14
+ def adjacent
15
+ stub.fetch(:adjacent)
16
+ end
17
+ end
18
+
19
+ def setup
20
+ @board = SimpleDouble.new(valid_pose?: true)
21
+ @pose = SimpleDouble.new
22
+ @placement = @object = Placement.new(board: @board, pose: @pose)
23
+ end
24
+
25
+ def test_respond_to_public_interface
26
+ assert_respond_to @placement, :update
27
+ assert_respond_to @placement, :advance
28
+ assert_respond_to @placement, :rotate
29
+ assert_respond_to @placement, :on_board?
30
+ end
31
+
32
+ def test_that_it_might_be_on_board
33
+ assert @placement.on_board?
34
+ end
35
+
36
+ def test_that_it_might_not_be_on_board
37
+ @board[:stub] = { valid_pose?: false }
38
+
39
+ refute @placement.on_board?
40
+ end
41
+
42
+ def test_that_it_might_update
43
+ new_pose = Object.new
44
+
45
+ @placement.update(new_pose)
46
+
47
+ assert_equal new_pose, @placement.instance_variable_get(:@pose)
48
+ end
49
+
50
+ def test_that_update_might_fail
51
+ @board[:stub] = { valid_pose?: false }
52
+
53
+ @placement.update('invalid_pose')
54
+
55
+ assert_equal @pose, @placement.instance_variable_get(:@pose)
56
+ end
57
+
58
+ def test_that_it_might_advance
59
+ adjacent_pose = Object.new
60
+ @pose[:stub] = { adjacent: adjacent_pose }
61
+
62
+ @placement.advance
63
+
64
+ assert_equal adjacent_pose, @placement.instance_variable_get(:@pose)
65
+ end
66
+
67
+ def test_that_it_might_not_advance
68
+ @board[:stub] = { valid_pose?: false }
69
+ @pose[:stub] = { adjacent: 'anything' }
70
+
71
+ @placement.advance
72
+
73
+ assert_equal @pose, @placement.instance_variable_get(:@pose)
74
+ end
75
+
76
+ def test_that_it_rotates
77
+ pose = MiniTest::Mock.new
78
+ placement = Placement.new(pose: pose)
79
+
80
+ pose.expect(:rotate!, nil, [123])
81
+ placement.rotate(123)
82
+ pose.verify
83
+ end
84
+
85
+ def test_that_it_reports
86
+ pose = MiniTest::Mock.new
87
+ placement = Placement.new(pose: pose)
88
+
89
+ pose.expect(:report, nil)
90
+ placement.report
91
+ pose.verify
92
+ end
93
+ end
94
+ end
data/test/test_pose.rb ADDED
@@ -0,0 +1,146 @@
1
+ require_relative '../lib/toy_robot/pose'
2
+ require_relative 'test_reporter_interface'
3
+ require 'minitest/autorun'
4
+
5
+ module ToyRobot
6
+ class PoseTest < MiniTest::Unit::TestCase
7
+ include ReporterInterfaceTest
8
+
9
+ def setup
10
+ @x, @y = 0, 1
11
+ @orientation = Pose::Orientation::EAST
12
+ @pose = @object = Pose.new(
13
+ x: @x,
14
+ y: @y,
15
+ orientation: @orientation
16
+ )
17
+ end
18
+
19
+ def test_public_interface
20
+ assert_respond_to @pose, :adjacent
21
+ assert_respond_to @pose, :rotate!
22
+ assert_respond_to @pose, :report
23
+ assert_respond_to @pose, :x
24
+ assert_respond_to @pose, :y
25
+ assert_respond_to @pose, :orientation
26
+ end
27
+
28
+ def test_it_can_be_initialized_without_arguments
29
+ assert Pose.new
30
+ end
31
+
32
+ def test_that_adjacent_returns_a_pose
33
+ assert_instance_of Pose, @pose.adjacent
34
+ end
35
+
36
+ def test_that_adjacent_pose_is_not_the_reciever
37
+ refute_equal @pose, @pose.adjacent
38
+ end
39
+
40
+ def test_that_knows_adjacent_when_facing_east
41
+ adjacent_pose_hash = {
42
+ x: @x + 1,
43
+ y: @y,
44
+ orientation: @orientation
45
+ }
46
+
47
+ assert_pose adjacent_pose_hash, @pose.adjacent
48
+ end
49
+
50
+ def test_that_knows_adjacent_when_facing_north
51
+ orientation = Pose::Orientation::NORTH
52
+ @pose.instance_variable_set(:@orientation, orientation)
53
+ adjacent_pose_hash = {
54
+ x: @x,
55
+ y: @y + 1,
56
+ orientation: orientation
57
+ }
58
+
59
+ assert_pose adjacent_pose_hash, @pose.adjacent
60
+ end
61
+
62
+ def test_that_knows_adjacent_when_facing_west
63
+ orientation = Pose::Orientation::WEST
64
+ @pose.instance_variable_set(:@orientation, orientation)
65
+ adjacent_pose_hash = {
66
+ x: @x - 1,
67
+ y: @y,
68
+ orientation: orientation
69
+ }
70
+
71
+ assert_pose adjacent_pose_hash, @pose.adjacent
72
+ end
73
+
74
+ def test_that_knows_adjacent_when_facing_south
75
+ orientation = Pose::Orientation::SOUTH
76
+ @pose.instance_variable_set(:@orientation, orientation)
77
+ adjacent_pose_hash = {
78
+ x: @x,
79
+ y: @y - 1,
80
+ orientation: orientation
81
+ }
82
+
83
+ assert_pose adjacent_pose_hash, @pose.adjacent
84
+ end
85
+
86
+ def test_that_can_rotate_90
87
+ clockwise_orientations = [
88
+ Pose::Orientation::NORTH,
89
+ Pose::Orientation::EAST,
90
+ Pose::Orientation::SOUTH,
91
+ Pose::Orientation::WEST,
92
+ Pose::Orientation::NORTH
93
+ ]
94
+
95
+ clockwise_orientations.each_cons(2) do |initial_orientation, rotated_orientation|
96
+ @pose.instance_variable_set(:@orientation, initial_orientation)
97
+ rotated_pose_hash = {x: @x, y: @y, orientation: rotated_orientation}
98
+
99
+ @pose.rotate!(90)
100
+
101
+ assert_pose rotated_pose_hash, @pose
102
+ end
103
+ end
104
+
105
+ def test_that_can_rotate_90_counterclockwise
106
+ counter_clockwise_orientations = [
107
+ Pose::Orientation::NORTH,
108
+ Pose::Orientation::WEST,
109
+ Pose::Orientation::SOUTH,
110
+ Pose::Orientation::EAST,
111
+ Pose::Orientation::NORTH
112
+ ]
113
+
114
+ counter_clockwise_orientations.each_cons(2) do |initial_orientation, rotated_orientation|
115
+ @pose.instance_variable_set(:@orientation, initial_orientation)
116
+ rotated_pose_hash = {
117
+ x: @x,
118
+ y: @y,
119
+ orientation: rotated_orientation
120
+ }
121
+
122
+ @pose.rotate!(-90)
123
+
124
+ assert_pose rotated_pose_hash, @pose
125
+ end
126
+ end
127
+
128
+ def test_that_it_reports_x
129
+ assert_includes @pose.report, :x
130
+ end
131
+
132
+ def test_that_it_reports_y
133
+ assert_includes @pose.report, :y
134
+ end
135
+
136
+ def test_that_it_reports_orientation
137
+ assert_includes @pose.report, :orientation
138
+ end
139
+
140
+ def assert_pose(expected_pose_hash, actual)
141
+ assert_equal expected_pose_hash[:x], actual.send(:x), ':x coordinate is different'
142
+ assert_equal expected_pose_hash[:y], actual.send(:y), ':y coordinate is different'
143
+ assert_equal expected_pose_hash[:orientation], actual.send(:orientation), ':orientation is different'
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,7 @@
1
+ module ToyRobot
2
+ module ReporterInterfaceTest
3
+ def test_it_implement_the_reproter_interface
4
+ assert_respond_to @object, :report
5
+ end
6
+ end
7
+ end