UG_RRobots 1.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 (165) hide show
  1. data/bin/rrobots +202 -0
  2. data/bin/tournament +413 -0
  3. data/config/rrobots.yml +15 -0
  4. data/contribs/allbots.rb +0 -0
  5. data/doc/manual.rdoc +126 -0
  6. data/doc/manual_fr.rdoc +129 -0
  7. data/images/explosion00.gif +0 -0
  8. data/images/explosion01.gif +0 -0
  9. data/images/explosion02.gif +0 -0
  10. data/images/explosion03.gif +0 -0
  11. data/images/explosion04.gif +0 -0
  12. data/images/explosion05.gif +0 -0
  13. data/images/explosion06.gif +0 -0
  14. data/images/explosion07.gif +0 -0
  15. data/images/explosion08.gif +0 -0
  16. data/images/explosion09.gif +0 -0
  17. data/images/explosion10.gif +0 -0
  18. data/images/explosion11.gif +0 -0
  19. data/images/explosion12.gif +0 -0
  20. data/images/explosion13.gif +0 -0
  21. data/images/explosion14.gif +0 -0
  22. data/images/red_body000.gif +0 -0
  23. data/images/red_body010.gif +0 -0
  24. data/images/red_body020.gif +0 -0
  25. data/images/red_body030.gif +0 -0
  26. data/images/red_body040.gif +0 -0
  27. data/images/red_body050.gif +0 -0
  28. data/images/red_body060.gif +0 -0
  29. data/images/red_body070.gif +0 -0
  30. data/images/red_body080.gif +0 -0
  31. data/images/red_body090.gif +0 -0
  32. data/images/red_body100.gif +0 -0
  33. data/images/red_body110.gif +0 -0
  34. data/images/red_body120.gif +0 -0
  35. data/images/red_body130.gif +0 -0
  36. data/images/red_body140.gif +0 -0
  37. data/images/red_body150.gif +0 -0
  38. data/images/red_body160.gif +0 -0
  39. data/images/red_body170.gif +0 -0
  40. data/images/red_body180.gif +0 -0
  41. data/images/red_body190.gif +0 -0
  42. data/images/red_body200.gif +0 -0
  43. data/images/red_body210.gif +0 -0
  44. data/images/red_body220.gif +0 -0
  45. data/images/red_body230.gif +0 -0
  46. data/images/red_body240.gif +0 -0
  47. data/images/red_body250.gif +0 -0
  48. data/images/red_body260.gif +0 -0
  49. data/images/red_body270.gif +0 -0
  50. data/images/red_body280.gif +0 -0
  51. data/images/red_body290.gif +0 -0
  52. data/images/red_body300.gif +0 -0
  53. data/images/red_body310.gif +0 -0
  54. data/images/red_body320.gif +0 -0
  55. data/images/red_body330.gif +0 -0
  56. data/images/red_body340.gif +0 -0
  57. data/images/red_body350.gif +0 -0
  58. data/images/red_radar000.gif +0 -0
  59. data/images/red_radar010.gif +0 -0
  60. data/images/red_radar020.gif +0 -0
  61. data/images/red_radar030.gif +0 -0
  62. data/images/red_radar040.gif +0 -0
  63. data/images/red_radar050.gif +0 -0
  64. data/images/red_radar060.gif +0 -0
  65. data/images/red_radar070.gif +0 -0
  66. data/images/red_radar080.gif +0 -0
  67. data/images/red_radar090.gif +0 -0
  68. data/images/red_radar100.gif +0 -0
  69. data/images/red_radar110.gif +0 -0
  70. data/images/red_radar120.gif +0 -0
  71. data/images/red_radar130.gif +0 -0
  72. data/images/red_radar140.gif +0 -0
  73. data/images/red_radar150.gif +0 -0
  74. data/images/red_radar160.gif +0 -0
  75. data/images/red_radar170.gif +0 -0
  76. data/images/red_radar180.gif +0 -0
  77. data/images/red_radar190.gif +0 -0
  78. data/images/red_radar200.gif +0 -0
  79. data/images/red_radar210.gif +0 -0
  80. data/images/red_radar220.gif +0 -0
  81. data/images/red_radar230.gif +0 -0
  82. data/images/red_radar240.gif +0 -0
  83. data/images/red_radar250.gif +0 -0
  84. data/images/red_radar260.gif +0 -0
  85. data/images/red_radar270.gif +0 -0
  86. data/images/red_radar280.gif +0 -0
  87. data/images/red_radar290.gif +0 -0
  88. data/images/red_radar300.gif +0 -0
  89. data/images/red_radar310.gif +0 -0
  90. data/images/red_radar320.gif +0 -0
  91. data/images/red_radar330.gif +0 -0
  92. data/images/red_radar340.gif +0 -0
  93. data/images/red_radar350.gif +0 -0
  94. data/images/red_turret000.gif +0 -0
  95. data/images/red_turret010.gif +0 -0
  96. data/images/red_turret020.gif +0 -0
  97. data/images/red_turret030.gif +0 -0
  98. data/images/red_turret040.gif +0 -0
  99. data/images/red_turret050.gif +0 -0
  100. data/images/red_turret060.gif +0 -0
  101. data/images/red_turret070.gif +0 -0
  102. data/images/red_turret080.gif +0 -0
  103. data/images/red_turret090.gif +0 -0
  104. data/images/red_turret100.gif +0 -0
  105. data/images/red_turret110.gif +0 -0
  106. data/images/red_turret120.gif +0 -0
  107. data/images/red_turret130.gif +0 -0
  108. data/images/red_turret140.gif +0 -0
  109. data/images/red_turret150.gif +0 -0
  110. data/images/red_turret160.gif +0 -0
  111. data/images/red_turret170.gif +0 -0
  112. data/images/red_turret180.gif +0 -0
  113. data/images/red_turret190.gif +0 -0
  114. data/images/red_turret200.gif +0 -0
  115. data/images/red_turret210.gif +0 -0
  116. data/images/red_turret220.gif +0 -0
  117. data/images/red_turret230.gif +0 -0
  118. data/images/red_turret240.gif +0 -0
  119. data/images/red_turret250.gif +0 -0
  120. data/images/red_turret260.gif +0 -0
  121. data/images/red_turret270.gif +0 -0
  122. data/images/red_turret280.gif +0 -0
  123. data/images/red_turret290.gif +0 -0
  124. data/images/red_turret300.gif +0 -0
  125. data/images/red_turret310.gif +0 -0
  126. data/images/red_turret320.gif +0 -0
  127. data/images/red_turret330.gif +0 -0
  128. data/images/red_turret340.gif +0 -0
  129. data/images/red_turret350.gif +0 -0
  130. data/images/toolbox.gif +0 -0
  131. data/lib/battlefield.rb +102 -0
  132. data/lib/bullets.rb +39 -0
  133. data/lib/configuration.rb +26 -0
  134. data/lib/explosions.rb +20 -0
  135. data/lib/overloads.rb +10 -0
  136. data/lib/robot.rb +122 -0
  137. data/lib/robotrunner.rb +260 -0
  138. data/lib/tkarena.rb +197 -0
  139. data/lib/toolboxes.rb +28 -0
  140. data/robots/BillDuck.rb +92 -0
  141. data/robots/BotOne.rb +39 -0
  142. data/robots/DuckBill.rb +384 -0
  143. data/robots/DuckBill04.rb +330 -0
  144. data/robots/DuckToEndAllDucks.rb +140 -0
  145. data/robots/EdgeBot.rb +203 -0
  146. data/robots/HuntingDuck.rb +74 -0
  147. data/robots/HyperactiveDuck.rb +15 -0
  148. data/robots/Killer.rb +58 -0
  149. data/robots/Kite.rb +193 -0
  150. data/robots/KoDuck.rb +57 -0
  151. data/robots/LinearShooter.rb +279 -0
  152. data/robots/LuckyDuck.rb +83 -0
  153. data/robots/MoxonoM.rb +85 -0
  154. data/robots/MsgBot.rb +13 -0
  155. data/robots/NervousDuck.rb +13 -0
  156. data/robots/Polisher.rb +15 -0
  157. data/robots/RomBot.rb +514 -0
  158. data/robots/RoomPainter.rb +205 -0
  159. data/robots/Rrrkele.rb +48 -0
  160. data/robots/Seeker.rb +57 -0
  161. data/robots/ShootingStation.rb +15 -0
  162. data/robots/SittingDuck.rb +18 -0
  163. data/robots/SniperDuck.rb +277 -0
  164. data/robots/WallPainter.rb +224 -0
  165. metadata +220 -0
