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