rtanque 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.rvmrc +34 -0
- data/.travis.yml +11 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/Gemfile.ci +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +120 -0
- data/Rakefile +1 -0
- data/bin/rtanque +108 -0
- data/lib/rtanque.rb +40 -0
- data/lib/rtanque/arena.rb +8 -0
- data/lib/rtanque/bot.rb +117 -0
- data/lib/rtanque/bot/brain.rb +50 -0
- data/lib/rtanque/bot/brain_helper.rb +23 -0
- data/lib/rtanque/bot/command.rb +23 -0
- data/lib/rtanque/bot/radar.rb +54 -0
- data/lib/rtanque/bot/sensors.rb +33 -0
- data/lib/rtanque/bot/turret.rb +14 -0
- data/lib/rtanque/configuration.rb +46 -0
- data/lib/rtanque/explosion.rb +23 -0
- data/lib/rtanque/gui.rb +24 -0
- data/lib/rtanque/gui/bot.rb +42 -0
- data/lib/rtanque/gui/draw_group.rb +30 -0
- data/lib/rtanque/gui/explosion.rb +25 -0
- data/lib/rtanque/gui/shell.rb +20 -0
- data/lib/rtanque/gui/window.rb +51 -0
- data/lib/rtanque/heading.rb +172 -0
- data/lib/rtanque/match.rb +67 -0
- data/lib/rtanque/match/tick_group.rb +50 -0
- data/lib/rtanque/movable.rb +51 -0
- data/lib/rtanque/normalized_attr.rb +69 -0
- data/lib/rtanque/point.rb +140 -0
- data/lib/rtanque/runner.rb +88 -0
- data/lib/rtanque/shell.rb +40 -0
- data/lib/rtanque/version.rb +3 -0
- data/resources/images/body.png +0 -0
- data/resources/images/bullet.png +0 -0
- data/resources/images/explosions/explosion2-1.png +0 -0
- data/resources/images/explosions/explosion2-10.png +0 -0
- data/resources/images/explosions/explosion2-11.png +0 -0
- data/resources/images/explosions/explosion2-12.png +0 -0
- data/resources/images/explosions/explosion2-13.png +0 -0
- data/resources/images/explosions/explosion2-14.png +0 -0
- data/resources/images/explosions/explosion2-15.png +0 -0
- data/resources/images/explosions/explosion2-16.png +0 -0
- data/resources/images/explosions/explosion2-17.png +0 -0
- data/resources/images/explosions/explosion2-18.png +0 -0
- data/resources/images/explosions/explosion2-19.png +0 -0
- data/resources/images/explosions/explosion2-2.png +0 -0
- data/resources/images/explosions/explosion2-20.png +0 -0
- data/resources/images/explosions/explosion2-21.png +0 -0
- data/resources/images/explosions/explosion2-22.png +0 -0
- data/resources/images/explosions/explosion2-23.png +0 -0
- data/resources/images/explosions/explosion2-24.png +0 -0
- data/resources/images/explosions/explosion2-25.png +0 -0
- data/resources/images/explosions/explosion2-26.png +0 -0
- data/resources/images/explosions/explosion2-27.png +0 -0
- data/resources/images/explosions/explosion2-28.png +0 -0
- data/resources/images/explosions/explosion2-29.png +0 -0
- data/resources/images/explosions/explosion2-3.png +0 -0
- data/resources/images/explosions/explosion2-30.png +0 -0
- data/resources/images/explosions/explosion2-31.png +0 -0
- data/resources/images/explosions/explosion2-32.png +0 -0
- data/resources/images/explosions/explosion2-33.png +0 -0
- data/resources/images/explosions/explosion2-34.png +0 -0
- data/resources/images/explosions/explosion2-35.png +0 -0
- data/resources/images/explosions/explosion2-36.png +0 -0
- data/resources/images/explosions/explosion2-37.png +0 -0
- data/resources/images/explosions/explosion2-38.png +0 -0
- data/resources/images/explosions/explosion2-39.png +0 -0
- data/resources/images/explosions/explosion2-4.png +0 -0
- data/resources/images/explosions/explosion2-40.png +0 -0
- data/resources/images/explosions/explosion2-41.png +0 -0
- data/resources/images/explosions/explosion2-42.png +0 -0
- data/resources/images/explosions/explosion2-43.png +0 -0
- data/resources/images/explosions/explosion2-44.png +0 -0
- data/resources/images/explosions/explosion2-45.png +0 -0
- data/resources/images/explosions/explosion2-46.png +0 -0
- data/resources/images/explosions/explosion2-47.png +0 -0
- data/resources/images/explosions/explosion2-48.png +0 -0
- data/resources/images/explosions/explosion2-49.png +0 -0
- data/resources/images/explosions/explosion2-5.png +0 -0
- data/resources/images/explosions/explosion2-50.png +0 -0
- data/resources/images/explosions/explosion2-51.png +0 -0
- data/resources/images/explosions/explosion2-52.png +0 -0
- data/resources/images/explosions/explosion2-53.png +0 -0
- data/resources/images/explosions/explosion2-54.png +0 -0
- data/resources/images/explosions/explosion2-55.png +0 -0
- data/resources/images/explosions/explosion2-56.png +0 -0
- data/resources/images/explosions/explosion2-57.png +0 -0
- data/resources/images/explosions/explosion2-58.png +0 -0
- data/resources/images/explosions/explosion2-59.png +0 -0
- data/resources/images/explosions/explosion2-6.png +0 -0
- data/resources/images/explosions/explosion2-60.png +0 -0
- data/resources/images/explosions/explosion2-61.png +0 -0
- data/resources/images/explosions/explosion2-62.png +0 -0
- data/resources/images/explosions/explosion2-63.png +0 -0
- data/resources/images/explosions/explosion2-64.png +0 -0
- data/resources/images/explosions/explosion2-65.png +0 -0
- data/resources/images/explosions/explosion2-66.png +0 -0
- data/resources/images/explosions/explosion2-67.png +0 -0
- data/resources/images/explosions/explosion2-68.png +0 -0
- data/resources/images/explosions/explosion2-69.png +0 -0
- data/resources/images/explosions/explosion2-7.png +0 -0
- data/resources/images/explosions/explosion2-70.png +0 -0
- data/resources/images/explosions/explosion2-71.png +0 -0
- data/resources/images/explosions/explosion2-8.png +0 -0
- data/resources/images/explosions/explosion2-9.png +0 -0
- data/resources/images/grass.png +0 -0
- data/resources/images/radar.png +0 -0
- data/resources/images/turret.png +0 -0
- data/rtanque.gemspec +33 -0
- data/sample_bots/camper.rb +79 -0
- data/sample_bots/keyboard.rb +50 -0
- data/sample_bots/seek_and_destroy.rb +51 -0
- data/screenshots/battle_1.png +0 -0
- data/screenshots/battle_2.png +0 -0
- data/spec/rtanque/bot_spec.rb +239 -0
- data/spec/rtanque/heading_spec.rb +279 -0
- data/spec/rtanque/match_spec.rb +36 -0
- data/spec/rtanque/normalized_attr_spec.rb +90 -0
- data/spec/rtanque/point_spec.rb +196 -0
- data/spec/rtanque/radar_spec.rb +87 -0
- data/spec/rtanque/shell_spec.rb +35 -0
- data/spec/rtanque_spec.rb +6 -0
- data/spec/spec_helper.rb +51 -0
- data/templates/bot.erb +11 -0
- metadata +310 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# Seek&Destroy: Sample Bot
|
2
|
+
#
|
3
|
+
# Enjoys following and target and firing many shots
|
4
|
+
class SeekAndDestroy < RTanque::Bot::Brain
|
5
|
+
NAME = 'Seek&Destroy'
|
6
|
+
include RTanque::Bot::BrainHelper
|
7
|
+
|
8
|
+
TURRET_FIRE_RANGE = RTanque::Heading::ONE_DEGREE * 5.0
|
9
|
+
|
10
|
+
def tick!
|
11
|
+
if (lock = self.get_radar_lock)
|
12
|
+
self.destroy_lock(lock)
|
13
|
+
@desired_heading = nil
|
14
|
+
else
|
15
|
+
self.seek_lock
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy_lock(reflection)
|
20
|
+
command.heading = reflection.heading
|
21
|
+
command.radar_heading = reflection.heading
|
22
|
+
command.turret_heading = reflection.heading
|
23
|
+
command.speed = reflection.distance > 200 ? MAX_BOT_SPEED : MAX_BOT_SPEED / 2.0
|
24
|
+
if (reflection.heading.delta(sensors.turret_heading)).abs < TURRET_FIRE_RANGE
|
25
|
+
command.fire(reflection.distance > 200 ? MAX_FIRE_POWER : MIN_FIRE_POWER)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def seek_lock
|
30
|
+
if sensors.position.on_wall?
|
31
|
+
@desired_heading = sensors.heading + RTanque::Heading::HALF_ANGLE
|
32
|
+
end
|
33
|
+
command.radar_heading = sensors.radar_heading + MAX_RADAR_ROTATION
|
34
|
+
command.speed = 1
|
35
|
+
if @desired_heading
|
36
|
+
command.heading = @desired_heading
|
37
|
+
command.turret_heading = @desired_heading
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_radar_lock
|
42
|
+
@locked_on ||= nil
|
43
|
+
lock = if @locked_on
|
44
|
+
sensors.radar.find { |reflection| reflection.name == @locked_on } || sensors.radar.first
|
45
|
+
else
|
46
|
+
sensors.radar.first
|
47
|
+
end
|
48
|
+
@locked_on = lock.name if lock
|
49
|
+
lock
|
50
|
+
end
|
51
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RTanque::Bot do
|
4
|
+
before(:each) { @brain_tick_lambda = lambda { } }
|
5
|
+
let(:bot){ brain_bot(&@brain_tick_lambda) }
|
6
|
+
|
7
|
+
context '#dead?' do
|
8
|
+
it 'should not initially be dead' do
|
9
|
+
expect(bot.dead?).to be_false
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should be true if health is below min' do
|
13
|
+
bot.health = RTanque::Configuration.bot.health.min - 1
|
14
|
+
expect(bot.dead?).to be_true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '#sensors' do
|
19
|
+
it 'should return a Sensors instance' do
|
20
|
+
expect(bot.sensors).to be_instance_of RTanque::Bot::Sensors
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should correctly transfer values from bot' do
|
24
|
+
bot.health = 5
|
25
|
+
bot.speed = -2
|
26
|
+
expect(bot.sensors.health).to eq 5
|
27
|
+
expect(bot.sensors.speed).to eq -2
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context '#tick' do
|
32
|
+
context 'no commands' do
|
33
|
+
it 'should not update bot position, heading on tick' do
|
34
|
+
bot.tick
|
35
|
+
expect(bot.position.x).to eq 0.0
|
36
|
+
expect(bot.position.y).to eq 0.0
|
37
|
+
expect(bot.heading).to eq 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'command with speed' do
|
42
|
+
before(:each) do
|
43
|
+
bot.speed = 1
|
44
|
+
bot.heading = 0
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should update bot position on tick' do
|
48
|
+
bot.tick
|
49
|
+
expect(bot.position.x).to eq 0.0
|
50
|
+
expect(bot.position.y).to eq 1.0
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should keep updating bot position' do
|
54
|
+
5.times { bot.tick }
|
55
|
+
expect(bot.position.x).to eq 0.0
|
56
|
+
expect(bot.position.y).to eq 5.0
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should stop at arena limit' do
|
60
|
+
(@arena.height + 2).times { bot.tick }
|
61
|
+
expect(bot.position.x).to eq 0.0
|
62
|
+
expect(bot.position.y).to eq @arena.height
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'command with heading' do
|
67
|
+
before(:each) do
|
68
|
+
bot.heading = RTanque::Heading::EAST
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should have heading east' do
|
72
|
+
bot.tick
|
73
|
+
expect(bot.heading).to eq RTanque::Heading::EAST
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should keep heading east' do
|
77
|
+
5.times { bot.tick }
|
78
|
+
expect(bot.heading).to eq RTanque::Heading::EAST
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should maintain heading given null heading' do
|
82
|
+
bot.tick
|
83
|
+
@brain_tick_lambda = lambda { command.heading = nil }
|
84
|
+
expect(bot.heading).to eq RTanque::Heading::EAST
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should not change radar and turret headings' do
|
88
|
+
bot.tick
|
89
|
+
expect(bot.radar.heading).to eq 0.0
|
90
|
+
expect(bot.turret.heading).to eq 0.0
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'radar heading' do
|
95
|
+
before(:each) do
|
96
|
+
bot.radar.heading = RTanque::Heading::EAST
|
97
|
+
bot.tick
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should change radar heading' do
|
101
|
+
expect(bot.radar.heading).to eq RTanque::Heading::EAST
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should not change bot heading' do
|
105
|
+
expect(bot.heading).to eq 0
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'turret heading' do
|
110
|
+
before(:each) do
|
111
|
+
bot.turret.heading = RTanque::Heading::EAST
|
112
|
+
bot.tick
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should change radar heading' do
|
116
|
+
expect(bot.turret.heading).to eq RTanque::Heading::EAST
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should not change bot heading' do
|
120
|
+
expect(bot.heading).to eq 0
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'fire power' do
|
125
|
+
it 'bot should have 0 fire_power' do
|
126
|
+
bot.tick
|
127
|
+
expect(bot.fire_power).to eq 0
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'bot should have fire_power reset' do
|
131
|
+
bot.fire_power = 1
|
132
|
+
bot.tick
|
133
|
+
expect(bot.fire_power).to eq 0
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'command with error' do
|
138
|
+
before(:each) do
|
139
|
+
@brain_tick_lambda = lambda { raise 'oops' }
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should capture error' do
|
143
|
+
expect{ bot.tick }.not_to raise_exception
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should reduce bot health' do
|
147
|
+
original_health = bot.health
|
148
|
+
bot.tick
|
149
|
+
expect(bot.health).to be < original_health
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'bot command speed' do
|
155
|
+
before(:each) do
|
156
|
+
@brain_tick_lambda = lambda { command.speed = RTanque::Bot::MAX_SPEED + 1 }
|
157
|
+
bot.tick
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should respect step size' do
|
161
|
+
expect(bot.speed).to eq RTanque::Configuration.bot.speed_step
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should respect max speed' do
|
165
|
+
times = RTanque::Bot::MAX_SPEED / RTanque::Configuration.bot.speed_step
|
166
|
+
(times + 1).to_i.times { bot.tick }
|
167
|
+
expect(bot.speed).to eq RTanque::Bot::MAX_SPEED
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should respect min speed' do
|
171
|
+
@brain_tick_lambda = lambda { command.speed = -(RTanque::Bot::MAX_SPEED + 1) }
|
172
|
+
times = RTanque::Bot::MAX_SPEED / RTanque::Configuration.bot.speed_step
|
173
|
+
(times + 1).to_i.times { bot.tick }
|
174
|
+
expect(bot.speed).to eq RTanque::Bot::MAX_SPEED
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'bot command heading' do
|
179
|
+
it 'should respect step size' do
|
180
|
+
@brain_tick_lambda = lambda { command.heading = RTanque::Heading::S }
|
181
|
+
bot.tick
|
182
|
+
expect(bot.heading).to eq RTanque::Heading.new(RTanque::Configuration.bot.turn_step)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should respect step size in negative' do
|
186
|
+
@brain_tick_lambda = lambda { command.heading = -RTanque::Heading::S }
|
187
|
+
bot.tick
|
188
|
+
expect(bot.heading).to eq RTanque::Heading.new(-RTanque::Configuration.bot.turn_step)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'bot command radar heading' do
|
193
|
+
it 'should respect step size' do
|
194
|
+
@brain_tick_lambda = lambda { command.radar_heading = RTanque::Heading::S }
|
195
|
+
bot.tick
|
196
|
+
expect(bot.radar.heading).to eq RTanque::Heading.new(RTanque::Configuration.radar.turn_step)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should respect step size in negative' do
|
200
|
+
@brain_tick_lambda = lambda { command.radar_heading = -RTanque::Heading::S }
|
201
|
+
bot.tick
|
202
|
+
expect(bot.radar.heading).to eq RTanque::Heading.new(-RTanque::Configuration.radar.turn_step)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context 'bot command turret heading' do
|
207
|
+
it 'should respect step size' do
|
208
|
+
@brain_tick_lambda = lambda { self.command.turret_heading = RTanque::Heading::S }
|
209
|
+
bot.tick
|
210
|
+
expect(bot.turret.heading).to eq RTanque::Heading.new(RTanque::Configuration.turret.turn_step)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'should respect step size in negative' do
|
214
|
+
@brain_tick_lambda = lambda { self.command.turret_heading = -RTanque::Heading::S }
|
215
|
+
bot.tick
|
216
|
+
expect(bot.turret.heading).to eq RTanque::Heading.new(-RTanque::Configuration.turret.turn_step)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'bot command fire_power' do
|
221
|
+
it 'should respect max' do
|
222
|
+
@brain_tick_lambda = lambda { command.fire_power = RTanque::Bot::MAX_FIRE_POWER + 1 }
|
223
|
+
bot.tick
|
224
|
+
expect(bot.fire_power).to eq RTanque::Bot::MAX_FIRE_POWER
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should respect min' do
|
228
|
+
@brain_tick_lambda = lambda { command.fire_power = RTanque::Bot::MIN_FIRE_POWER - 1 }
|
229
|
+
bot.tick
|
230
|
+
expect(bot.fire_power).to eq RTanque::Bot::MIN_FIRE_POWER
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'should not allow constant shooting' do
|
234
|
+
@brain_tick_lambda = lambda { command.fire_power = RTanque::Bot::MAX_FIRE_POWER }
|
235
|
+
5.times { bot.tick }
|
236
|
+
expect(bot.fire_power).not_to eq RTanque::Bot::MAX_FIRE_POWER
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RTanque::Heading do
|
4
|
+
NINETY = Math::PI / 2.0
|
5
|
+
|
6
|
+
describe '#delta' do
|
7
|
+
before do
|
8
|
+
@instance = described_class.new(0)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'receives floats' do
|
12
|
+
other = 0.0
|
13
|
+
expect(@instance.delta(other)).to eql 0.0
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'receives headings' do
|
17
|
+
other = described_class.new(0.0)
|
18
|
+
expect(@instance.delta(other)).to eql 0.0
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'gives 0 when provided same a == b' do
|
22
|
+
expect(@instance.delta(@instance)).to eq 0
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'outputs positive when a < b' do
|
26
|
+
expect(@instance.delta(@instance + 1)).to be > 0
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'outputs negative when b < a' do
|
30
|
+
expect(@instance.delta(@instance - 1)).to be < 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'correct output when difference < 180 deg' do
|
34
|
+
expect(@instance.delta(NINETY)).to eq NINETY
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'correct output when difference > 180 deg' do
|
38
|
+
expect(@instance.delta(NINETY * 3)).to eq -NINETY
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'correctly handles degress > 360' do
|
42
|
+
expect(@instance.delta(NINETY * 5)).to eq NINETY
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'correctly handles differences > 180 in which a < b' do
|
46
|
+
expect(@instance.delta(NINETY * 3)).to eq -NINETY
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'correctly handles differences > 180 in which a > b' do
|
50
|
+
expect(@instance.delta(-(NINETY * 3))).to eq NINETY
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'correctly handles when a and b are on both sides of 180' do
|
54
|
+
@instance = described_class.new(RTanque::Heading::S - RTanque::Heading::ONE_DEGREE)
|
55
|
+
delta = RTanque.round(@instance.delta(RTanque::Heading::S + RTanque::Heading::ONE_DEGREE), 5)
|
56
|
+
expected = RTanque.round(RTanque::Heading::ONE_DEGREE * 2, 5)
|
57
|
+
expect(delta).to eq(expected)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '.new_from_degrees' do
|
62
|
+
it 'receives positive degrees' do
|
63
|
+
expect(described_class.new_from_degrees(90).to_f).to eql NINETY
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'receives degrees large than 360' do
|
67
|
+
expect(described_class.new_from_degrees(90 + 360).to_f).to eql NINETY
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'receives negative degrees' do
|
71
|
+
expect(described_class.new_from_degrees(-90).to_f).to eql NINETY * 3
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'receives negative degrees less than -360' do
|
75
|
+
expect(described_class.new_from_degrees(-90 - 360).to_f).to eql NINETY * 3
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '.delta_between_points' do
|
80
|
+
let(:from_point) { RTanque::Point.new(10, 10, @arena) }
|
81
|
+
let(:from_heading) { described_class.new(0) }
|
82
|
+
it 'is correct when 0 delta' do
|
83
|
+
to_point = RTanque::Point.new(10, 20, @arena)
|
84
|
+
expect(described_class.delta_between_points(from_point, from_heading, to_point)).to eq 0
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'is correct when delta is negative' do
|
88
|
+
to_point = RTanque::Point.new(9, 11)
|
89
|
+
expect(described_class.delta_between_points(from_point, from_heading, to_point)).to eq -(NINETY / 2)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'is correct when delta is positive' do
|
93
|
+
to_point = RTanque::Point.new(11, 11)
|
94
|
+
expect(described_class.delta_between_points(from_point, from_heading, to_point)).to eq(NINETY / 2)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'is correct when delta is max' do
|
98
|
+
to_point = RTanque::Point.new(10, 9)
|
99
|
+
expect(described_class.delta_between_points(from_point, from_heading, to_point)).to eq(NINETY * 2)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#initialize' do
|
104
|
+
it 'receives inits and sets radians to float' do
|
105
|
+
expect(described_class.new(0).radians).to eql 0.0
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'receives negative floats' do
|
109
|
+
expect(described_class.new(-NINETY).radians).to eql NINETY * 3
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'creates frozen object' do
|
113
|
+
expect(described_class.new(0).frozen?).to be_true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#clone' do
|
118
|
+
it 'returns a Heading' do
|
119
|
+
expect(described_class.new.clone).to be_instance_of described_class
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'returns a new object' do
|
123
|
+
original = described_class.new
|
124
|
+
copy = original.clone
|
125
|
+
expect(original.object_id).not_to eq copy.object_id
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'returns a new object which does not affect old' do
|
129
|
+
original = described_class.new(0.0)
|
130
|
+
copy = original.clone
|
131
|
+
expect(copy).not_to equal original
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#==' do
|
136
|
+
it 'works like Numeric, ignoring type' do
|
137
|
+
a = described_class.new(1.0)
|
138
|
+
expect(a == 1).to be_true
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'correctly compares two equal headings' do
|
142
|
+
a = described_class.new(1.0)
|
143
|
+
b = described_class.new(1.0)
|
144
|
+
expect(a == b).to be_true
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'correctly compares two different headings' do
|
148
|
+
a = described_class.new(0)
|
149
|
+
b = described_class.new(1.0)
|
150
|
+
expect(a == b).to be_false
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'compares to a numeric on LHS' do
|
154
|
+
a = described_class.new(Math::PI)
|
155
|
+
expect(Math::PI == a).to be_true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#eql?' do
|
160
|
+
it 'compares types like Numeric, comparing type' do
|
161
|
+
a = described_class.new(1.0)
|
162
|
+
expect(a.eql?(1)).to be_false
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'correctly compares two equal headings' do
|
166
|
+
a = described_class.new(1.0)
|
167
|
+
b = described_class.new(1.0)
|
168
|
+
expect(a.eql?(b)).to be_true
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'correctly compares two different headings' do
|
172
|
+
a = described_class.new(0)
|
173
|
+
b = described_class.new(1.0)
|
174
|
+
expect(a.eql?(b)).to be_false
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe '#<=>' do
|
179
|
+
it 'receives headings' do
|
180
|
+
a = described_class.new
|
181
|
+
b = described_class.new(NINETY)
|
182
|
+
expect(a <=> b).to eq -1
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'receives floats' do
|
186
|
+
a = described_class.new
|
187
|
+
expect(a <=> NINETY).to eq -1
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe '#+' do
|
192
|
+
it 'returns new instance' do
|
193
|
+
a = described_class.new(0.0)
|
194
|
+
result = a + NINETY
|
195
|
+
expect(result).not_to eql a
|
196
|
+
expect(result).not_to equal a
|
197
|
+
expect(result).to be_kind_of described_class
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'leaves receiver unchanged' do
|
201
|
+
a = described_class.new(1.0)
|
202
|
+
a + NINETY
|
203
|
+
expect(a).to eql described_class.new(1.0)
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'correctly adds' do
|
207
|
+
a = described_class.new(0.0) + NINETY
|
208
|
+
expect(a).to eq NINETY
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'correctly adds negative numbers' do
|
212
|
+
a = described_class.new(0.0) + -NINETY
|
213
|
+
expect(a).to eq(NINETY * 3)
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'correctly adds other headings' do
|
217
|
+
a = described_class.new(NINETY * 2)
|
218
|
+
b = described_class.new(NINETY)
|
219
|
+
expect(a + b).to eq(NINETY * 3)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'respects radian module ring' do
|
223
|
+
a = described_class.new(NINETY * 3)
|
224
|
+
b = described_class.new(NINETY * 3)
|
225
|
+
expect(a + b).to eq NINETY * 2
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe '#*' do
|
230
|
+
it 'returns new instance' do
|
231
|
+
a = described_class.new(NINETY)
|
232
|
+
result = a * NINETY
|
233
|
+
expect(result).not_to eql a
|
234
|
+
expect(result).not_to equal a
|
235
|
+
expect(result).to be_kind_of described_class
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'correctly multiplies' do
|
239
|
+
a = described_class.new(NINETY)
|
240
|
+
expect(a * 2.0).to eq NINETY * 2.0
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe '#/' do
|
245
|
+
it 'returns new instance' do
|
246
|
+
a = described_class.new(NINETY)
|
247
|
+
result = a / NINETY
|
248
|
+
expect(result).not_to eql a
|
249
|
+
expect(result).not_to equal a
|
250
|
+
expect(result).to be_kind_of described_class
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'correctly divides' do
|
254
|
+
a = described_class.new(NINETY * 2.0)
|
255
|
+
expect(a / 2.0).to eq NINETY
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#-@' do
|
260
|
+
it 'creates a new instance' do
|
261
|
+
a = described_class.new(1.0)
|
262
|
+
b = -a
|
263
|
+
expect(b).not_to equal a
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'correctly negates itself' do
|
267
|
+
expect(-described_class.new(NINETY)).to eq(NINETY * 3)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
describe '#to_degrees' do
|
272
|
+
it 'returns float' do
|
273
|
+
a = described_class.new
|
274
|
+
expect(a.to_degrees).to eq 0.0
|
275
|
+
b = described_class.new(NINETY)
|
276
|
+
expect(b.to_degrees).to eq 90.0
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|