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