@@ -0,0 +1,203 @@
1
+ # EdgeBot.rb
2
+ require 'robot'
3
+
4
+ class EdgeBot
5
+ include Robot
6
+
7
+ @@east = 0
8
+ @@north = 90
9
+ @@west = 180
10
+ @@south = 270
11
+ @@none = -1
12
+
13
+ @@direction = {
14
+ :east => {
15
+ :angle => @@east,
16
+ :inward_angle => @@west,
17
+ :next_edge => [:north, :south]
18
+ },
19
+ :north => {
20
+ :angle => @@north,
21
+ :inward_angle => @@south,
22
+ :next_edge => [:west, :east]
23
+ },
24
+ :west => {
25
+ :angle => @@west,
26
+ :inward_angle => @@east,
27
+ :next_edge => [:south, :north]
28
+ },
29
+ :south => {
30
+ :angle => @@south,
31
+ :inward_angle => @@north,
32
+ :next_edge => [:east, :west]
33
+ }
34
+ }
35
+
36
+ @@max_robot_turn = 10
37
+ @@max_gun_turn = 30
38
+ @@max_radar_turn = 60
39
+ @@max_speed = 8
40
+
41
+ @@chicken_energy = 10
42
+
43
+ def tick( events )
44
+ if time == 0
45
+ @direction = 0
46
+ @target_angle = Hash.new
47
+ @next_edge = nearest_edge
48
+ @target_angle[:robot] = @@direction[@next_edge][:angle]
49
+ @target_angle[:gun] = @@direction[@next_edge][:angle]
50
+ @target_angle[:radar] = @@direction[@next_edge][:angle]
51
+ @mode = :seek_edge
52
+ @prev_radar_heading = radar_heading
53
+ @fire_power = 3
54
+ end
55
+
56
+ rotate!
57
+ fire( @fire_power )
58
+
59
+ case @mode
60
+ when :seek_edge
61
+ # puts "seek_edge"
62
+ @fire_power = 0.1
63
+ if @turn_complete
64
+ @mode = :move_to_edge
65
+ end
66
+ when :move_to_edge
67
+ # puts "move_to_edge"
68
+ fire( 3 ) unless events['robot_scanned'].empty?
69
+ unless at_edge? @next_edge
70
+ accelerate( 1 )
71
+ else
72
+ @current_edge = @next_edge
73
+ @next_edge = @@direction[@current_edge][:next_edge][@direction]
74
+ @target_angle[:robot] = @@direction[@next_edge][:angle]
75
+ @target_angle[:gun] = @@direction[@current_edge][:inward_angle]
76
+ @target_angle[:radar] = @@direction[@next_edge][:angle]
77
+ @mode = :edge_align
78
+ end
79
+ when :edge_align
80
+ # puts "edge_align"
81
+ @fire_power = 0.1
82
+ if speed != 0
83
+ stop
84
+ end
85
+ if @turn_complete && @prev_radar_heading == radar_heading
86
+ # Pause for a still radar shot before progressing
87
+ @mode = :fire
88
+ end
89
+ if edge_occupied?( @@direction[@next_edge][:angle], events )
90
+ @mode = :clear_edge_align
91
+ @target_angle[:gun] = @@direction[@next_edge][:angle]
92
+ @target_angle[:radar] = @@direction[@next_edge][:angle]
93
+ end
94
+ when :fire
95
+ # puts "fire"
96
+ @fire_power = 0.1
97
+ if velocity < @@max_speed
98
+ accelerate( 1 )
99
+ end
100
+ if at_edge?( @next_edge )
101
+ @current_edge = @next_edge
102
+ @next_edge = @@direction[@current_edge][:next_edge][@direction]
103
+ @target_angle[:robot] = @@direction[@next_edge][:angle]
104
+ @target_angle[:gun] = @@direction[@current_edge][:inward_angle]
105
+ @target_angle[:radar] = @@direction[@next_edge][:angle]
106
+ @mode = :edge_align
107
+ elsif edge_occupied?( @@direction[@next_edge][:angle], events )
108
+ @mode = :clear_edge_align
109
+ @target_angle[:gun] = @@direction[@next_edge][:angle]
110
+ @target_angle[:radar] = @@direction[@next_edge][:angle]
111
+ end
112
+ when :clear_edge_align
113
+ # puts "clear_edge_align"
114
+ @fire_power = 0.1
115
+ if speed != 0
116
+ stop
117
+ end
118
+ if @turn_complete
119
+ @mode = :clear_edge
120
+ end
121
+ when :clear_edge
122
+ @fire_power = 0.1
123
+ if not edge_occupied?( @@direction[@next_edge][:angle], events )
124
+ @target_angle[:gun] = @@direction[@current_edge][:inward_angle]
125
+ @target_angle[:radar] = @@direction[@next_edge][:angle]
126
+ @mode = :edge_align
127
+ elsif energy < @@chicken_energy
128
+ reverse_course!
129
+ end
130
+ end
131
+
132
+ @prev_radar_heading = radar_heading
133
+ end
134
+
135
+ def rotate!
136
+ @turn_complete = true
137
+ if @target_angle[:robot] != @@none && heading != @target_angle[:robot]
138
+ turn( angle_to( @target_angle[:robot], heading, @@max_robot_turn ) )
139
+ @turn_complete = false
140
+ elsif @target_angle[:gun] != @@none && gun_heading != @target_angle[:gun]
141
+ turn_gun( angle_to( @target_angle[:gun], gun_heading, @@max_gun_turn ) )
142
+ @turn_complete = false
143
+ elsif @target_angle[:radar] != @@none && radar_heading != @target_angle[:radar]
144
+ turn_radar( angle_to( @target_angle[:radar], radar_heading, @@max_radar_turn ) )
145
+ @turn_complete = false
146
+ end
147
+ end
148
+
149
+ def angle_to( target_heading, current_heading, max_turn )
150
+ normalised_heading = current_heading - target_heading
151
+ dir = (normalised_heading < 180) ? -1 : 1
152
+ if normalised_heading < max_turn
153
+ return dir * normalised_heading
154
+ else
155
+ return dir * max_turn
156
+ end
157
+ end
158
+
159
+ def at_edge?( edge )
160
+ case edge
161
+ when :east
162
+ return x >= (battlefield_width - size)
163
+ when :north
164
+ return y <= size
165
+ when :west
166
+ return x <= size
167
+ when :south
168
+ return y >= (battlefield_height - size)
169
+ end
170
+ end
171
+
172
+ def edge_occupied?( angle, events )
173
+ return (@prev_radar_heading == angle &&
174
+ radar_heading == angle &&
175
+ !events['robot_scanned'].empty?)
176
+ end
177
+
178
+ def nearest_edge
179
+ min = [:west, x]
180
+ if y < min[1]
181
+ min = [:north, y]
182
+ end
183
+ if battlefield_width - x < min[1]
184
+ min = [:east, battlefield_width - x]
185
+ end
186
+ if battlefield_height - y < min[1]
187
+ min = [:south, battlefield_height - y]
188
+ end
189
+ return min[0]
190
+ end
191
+
192
+ def reverse_course!
193
+ @direction = (@direction + 1) % 2
194
+ @next_edge = @@direction[@current_edge][:next_edge][@direction]
195
+ @target_angle[:robot] = @@direction[@next_edge][:angle]
196
+ @target_angle[:gun] = @@direction[@current_edge][:inward_angle]
197
+ @target_angle[:radar] = @@direction[@next_edge][:angle]
198
+ @mode = :edge_align
199
+ end
200
+
201
+ end
202
+
203
+
@@ -0,0 +1,74 @@
1
+ require 'robot'
2
+
3
+ class HuntingDuck
4
+ include Robot
5
+ def initialize *bf
6
+ if bf.size != 0
7
+ super(bf[0])
8
+ @tourney = false
9
+ else
10
+ super
11
+ @tourney = true
12
+ end
13
+ @time_since=10
14
+ @direction=1
15
+ @rob_distance=200
16
+ end
17
+ def rel_direction(from,to)
18
+ rel = to -from
19
+ if rel > 180
20
+ rel = -360 + rel
21
+ end
22
+ if rel < -180
23
+ rel = 360+rel
24
+ end
25
+ return rel
26
+ end
27
+ def rel_gun_heading
28
+ rel_direction(heading, gun_heading)
29
+ end
30
+ def tick events
31
+ accelerate 1
32
+ @rob_distance=events['robot_scanned'][0][0] unless events['robot_scanned'].empty?
33
+ if @rob_distance > 800
34
+ #make small turret adjustments when distance to enemy is great
35
+ turret_turn=3
36
+ else
37
+ turret_turn=6
38
+ end
39
+ #turn clockwise or counter clockwise?
40
+ @direction = -@direction if Kernel.rand < 0.02
41
+ if !events['got_hit'].empty?
42
+ #if we got hit we'd better shoot back and try to turn away
43
+ fire 2
44
+ turn -10*@direction
45
+ end
46
+ if !events['robot_scanned'].empty?
47
+ fire 3
48
+ @time_since=0
49
+ else
50
+ if @time_since < 15
51
+ #spotted the enemy, moving closer
52
+ #while moving the turret to spot him again
53
+ if Kernel.rand < 0.5 && rel_gun_heading < 30
54
+ turn_gun turret_turn
55
+ elsif rel_gun_heading > -30
56
+ turn_gun -turret_turn
57
+ else
58
+ turn_gun turret_turn
59
+ end
60
+ fire 0.5
61
+ elsif @time_since < 100
62
+ #try to spot the enemy
63
+ turn 10*@direction
64
+ else
65
+ #if we got stuck this should get us out
66
+ turn 5
67
+ end
68
+ end
69
+ @time_since += 1
70
+ end
71
+ end
72
+
73
+
74
+
@@ -0,0 +1,15 @@
1
+ require 'robot'
2
+
3
+ class HyperactiveDuck
4
+ include Robot
5
+
6
+ def tick events
7
+ turn_radar 1 if time == 0
8
+ accelerate 1
9
+ turn 2
10
+ if !events['robot_scanned'].empty? && gun_heat <= 0
11
+ fire 1
12
+ turn_gun -30
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,58 @@
1
+ require 'robot'
2
+
3
+ class Killer
4
+ include Robot
5
+
6
+ def min_max value, m
7
+ value-= 360 if value > 180
8
+ value+= 360 if value < -180
9
+ value = -m if value < -m
10
+ value = m if value > m
11
+ return value
12
+ end
13
+
14
+ def tick events
15
+ @dist = 1000 if @dist.nil?
16
+ @target_heading = 0 if @target_heading.nil?
17
+ @predicted_heading = 0 if @predicted_heading.nil?
18
+ @radar_range = 60 if @radar_range.nil?
19
+ @direction = 1 if @direction.nil?
20
+
21
+ if !events['robot_scanned'].empty?
22
+ @dist = events['robot_scanned'].first.first
23
+ @target_heading = radar_heading - @radar_range * 0.5
24
+ @predicted_heading = @target_heading + 16.0 * (rand(3)-1)
25
+ if (@dist * @radar_range.abs <= 500)
26
+ sx = x + Math::cos(@target_heading / 180 * Math::PI) * @dist
27
+ sy = y - Math::sin(@target_heading / 180 * Math::PI) * @dist
28
+ if @last_scan && (time - @last_scan[2]) == 1
29
+ px = sx + (sx - @last_scan[0]) * (@dist - 90) / 30
30
+ py = sy + (sy - @last_scan[1]) * (@dist - 90) / 30
31
+ @predicted_heading = Math.atan2(y - py, px - x) / Math::PI * 180 % 360
32
+ end
33
+ @last_scan = [sx, sy, time]
34
+ end
35
+ @radar_range = (@dist * @radar_range.abs > 250) ? -@radar_range * 0.5 : -@radar_range
36
+ else
37
+ @radar_range *= -2 if (@radar_range.abs < 60)
38
+ end
39
+
40
+ fire 3
41
+
42
+ if ((x < 100) || (x > 1500) || (y < 100) || (y > 1500)) && (@escape.nil? || @escape.zero?)
43
+ @escape = 30
44
+ @direction *= -1
45
+ end
46
+
47
+ @escape -= 1 if @escape && (@escape > 0)
48
+ accelerate(@direction)
49
+
50
+ turn_body = min_max(@target_heading - heading + 90, 10)
51
+ gun = min_max(@predicted_heading - gun_heading - turn_body, 30)
52
+ radar = min_max(@radar_range - gun - turn_body, 60)
53
+
54
+ turn(turn_body)
55
+ turn_gun(gun)
56
+ turn_radar(radar)
57
+ end
58
+ end
@@ -0,0 +1,193 @@
1
+ # Uses linear targetting and
2
+ # moves in a way that dodges linear targetting.
3
+ # Not very good for melee.
4
+ #
5
+ require 'robot'
6
+ require 'matrix'
7
+
8
+ class Numeric
9
+ def deg2rad
10
+ self * 0.0174532925199433
11
+ end
12
+ def rad2deg
13
+ self * 57.2957795130823
14
+ end
15
+ def sign
16
+ self / abs
17
+ end
18
+ end
19
+ class Array
20
+ def average
21
+ inject{|s,i|s+i} / size.to_f
22
+ end
23
+ def sd
24
+ avg = average
25
+ Math.sqrt( inject(0){|s,i| s+(i-avg)**2} / (size-1.0) )
26
+ end
27
+ end
28
+
29
+ class Kite
30
+ include Robot
31
+
32
+ def initialize *args, &block
33
+ super
34
+ @rt = @radar_scan = 15
35
+ @min_radar_scan = 1.5
36
+ @max_radar_scan = 60.0
37
+ @lock = false
38
+ @firing_threshold = 20.0
39
+ @wanted_turn = @wanted_gun_turn = @wanted_radar_turn = 0
40
+ @rturn_dir = 1
41
+ @racc_dir = 1
42
+ @target_positions = []
43
+ @sd_limit = 30
44
+ end
45
+
46
+ def tick events
47
+ @prev_health = energy if time == 0
48
+ if events['robot_scanned'].empty?
49
+ @radar_scan = [@radar_scan * 1.5, @max_radar_scan].min
50
+ else
51
+ @radar_scan = [@radar_scan * 0.5, @min_radar_scan].max
52
+ end
53
+ @rt = (time/2 % 2 < 1 ? -@radar_scan/2.0 : @radar_scan/2.0) if @radar_scan.abs < @max_radar_scan - 0.1
54
+ @wanted_radar_turn += @rt
55
+ firing_solution events
56
+ @hit = unless events['got_hit'].empty?
57
+ @racc_dir = @racc_dir / @racc_dir.abs
58
+ 20
59
+ else
60
+ (@hit||0) - 1
61
+ end
62
+ if @hit < 0
63
+ @racc_dir = (@min_distance and @min_distance < 450) ? @racc_dir.sign : Math.sin(time*0.1+rand*0.2)
64
+ end
65
+ accelerate(@racc_dir)
66
+ if approaching_wall?
67
+ @wanted_turn = 60 * @rturn_dir
68
+ elsif @target_heading and @wanted_turn <= 1
69
+ @wanted_turn = heading_distance(((@min_distance and @min_distance < 450) ? (90-@rturn_dir.sign*30)*-@racc_dir.sign : 0)+heading, 90+@target_heading)
70
+ elsif rand < 0.3
71
+ @wanted_turn += rand * 10 * @racc_dir.sign * @rturn_dir
72
+ elsif rand < 0.01
73
+ @rturn_dir *= -1
74
+ elsif rand < 0.01
75
+ @racc_dir *= -1
76
+ end
77
+ turn_hull
78
+ turn_turret
79
+ turn_radar_dish
80
+ @prev_health = energy
81
+ end
82
+
83
+ def firing_solution events
84
+ unless events['robot_scanned'].empty?
85
+ last = @target_positions.last || Vector[0,0]
86
+ position = events['robot_scanned'].map{|d|
87
+ tx = x + Math.cos((radar_heading - @radar_scan.abs / 2.0).deg2rad) * d[0]
88
+ ty = y - Math.sin((radar_heading - @radar_scan.abs / 2.0).deg2rad) * d[0]
89
+ Vector[tx,ty]
90
+ }.min{|a,b| (a - last).r <=> (b - last).r }
91
+ @target_positions.push position
92
+ @min_distance = events['robot_scanned'].flatten.min
93
+ end
94
+ @target_positions.shift if @target_positions.size > 10
95
+ @min_distance = nil if @target_positions.empty?
96
+ @target_heading = target_heading
97
+ gtd = heading_distance(gun_heading, @target_heading) if @target_heading
98
+ @firepower = [3*25 / (@vsd||1500)].max / 2.0
99
+ fire @firepower if @on_target
100
+ if gtd and gtd.abs < @firing_threshold
101
+ @wanted_gun_turn = gtd
102
+ @on_target = true
103
+ else
104
+ @wanted_gun_turn = gtd || (gun_radar_distance/3.0)
105
+ @on_target = false
106
+ end
107
+ end
108
+
109
+ def average arr
110
+ arr.inject{|s,i| s+i} * (1.0/arr.size)
111
+ end
112
+
113
+ def target_heading
114
+ return nil if @target_positions.size < 10
115
+ return radar_heading if @min_distance and @min_distance < 200
116
+ lps = (0...5).map{|i| @target_positions[i*2,2] }.map{|pta| average(pta) }
117
+ p4 = lps.last
118
+ vs = lps[0..-2].zip(lps[1..-1]).map{|a,b| (b-a) * 0.5 }
119
+ @vsd = Math.sqrt(vs.map{|v| v[0]}.sd**2 + vs.map{|v| v[1]}.sd**2)
120
+ return nil if @vsd > @sd_limit
121
+ v = average(vs)
122
+ return heading_for(average(lps)) if v.r < 4.0
123
+ p4 = p4 + (v*0.5)
124
+ distance = p4 - Vector[x,y]
125
+ shot_speed = (30/8.0)*v.r
126
+ a = distance[0]**2 + distance[1]**2
127
+ b = 2*distance[0]*v[0] + 2*distance[1]*v[1]
128
+ c = v[0]**2 + v[1]**2 - shot_speed**2
129
+ t = 2*a / (-b + Math.sqrt(b**2-4*a*c))
130
+ ep = p4 + v*t
131
+ estimated_position = Vector[
132
+ [size, [battlefield_width-size, ep[0]].min].max,
133
+ [size, [battlefield_height-size, ep[1]].min].max]
134
+ heading_for(estimated_position) + (rand-0.5)*@vsd*0.2
135
+ end
136
+
137
+ def heading_for(position)
138
+ distance = position - Vector[x,y]
139
+ heading = (Math.atan2(-distance[1], distance[0])).rad2deg
140
+ heading += 360 if heading < 0
141
+ heading
142
+ end
143
+
144
+ def heading_distance h1, h2
145
+ limit h2 - h1, 180
146
+ end
147
+
148
+ def limit value, m
149
+ value -= 360 if value > 180
150
+ value += 360 if value < -180
151
+ value = -m if value < -m
152
+ value = m if value > m
153
+ return value
154
+ end
155
+
156
+ def gun_radar_distance
157
+ heading_distance gun_heading, radar_heading
158
+ end
159
+
160
+ def turn_hull
161
+ turn_amt = [-10.0, [@wanted_turn, 10.0].min].max
162
+ turn turn_amt
163
+ @wanted_turn -= turn_amt
164
+ @wanted_gun_turn -= turn_amt
165
+ @wanted_radar_turn -= turn_amt
166
+ end
167
+
168
+ def turn_turret
169
+ turn_amt = [-30.0, [@wanted_gun_turn, 30.0].min].max
170
+ turn_gun turn_amt
171
+ @wanted_gun_turn -= turn_amt
172
+ @wanted_radar_turn -= turn_amt
173
+ end
174
+
175
+ def turn_radar_dish
176
+ turn_amt = [-60.0, [@wanted_radar_turn, 60.0].min].max
177
+ turn_radar turn_amt
178
+ @wanted_radar_turn -= turn_amt
179
+ end
180
+
181
+ def approaching_wall?
182
+ if not ( (velocity > 0) ^ heading.between?(0.0, 180.0) )
183
+ y < 100
184
+ else
185
+ y > battlefield_height - 100
186
+ end or if not ( (velocity > 0) ^ heading.between?(90.0, 270.0) )
187
+ x < 100
188
+ else
189
+ x > battlefield_width - 100
190
+ end
191
+ end
192
+
193
+ end