rtanque 0.0.2

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 (130) hide show
  1. data/.gitignore +22 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +34 -0
  4. data/.travis.yml +11 -0
  5. data/.yardopts +4 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.ci +6 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +120 -0
  10. data/Rakefile +1 -0
  11. data/bin/rtanque +108 -0
  12. data/lib/rtanque.rb +40 -0
  13. data/lib/rtanque/arena.rb +8 -0
  14. data/lib/rtanque/bot.rb +117 -0
  15. data/lib/rtanque/bot/brain.rb +50 -0
  16. data/lib/rtanque/bot/brain_helper.rb +23 -0
  17. data/lib/rtanque/bot/command.rb +23 -0
  18. data/lib/rtanque/bot/radar.rb +54 -0
  19. data/lib/rtanque/bot/sensors.rb +33 -0
  20. data/lib/rtanque/bot/turret.rb +14 -0
  21. data/lib/rtanque/configuration.rb +46 -0
  22. data/lib/rtanque/explosion.rb +23 -0
  23. data/lib/rtanque/gui.rb +24 -0
  24. data/lib/rtanque/gui/bot.rb +42 -0
  25. data/lib/rtanque/gui/draw_group.rb +30 -0
  26. data/lib/rtanque/gui/explosion.rb +25 -0
  27. data/lib/rtanque/gui/shell.rb +20 -0
  28. data/lib/rtanque/gui/window.rb +51 -0
  29. data/lib/rtanque/heading.rb +172 -0
  30. data/lib/rtanque/match.rb +67 -0
  31. data/lib/rtanque/match/tick_group.rb +50 -0
  32. data/lib/rtanque/movable.rb +51 -0
  33. data/lib/rtanque/normalized_attr.rb +69 -0
  34. data/lib/rtanque/point.rb +140 -0
  35. data/lib/rtanque/runner.rb +88 -0
  36. data/lib/rtanque/shell.rb +40 -0
  37. data/lib/rtanque/version.rb +3 -0
  38. data/resources/images/body.png +0 -0
  39. data/resources/images/bullet.png +0 -0
  40. data/resources/images/explosions/explosion2-1.png +0 -0
  41. data/resources/images/explosions/explosion2-10.png +0 -0
  42. data/resources/images/explosions/explosion2-11.png +0 -0
  43. data/resources/images/explosions/explosion2-12.png +0 -0
  44. data/resources/images/explosions/explosion2-13.png +0 -0
  45. data/resources/images/explosions/explosion2-14.png +0 -0
  46. data/resources/images/explosions/explosion2-15.png +0 -0
  47. data/resources/images/explosions/explosion2-16.png +0 -0
  48. data/resources/images/explosions/explosion2-17.png +0 -0
  49. data/resources/images/explosions/explosion2-18.png +0 -0
  50. data/resources/images/explosions/explosion2-19.png +0 -0
  51. data/resources/images/explosions/explosion2-2.png +0 -0
  52. data/resources/images/explosions/explosion2-20.png +0 -0
  53. data/resources/images/explosions/explosion2-21.png +0 -0
  54. data/resources/images/explosions/explosion2-22.png +0 -0
  55. data/resources/images/explosions/explosion2-23.png +0 -0
  56. data/resources/images/explosions/explosion2-24.png +0 -0
  57. data/resources/images/explosions/explosion2-25.png +0 -0
  58. data/resources/images/explosions/explosion2-26.png +0 -0
  59. data/resources/images/explosions/explosion2-27.png +0 -0
  60. data/resources/images/explosions/explosion2-28.png +0 -0
  61. data/resources/images/explosions/explosion2-29.png +0 -0
  62. data/resources/images/explosions/explosion2-3.png +0 -0
  63. data/resources/images/explosions/explosion2-30.png +0 -0
  64. data/resources/images/explosions/explosion2-31.png +0 -0
  65. data/resources/images/explosions/explosion2-32.png +0 -0
  66. data/resources/images/explosions/explosion2-33.png +0 -0
  67. data/resources/images/explosions/explosion2-34.png +0 -0
  68. data/resources/images/explosions/explosion2-35.png +0 -0
  69. data/resources/images/explosions/explosion2-36.png +0 -0
  70. data/resources/images/explosions/explosion2-37.png +0 -0
  71. data/resources/images/explosions/explosion2-38.png +0 -0
  72. data/resources/images/explosions/explosion2-39.png +0 -0
  73. data/resources/images/explosions/explosion2-4.png +0 -0
  74. data/resources/images/explosions/explosion2-40.png +0 -0
  75. data/resources/images/explosions/explosion2-41.png +0 -0
  76. data/resources/images/explosions/explosion2-42.png +0 -0
  77. data/resources/images/explosions/explosion2-43.png +0 -0
  78. data/resources/images/explosions/explosion2-44.png +0 -0
  79. data/resources/images/explosions/explosion2-45.png +0 -0
  80. data/resources/images/explosions/explosion2-46.png +0 -0
  81. data/resources/images/explosions/explosion2-47.png +0 -0
  82. data/resources/images/explosions/explosion2-48.png +0 -0
  83. data/resources/images/explosions/explosion2-49.png +0 -0
  84. data/resources/images/explosions/explosion2-5.png +0 -0
  85. data/resources/images/explosions/explosion2-50.png +0 -0
  86. data/resources/images/explosions/explosion2-51.png +0 -0
  87. data/resources/images/explosions/explosion2-52.png +0 -0
  88. data/resources/images/explosions/explosion2-53.png +0 -0
  89. data/resources/images/explosions/explosion2-54.png +0 -0
  90. data/resources/images/explosions/explosion2-55.png +0 -0
  91. data/resources/images/explosions/explosion2-56.png +0 -0
  92. data/resources/images/explosions/explosion2-57.png +0 -0
  93. data/resources/images/explosions/explosion2-58.png +0 -0
  94. data/resources/images/explosions/explosion2-59.png +0 -0
  95. data/resources/images/explosions/explosion2-6.png +0 -0
  96. data/resources/images/explosions/explosion2-60.png +0 -0
  97. data/resources/images/explosions/explosion2-61.png +0 -0
  98. data/resources/images/explosions/explosion2-62.png +0 -0
  99. data/resources/images/explosions/explosion2-63.png +0 -0
  100. data/resources/images/explosions/explosion2-64.png +0 -0
  101. data/resources/images/explosions/explosion2-65.png +0 -0
  102. data/resources/images/explosions/explosion2-66.png +0 -0
  103. data/resources/images/explosions/explosion2-67.png +0 -0
  104. data/resources/images/explosions/explosion2-68.png +0 -0
  105. data/resources/images/explosions/explosion2-69.png +0 -0
  106. data/resources/images/explosions/explosion2-7.png +0 -0
  107. data/resources/images/explosions/explosion2-70.png +0 -0
  108. data/resources/images/explosions/explosion2-71.png +0 -0
  109. data/resources/images/explosions/explosion2-8.png +0 -0
  110. data/resources/images/explosions/explosion2-9.png +0 -0
  111. data/resources/images/grass.png +0 -0
  112. data/resources/images/radar.png +0 -0
  113. data/resources/images/turret.png +0 -0
  114. data/rtanque.gemspec +33 -0
  115. data/sample_bots/camper.rb +79 -0
  116. data/sample_bots/keyboard.rb +50 -0
  117. data/sample_bots/seek_and_destroy.rb +51 -0
  118. data/screenshots/battle_1.png +0 -0
  119. data/screenshots/battle_2.png +0 -0
  120. data/spec/rtanque/bot_spec.rb +239 -0
  121. data/spec/rtanque/heading_spec.rb +279 -0
  122. data/spec/rtanque/match_spec.rb +36 -0
  123. data/spec/rtanque/normalized_attr_spec.rb +90 -0
  124. data/spec/rtanque/point_spec.rb +196 -0
  125. data/spec/rtanque/radar_spec.rb +87 -0
  126. data/spec/rtanque/shell_spec.rb +35 -0
  127. data/spec/rtanque_spec.rb +6 -0
  128. data/spec/spec_helper.rb +51 -0
  129. data/templates/bot.erb +11 -0
  130. metadata +310 -0
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe RTanque::Match do
4
+ before do
5
+ @instance = described_class.new(@arena)
6
+ end
7
+
8
+ it 'should start with 0 ticks' do
9
+ expect(@instance.ticks).to eql 0
10
+ end
11
+
12
+ it 'should allow adding bots' do
13
+ bot = double('bot')
14
+ @instance.add_bots(bot)
15
+ expect(@instance.bots.include?(bot)).to be_true
16
+ end
17
+
18
+ describe '#finished?' do
19
+ it 'should be true if one or less bots left' do
20
+ expect(@instance.finished?).to be_true
21
+ end
22
+
23
+ it 'should be false if two or more bots left' do
24
+ @instance.add_bots(:bot, :bot2)
25
+ expect(@instance.finished?).to be_false
26
+ end
27
+ end
28
+
29
+ describe '#tick' do
30
+ it 'should increment tick count' do
31
+ @instance.tick
32
+ expect(@instance.ticks).to eq 1
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,90 @@
1
+ require "spec_helper"
2
+
3
+ INFINITY = 1.0 / 0.0
4
+
5
+ describe RTanque::NormalizedAttr do
6
+ context 'integer range, no max delta' do
7
+ let(:instance) do
8
+ described_class::AttrContainer.new(1..5)
9
+ end
10
+ let(:attached_instance) do
11
+ mock('some_object')
12
+ end
13
+
14
+ it 'has -infinity as max_delta' do
15
+ expect(instance.max_delta(attached_instance)).to eq INFINITY
16
+ end
17
+
18
+ it 'has correct delta' do
19
+ expect(instance.delta(nil, 100)).to eq 0
20
+ end
21
+
22
+ it 'does not allow more than range' do
23
+ expect(instance.normalize(attached_instance, nil, 100)).to eq 5
24
+ end
25
+
26
+ it 'does not allow less than range' do
27
+ expect(instance.normalize(attached_instance, nil, -100)).to eq 1
28
+ end
29
+ end
30
+
31
+ context 'integer range, max delta' do
32
+ let(:instance) do
33
+ described_class::AttrContainer.new(0..5, lambda{ |attached| attached.max_delta })
34
+ end
35
+ let(:attached_instance) do
36
+ mock('some_object', :max_delta => 0.1)
37
+ end
38
+
39
+ it 'should call block for max_delta' do
40
+ attached = mock('some_object')
41
+ attached.should_receive(:max_delta).and_return(0.1)
42
+ expect(instance.max_delta(attached)).to eq 0.1
43
+ end
44
+
45
+ it 'does not allow a change greater than given delta' do
46
+ expect(instance.normalize(attached_instance, 1, 2)).to eq 1.1
47
+ end
48
+
49
+ it 'does not allow a change less than given delta' do
50
+ expect(instance.normalize(attached_instance, 2, 1)).to eq 1.9
51
+ end
52
+ end
53
+
54
+ #context 'heading range' do
55
+ # let(:normalized) do
56
+ # described_class::AttrContainer.new(0..RTanque::Heading::FULL_ANGLE, RTanque::Heading::ONE_DEGREE)
57
+ # end
58
+ #
59
+ # it 'should respect step' do
60
+ # expect(normalized.normalize(Math::PI, 0)).to eq RTanque::Heading::ONE_DEGREE
61
+ # end
62
+ #
63
+ # it 'should allow full 360 turn' do
64
+ # previous = 0
65
+ # 360.times do
66
+ # new_value = normalized.normalize(previous + Math::PI, previous)
67
+ # expect(new_value).to eq(RTanque::Heading.new(previous) + RTanque::Heading::ONE_DEGREE)
68
+ # previous = new_value
69
+ # end
70
+ # end
71
+ #end
72
+ #
73
+ #context 'mixin' do
74
+ # class Tester
75
+ # extend RTanque::NormalizedAttr
76
+ # attr_accessor :speed
77
+ # attr_normalized :speed, 0..5
78
+ # end
79
+ #
80
+ # it 'defines normalize method' do
81
+ # expect(Tester.instance_methods.grep(/normalized_speed/)).to have(1).match
82
+ # end
83
+ #
84
+ # it 'normalize method behaves correctly' do
85
+ # i = Tester.new
86
+ # expect(i.normalized_speed(100)).to eq 5
87
+ # end
88
+ #
89
+ #end
90
+ end
@@ -0,0 +1,196 @@
1
+ require "spec_helper"
2
+
3
+ describe RTanque::Point do
4
+ context '.distance' do
5
+ it 'gives 0 when points are equal' do
6
+ a = described_class.new(0, 0, @arena)
7
+ b = described_class.new(0, 0, @arena)
8
+ expect(described_class.distance(a,b)).to eq 0
9
+ end
10
+
11
+ it 'always returns positive number' do
12
+ a = described_class.new(0, 0, @arena)
13
+ b = described_class.new(10, 0, @arena)
14
+ expect(described_class.distance(a, b)).to eq 10
15
+ expect(described_class.distance(b, a)).to eq 10
16
+ end
17
+ end
18
+
19
+ context '#initialize' do
20
+ it 'should be frozen' do
21
+ a = described_class.new(10, 10, @arena)
22
+ expect(a.frozen?).to be_true
23
+ end
24
+
25
+ it 'should not allow modification' do
26
+ a = described_class.new(10, 10, @arena)
27
+ expect { a.x = 1 }.to raise_exception(RUBY_VERSION >= '1.9' ? RuntimeError : TypeError)
28
+ end
29
+ end
30
+
31
+ context '#heading' do
32
+ before do
33
+ @instance = described_class.new(5, 5, @arena)
34
+ end
35
+
36
+ it 'return correct heading when at 0deg' do
37
+ other = described_class.new(5, 10, @arena)
38
+ expect(@instance.heading(other)).to eq RTanque::Heading::N
39
+ end
40
+
41
+ it 'return correct heading when at 45deg' do
42
+ other = described_class.new(10, 10, @arena)
43
+ expect(@instance.heading(other)).to eq RTanque::Heading::NE
44
+ end
45
+
46
+ it 'return correct heading when at 90deg' do
47
+ other = described_class.new(10, 5, @arena)
48
+ expect(@instance.heading(other)).to eq RTanque::Heading::E
49
+ end
50
+
51
+ it 'return correct heading when at 135deg' do
52
+ other = described_class.new(10, 0, @arena)
53
+ expect(@instance.heading(other)).to eq RTanque::Heading::SE
54
+ end
55
+
56
+ it 'return correct heading when at 180deg' do
57
+ other = described_class.new(5, 0, @arena)
58
+ expect(@instance.heading(other)).to eq RTanque::Heading::S
59
+ end
60
+
61
+ it 'return correct heading when at 225deg' do
62
+ other = described_class.new(0, 0, @arena)
63
+ expect(@instance.heading(other)).to eq RTanque::Heading::SW
64
+ end
65
+
66
+ it 'return correct heading when at 270deg' do
67
+ other = described_class.new(0, 5, @arena)
68
+ expect(@instance.heading(other)).to eq RTanque::Heading::W
69
+ end
70
+
71
+ it 'return correct heading when at 315deg' do
72
+ other = described_class.new(0, 10, @arena)
73
+ expect(@instance.heading(other)).to eq RTanque::Heading::NW
74
+ end
75
+
76
+ it 'returns correct heading when points are the same' do
77
+ other = @instance.clone
78
+ expect(@instance.heading(other)).to eq RTanque::Heading::N
79
+ end
80
+ end
81
+
82
+ context '#==' do
83
+ it 'is true when x & y are equal' do
84
+ a = described_class.new(1, 1, @arena)
85
+ b = described_class.new(1, 1, @arena)
86
+ expect(a == b).to be_true
87
+ expect(b == a).to be_true
88
+ end
89
+
90
+ it 'is not true when x & y are equal' do
91
+ a = described_class.new(2, 1, @arena)
92
+ b = described_class.new(1, 1, @arena)
93
+ expect(a == b).to be_false
94
+ expect(b == a).to be_false
95
+ end
96
+ end
97
+
98
+ context '#move' do
99
+ before do
100
+ @init_x, @init_y = 0.0, 0.0
101
+ @point = RTanque::Point.new(@init_x, @init_y, @arena)
102
+ @heading = RTanque::Heading.new(RTanque::Heading::NORTH)
103
+ end
104
+
105
+ it 'should not move if no speed' do
106
+ new_point = @point.move(@heading, 0)
107
+ expect(new_point.x).to eq @init_x
108
+ expect(new_point.y).to eq @init_y
109
+ end
110
+
111
+ it 'should move on y axis' do
112
+ new_point = @point.move(@heading, 1)
113
+ expect(new_point.x).to eq @init_x
114
+ expect(new_point.y).to eq @init_y + 1
115
+ end
116
+
117
+ it 'should move on x axis' do
118
+ @heading = RTanque::Heading.new(RTanque::Heading::EAST)
119
+ new_point = @point.move(@heading, 1)
120
+ expect(new_point.x).to eq @init_x + 1
121
+ expect(new_point.y).to eq @init_y
122
+ end
123
+
124
+ it 'should move on y axis twice' do
125
+ @heading = RTanque::Heading.new(RTanque::Heading::EAST)
126
+ new_point = @point.move(@heading, 1).move(@heading, 1)
127
+ expect(new_point.x).to eq @init_x + 2
128
+ expect(new_point.y).to eq @init_y
129
+ end
130
+
131
+ it 'should move on x and y axis' do
132
+ @heading = RTanque::Heading.new(RTanque::Heading::NORTH_EAST)
133
+ new_point = @point.move(@heading, Math.hypot(1,1) * 2)
134
+ expect(new_point.x).to eq @init_x + 2
135
+ expect(new_point.y).to eq @init_y + 2
136
+ end
137
+
138
+ it 'should move on x and y axis forwards and backwards' do
139
+ @heading = RTanque::Heading.new(RTanque::Heading::NORTH_EAST)
140
+ new_point = @point.move(@heading, Math.hypot(1,1) * 2)
141
+ expect(new_point.x).to eq @init_x + 2
142
+ expect(new_point.y).to eq @init_y + 2
143
+ new_point = @point.move(@heading, -Math.hypot(1,1) * 2)
144
+ expect(new_point.x).to eq @init_x
145
+ expect(new_point.y).to eq @init_y
146
+ end
147
+
148
+ it 'should stay within arena when bound' do
149
+ @heading = RTanque::Heading.new(RTanque::Heading::NORTH_EAST)
150
+ new_point = @point.move(@heading, Math.hypot(1,1) * 1000)
151
+ expect(new_point.x).to eq @arena.width
152
+ expect(new_point.y).to eq @arena.height
153
+
154
+ @heading = RTanque::Heading.new(RTanque::Heading::NORTH_EAST)
155
+ new_point = @point.move(@heading, Math.hypot(1,1) * -1000)
156
+ expect(new_point.x).to eq @init_x
157
+ expect(new_point.y).to eq @init_y
158
+ end
159
+ end
160
+
161
+ describe '#within_radius?' do
162
+ before do
163
+ @a = described_class.new(5, 5, @arena)
164
+ end
165
+
166
+ it 'correctly detects equal points' do
167
+ @b = @a.clone
168
+ expect(@a.within_radius?(@b, 1)).to be_true
169
+ end
170
+
171
+ it 'correctly detects point 1 to left' do
172
+ @b = described_class.new(4, 5, @arena)
173
+ expect(@a.within_radius?(@b, 1)).to be_true
174
+ end
175
+
176
+ it 'correctly detects point 1 to right' do
177
+ @b = described_class.new(6, 5, @arena)
178
+ expect(@a.within_radius?(@b, 1)).to be_true
179
+ end
180
+
181
+ it 'correctly detects point 1 above' do
182
+ @b = described_class.new(5, 6, @arena)
183
+ expect(@a.within_radius?(@b, 1)).to be_true
184
+ end
185
+
186
+ it 'correctly detects point 1 below' do
187
+ @b = described_class.new(5, 4, @arena)
188
+ expect(@a.within_radius?(@b, 1)).to be_true
189
+ end
190
+
191
+ it 'correctly detects point 1 NE' do
192
+ @b = described_class.new(6, 6, @arena)
193
+ expect(@a.within_radius?(@b, 1)).to be_false
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,87 @@
1
+ require "spec_helper"
2
+
3
+ describe RTanque::Bot::Radar do
4
+ def reflection(heading, distance, name)
5
+ described_class::Reflection.new(RTanque::Heading.new(heading), distance, name)
6
+ end
7
+
8
+ let(:radar) { described_class.new(mockbot(10, 10), RTanque::Heading.new(0)) }
9
+
10
+ context '#scan' do
11
+ it 'should return an array' do
12
+ radar.scan([])
13
+ expect(radar.to_a).to be_an_instance_of Array
14
+ end
15
+
16
+ it 'should return an empty array' do
17
+ radar.scan([])
18
+ expect(radar.empty?).to be_true
19
+ end
20
+
21
+ it 'should 1 Reflection' do
22
+ radar.scan([mockbot(10, 20)])
23
+ expect(radar).to have(1).reflections
24
+ expect(radar.first).to be_an_instance_of described_class::Reflection
25
+ end
26
+
27
+ it 'reflection should be correct' do
28
+ radar.scan([mockbot(10, 20, 'otherbot')])
29
+ expect(radar.first).to eq reflection(0, 10.0, 'otherbot')
30
+ end
31
+
32
+ it 'should clear self' do
33
+ radar.scan([mockbot(10, 20, 'otherbot')])
34
+ radar.scan([])
35
+ expect(radar.empty?).to be_true
36
+ end
37
+
38
+ it 'reflections should be correct' do
39
+ bots = [mockbot(10, 20, 'below'), mockbot(10, 50, 'right')]
40
+ radar.scan(bots)
41
+ expect(radar.to_a).to include(
42
+ reflection(RTanque::Heading::N, 10.0, 'below'),
43
+ reflection(RTanque::Heading::N, 40.0, 'right')
44
+ )
45
+ end
46
+
47
+ it 'should detect bot in same position' do
48
+ bots = [mockbot(10, 10, 'ontop')]
49
+ radar.scan(bots)
50
+ expect(radar.to_a).to include(
51
+ reflection(RTanque::Heading::N, 0.0, 'ontop')
52
+ )
53
+ end
54
+
55
+ it 'should detect point on max vision range' do
56
+ x = 10 + Math.sin(RTanque::Bot::Radar::VISION_RANGE.last) * 10
57
+ y = 10 + Math.cos(RTanque::Bot::Radar::VISION_RANGE.last) * 10
58
+ bots = [mockbot(x, y, 'border')]
59
+ radar.scan(bots)
60
+ expect(radar.to_a).to have(1).reflections
61
+ end
62
+
63
+ it 'should detect point on min vision range' do
64
+ x = 10 + Math.sin(RTanque::Bot::Radar::VISION_RANGE.first) * 10
65
+ y = 10 + Math.cos(RTanque::Bot::Radar::VISION_RANGE.first) * 10
66
+ bots = [mockbot(x, y, 'border')]
67
+ radar.scan(bots)
68
+ expect(radar.to_a).to have(1).reflections
69
+ end
70
+
71
+ it 'should not detect point outside max vision range' do
72
+ x = 10 + Math.sin(RTanque::Bot::Radar::VISION_RANGE.last) * 10
73
+ y = 10 + Math.cos(RTanque::Bot::Radar::VISION_RANGE.last) * 10
74
+ bots = [mockbot(x + 0.01, y, 'border')]
75
+ radar.scan(bots)
76
+ expect(radar.to_a).to have(0).reflections
77
+ end
78
+
79
+ it 'should not detect point outside max vision range' do
80
+ x = 10 + Math.sin(RTanque::Bot::Radar::VISION_RANGE.first) * 10
81
+ y = 10 + Math.cos(RTanque::Bot::Radar::VISION_RANGE.first) * 10
82
+ bots = [mockbot(x, y - 0.01, 'border')]
83
+ radar.scan(bots)
84
+ expect(radar.to_a).to have(0).reflections
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+
3
+ describe RTanque::Shell do
4
+ let(:bot) { mockbot(10, 10, 'shooter') }
5
+ let(:shell) { described_class.new(bot, RTanque::Point.new(10, 10, @arena), RTanque::Heading.new(0), 1) }
6
+
7
+ context '#hits' do
8
+ it 'should yield no bots' do
9
+ bots = []
10
+ expect{ |p| shell.hits(bots, &p) }.not_to yield_control
11
+ end
12
+
13
+ it 'should yield hit bot' do
14
+ bots = [mockbot(10, 10, 'deadbot')]
15
+ expect { |p| shell.hits(bots, &p) }.to yield_with_args(bot, bots[0])
16
+ end
17
+
18
+ it 'shell should be dead after hit' do
19
+ bots = [mockbot(10, 10, 'deadbot')]
20
+ expect(shell.dead?).to be_false
21
+ shell.hits(bots)
22
+ expect(shell.dead?).to be_true
23
+ end
24
+
25
+ it 'should hit shell on bot radius' do
26
+ bots = [mockbot(10 + RTanque::Bot::RADIUS, 10, 'deadbot')]
27
+ expect { |p| shell.hits(bots, &p) }.to yield_with_args(bot, bots[0])
28
+ end
29
+
30
+ it 'should not hit shell outside bot radius' do
31
+ bots = [mockbot(10 + RTanque::Bot::RADIUS + 0.01, 10, 'deadbot')]
32
+ expect { |p| shell.hits(bots, &p) }.not_to yield_with_args(bot, bots[0])
33
+ end
34
+ end
35
+ end