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,197 @@
1
+ require 'tk'
2
+ require 'base64'
3
+
4
+ TkRobot = Struct.new(:body, :gun, :radar, :speech, :info, :status)
5
+
6
+ class TkArena
7
+
8
+ attr_reader :battlefield, :xres, :yres
9
+ attr_accessor :speed_multiplier, :on_game_over_handlers
10
+ attr_accessor :canvas, :boom, :robots, :bullets, :explosions, :colors, :toolboxes, :toolbox_img
11
+ attr_accessor :default_skin_prefix, :path_prefix
12
+
13
+ def initialize battlefield, xres, yres, speed_multiplier
14
+ @battlefield = battlefield
15
+ @xres, @yres = xres, yres
16
+ @speed_multiplier = speed_multiplier
17
+ @text_colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff', '#777777']
18
+
19
+ # TODO : Perhaps a better way to do it ...
20
+ # regarder Rubygems pour avoir le path direct ..
21
+ table_map = Gem.location_of_caller.first.split('/')
22
+ 2.times do
23
+ table_map.pop
24
+ end
25
+ @path_prefix = table_map.join('/')
26
+
27
+ @default_skin_prefix = "images/red_"
28
+ @on_game_over_handlers = []
29
+ init_canvas
30
+ init_simulation
31
+ end
32
+
33
+ def on_game_over(&block)
34
+ @on_game_over_handlers << block
35
+ end
36
+
37
+ def read_gif name, c1, c2, c3
38
+ data = nil
39
+ open(name, 'rb') do |f|
40
+ data = f.read()
41
+ ncolors = 2**(1 + data[10][0] + data[10][1] * 2 + data[10][2] * 4)
42
+ ncolors.times do |j|
43
+ data[13 + j * 3 + 0], data[13 + j * 3 + 1], data[13 + j * 3 + 2] =
44
+ data[13 + j * 3 + c1], data[13 + j * 3 + c2], data[13 + j * 3 + c3]
45
+ end
46
+ end
47
+ TkPhotoImage.new(:data => Base64.encode64(data))
48
+ end
49
+
50
+ def usage
51
+ puts "usage: rrobots.rb <FirstRobotClassName[.rb]> <SecondRobotClassName[.rb]> <...>"
52
+ puts "\tthe names of the rb files have to match the class names of the robots"
53
+ puts "\t(up to 8 robots)"
54
+ puts "\te.g. 'ruby rrobots.rb SittingDuck NervousDuck'"
55
+ exit
56
+ end
57
+
58
+ def init_canvas
59
+ @canvas = TkCanvas.new(:height=>yres, :width=>xres, :scrollregion=>[0, 0, xres, yres], :background => '#000000').pack
60
+ @colors = []
61
+ [[0,1,1],[1,0,1],[1,1,0],[0,0,1],[1,0,0],[0,1,0],[0,0,0],[1,1,1]][0...@battlefield.robots.length].zip(@battlefield.robots) do |color, robot|
62
+ bodies, guns, radars = [], [], []
63
+ image_path = robot.skin_prefix || @default_skin_prefix
64
+ image_path = "#{@path_prefix}/#{image_path}"
65
+ reader = robot.skin_prefix ? lambda{|fn| TkPhotoImage.new(:file => fn) } : lambda{|fn| read_gif(fn, *color)}
66
+ 36.times do |i|
67
+ bodies << reader["#{image_path}body#{(i*10).to_s.rjust(3, '0')}.gif"]
68
+ guns << reader["#{image_path}turret#{(i*10).to_s.rjust(3, '0')}.gif"]
69
+ radars << reader["#{image_path}radar#{(i*10).to_s.rjust(3, '0')}.gif"]
70
+ end
71
+ @colors << TkRobot.new(bodies << bodies[0], guns << guns[0], radars << radars[0])
72
+ end
73
+
74
+ @boom = (0..14).map do |i|
75
+ TkPhotoImage.new(:file => "#{path_prefix}/images/explosion#{i.to_s.rjust(2, '0')}.gif")
76
+ end
77
+ @toolbox_img = TkPhotoImage.new(:file => "#{path_prefix}/images/toolbox.gif")
78
+ end
79
+
80
+ def init_simulation
81
+ @robots, @bullets, @explosions, @toolboxes = {}, {}, {}, {}
82
+ TkTimer.new(20, -1, Proc.new{
83
+ begin
84
+ draw_frame
85
+ rescue => err
86
+ puts err.class, err, err.backtrace
87
+ raise
88
+ end
89
+ }).start
90
+ end
91
+
92
+ def draw_frame
93
+ simulate(@speed_multiplier)
94
+ draw_battlefield
95
+ end
96
+
97
+ def simulate(ticks=1)
98
+ @explosions.reject!{|e,tko| @canvas.delete(tko) if e.dead; e.dead }
99
+ @bullets.reject!{|b,tko| @canvas.delete(tko) if b.dead; b.dead }
100
+ @toolboxes.reject!{|t,tko| @canvas.delete(tko) if t.dead; t.dead }
101
+ @robots.reject! do |ai,tko|
102
+ if ai.dead
103
+ tko.status.configure(:text => "#{ai.name.ljust(20)} dead")
104
+ tko.each{|part| @canvas.delete(part) if part != tko.status}
105
+ true
106
+ end
107
+ end
108
+ ticks.times do
109
+ if @battlefield.game_over
110
+ @on_game_over_handlers.each{|h| h.call(@battlefield) }
111
+ unless @game_over
112
+ winner = @robots.keys.first
113
+ whohaswon = if winner.nil?
114
+ "Draw!"
115
+ elsif @battlefield.teams.all?{|k,t|t.size<2}
116
+ "#{winner.name} won!"
117
+ else
118
+ "Team #{winner.team} won!"
119
+ end
120
+ text_color = winner ? winner.team : 7
121
+ @game_over = TkcText.new(canvas,
122
+ :fill => @text_colors[text_color],
123
+ :anchor => 'c', :coords => [400,400], :font=>'courier 36', :justify => 'center',
124
+ :text => "GAME OVER\n#{whohaswon}")
125
+ end
126
+ end
127
+ @battlefield.tick
128
+ end
129
+ end
130
+
131
+ def draw_battlefield
132
+ draw_toolboxes
133
+ draw_robots
134
+ draw_bullets
135
+ draw_explosions
136
+ end
137
+
138
+ def draw_robots
139
+ @battlefield.robots.each_with_index do |ai, i|
140
+ next if ai.dead
141
+ @robots[ai] ||= TkRobot.new(
142
+ TkcImage.new(@canvas, 0, 0),
143
+ TkcImage.new(@canvas, 0, 0),
144
+ TkcImage.new(@canvas, 0, 0),
145
+ TkcText.new(@canvas,
146
+ :fill => @text_colors[ai.team],
147
+ :anchor => 's', :justify => 'center', :coords => [ai.x / 2, ai.y / 2 - ai.size / 2]),
148
+ TkcText.new(@canvas,
149
+ :fill => @text_colors[ai.team],
150
+ :anchor => 'n', :justify => 'center', :coords => [ai.x / 2, ai.y / 2 + ai.size / 2]),
151
+ TkcText.new(@canvas,
152
+ :fill => @text_colors[ai.team],
153
+ :anchor => 'nw', :coords => [10, 15 * i + 10], :font => TkFont.new("courier 9")))
154
+ @robots[ai].body.configure( :image => @colors[ai.team].body[(ai.heading+5) / 10],
155
+ :coords => [ai.x / 2, ai.y / 2])
156
+ @robots[ai].gun.configure( :image => @colors[ai.team].gun[(ai.gun_heading+5) / 10],
157
+ :coords => [ai.x / 2, ai.y / 2])
158
+ @robots[ai].radar.configure(:image => @colors[ai.team].radar[(ai.radar_heading+5) / 10],
159
+ :coords => [ai.x / 2, ai.y / 2])
160
+ @robots[ai].speech.configure(:text => "#{ai.speech}",
161
+ :coords => [ai.x / 2, ai.y / 2 - ai.size / 2])
162
+ @robots[ai].info.configure(:text => "#{ai.name}\n#{'|' * (ai.energy / 5)}",
163
+ :coords => [ai.x / 2, ai.y / 2 + ai.size / 2])
164
+ @robots[ai].status.configure(:text => "#{ai.name.ljust(20)} #{'%.1f' % ai.energy}")
165
+ end
166
+ end
167
+
168
+ def draw_bullets
169
+ @battlefield.bullets.each do |bullet|
170
+ @bullets[bullet] ||= TkcOval.new(
171
+ @canvas, [-2, -2], [3, 3],
172
+ :fill=>'#'+("%02x" % (128+bullet.energy*14).to_i)*3)
173
+ @bullets[bullet].coords(
174
+ bullet.x / 2 - 2, bullet.y / 2 - 2,
175
+ bullet.x / 2 + 3, bullet.y / 2 + 3)
176
+ end
177
+ end
178
+
179
+ def draw_explosions
180
+ @battlefield.explosions.each do |explosion|
181
+ @explosions[explosion] ||= TkcImage.new(@canvas, explosion.x / 2, explosion.y / 2)
182
+ @explosions[explosion].image(boom[explosion.t])
183
+ end
184
+ end
185
+
186
+ def draw_toolboxes
187
+ @battlefield.toolboxes.each do |toolbox|
188
+ @toolboxes[toolbox] ||= TkcImage.new(@canvas, toolbox.x / 2, toolbox.y / 2)
189
+ @toolboxes[toolbox].image(toolbox_img)
190
+ end
191
+ end
192
+
193
+ def run
194
+ Tk.mainloop
195
+ end
196
+
197
+ end
@@ -0,0 +1,28 @@
1
+ class Toolbox
2
+ attr_accessor :x
3
+ attr_accessor :y
4
+ attr_accessor :t
5
+ attr_accessor :dead
6
+ attr_accessor :energy
7
+
8
+ def initialize bf, x, y, t, energy
9
+ @x, @y, @t = x, y, t
10
+ @battlefield, @energy, @dead = bf, energy, false
11
+ end
12
+
13
+ def state
14
+ {:x=>x, :y=>y, :energy=>energy}
15
+ end
16
+
17
+ def tick
18
+ @t += 1
19
+ @dead ||= t > @battlefield.config.toolboxes[:life_time]
20
+ @battlefield.robots.each do |robot|
21
+ if Math.hypot(@y - robot.y, robot.x - @x) < 20 && (!robot.dead)
22
+ healing = robot.heal(self)
23
+ @dead = true
24
+ end
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,92 @@
1
+ require 'robot'
2
+
3
+ class BillDuck
4
+ include Robot
5
+ def min(a,b)
6
+ (a < b)? a : b
7
+ end
8
+ def aimtank(angle,rate)
9
+ error = (360 + angle - heading) % 360
10
+ if error > 180
11
+ turn -min(360-error,rate)
12
+ else
13
+ turn min(error,rate)
14
+ end
15
+ error == 0
16
+ end
17
+ def aimgun(angle,rate)
18
+ error = (360 + angle - gun_heading) % 360
19
+ if error > 180
20
+ turn_gun -min(360-error,rate)
21
+ else
22
+ turn_gun min(error,rate)
23
+ end
24
+ error == 0
25
+ end
26
+ def aimrad(angle,rate)
27
+ error = (360 + angle - radar_heading) % 360
28
+ if error > 180
29
+ turn_radar -min(360-error,rate)
30
+ else
31
+ turn_radar min(error,rate)
32
+ end
33
+ error == 0
34
+ end
35
+ def tick events
36
+ #mode nil is startup and initialize variables
37
+ if @mode == nil
38
+ @mode = 0
39
+ @stage = 0
40
+ @dir = 0
41
+ @firecnt = 0
42
+ @timer = 0
43
+ @sincehit = 100
44
+ @sinceblip = 100
45
+ #mode 0 is turn to new heading
46
+ elsif @mode == 0
47
+ a,b,c = aimtank(@dir*90,10),aimgun(@dir*90,30),aimrad(@dir*90,60)
48
+ @stage = 0
49
+ @mode = 1 if a and b and c
50
+ #mode 1 is look forward down the wall
51
+ elsif @mode == 1
52
+ (@stage%2==0)? aimrad(@dir*90 + 5,60) : aimrad(@dir*90-5,60)
53
+ if @stage > 2 and @sinceblip > 2
54
+ @mode,@stage = 2,0
55
+ else
56
+ @stage += 1
57
+ end
58
+ #mode 2 is look backward down the wall
59
+ elsif @mode == 2
60
+ if @stage == 0
61
+ a,b = aimgun(@dir*90 + 180,30), aimrad(@dir*90 + 180,60)
62
+ @stage += 1 if a and b
63
+ else
64
+ (@stage%2==0)? aimrad(@dir*90 + 185,10) : aimrad(@dir*90 +175,10)
65
+ @stage += 1
66
+ end
67
+ if @stage > 2 and @sinceblip > 2
68
+ @mode,@stage = 3,0
69
+ end
70
+ #mode 3 is scan towards the center of the arena and run down the wall
71
+ elsif @mode == 3
72
+ if @stage == 0
73
+ a,b = aimgun(@dir*90 + 90,30), aimrad(@dir*90 + 80,60)
74
+ @stage += 1 if a and b
75
+ else
76
+ @stage += 1
77
+ (@stage%2 == 0)? aimrad(@dir*90 + 90,10) : aimrad(@dir*90 + 80,10)
78
+ end
79
+ end
80
+ walls = [battlefield_width - x,y,x,battlefield_height - y]
81
+ if walls[@dir] < 100
82
+ @mode,@stage,@dir = 0,0,(@dir+1)%4
83
+ end
84
+ accelerate 1
85
+ @sincehit += 1
86
+ @sincehit = 0 if not events['got_hit'].empty?
87
+ @sinceblip += 1
88
+ @sinceblip = 0 if not events['robot_scanned'].empty?
89
+ fire 0.1
90
+ STDOUT.flush
91
+ end
92
+ end
@@ -0,0 +1,39 @@
1
+ require 'robot'
2
+
3
+ class BotOne
4
+ include Robot
5
+ def tick events
6
+ @rapid_fire = 0 if @rapid_fire.nil?
7
+ @last_seen = 0 if @last_seen.nil?
8
+ @turn_speed = 3 if @turn_speed.nil?
9
+
10
+ if time - @last_seen > 200
11
+ @turn_speed *= -1
12
+ @last_seen = time
13
+ end
14
+
15
+ turn @turn_speed
16
+
17
+ if( @rapid_fire > 0 )
18
+ fire 0.84
19
+ turn_gun -(@turn_speed / @turn_speed) *2
20
+ @rapid_fire = @rapid_fire - 1
21
+ else
22
+ turn_gun @turn_speed * 1.25
23
+ end
24
+
25
+ if( !events['robot_scanned'].empty? )
26
+ @turn_speed *= -1
27
+ @last_seen = time
28
+ @rapid_fire = 20
29
+ end
30
+ @last_hit = time unless events['got_hit'].empty?
31
+ if @last_hit && time - @last_hit < 20
32
+ accelerate(-1)
33
+ else
34
+ accelerate 1
35
+ end
36
+ end
37
+ end
38
+
39
+
@@ -0,0 +1,384 @@
1
+ require 'robot'
2
+ class Tracking
3
+ def debug(a)
4
+ print a if @trkdbg
5
+ STDOUT.flush if @trkdbg
6
+ end
7
+ def initialize(width,height)
8
+ @trkdbg = false
9
+ @tracking = []
10
+ @width = width
11
+ @height = height
12
+ end
13
+ def add(x,y,angle,dist,time)
14
+ @tracking << [x + Math::cos(angle.to_rad)*dist,y + Math::sin(angle.to_rad)*dist,time]
15
+ debug "added track angle=#{angle},dist=#{dist},#{@tracking.last.inspect}\n"
16
+ end
17
+ def trim(time)
18
+ #delete really old samples
19
+ @tracking.delete_if{|e| time - e[2] > 30}
20
+ #limit to 10 samples
21
+ @tracking.shift while @tracking.size>10
22
+ #eliminate samples if they came from a different robot. we can tell this because they have max speed of 8
23
+ gap = 0
24
+ (@tracking.size- 1).times{|i|
25
+ if v=velocity(@tracking[i],@tracking[i+1]) > 25
26
+ gap = i+1
27
+ debug "traking gap #{@tracking[i].inspect},#{@tracking[i+1].inspect},v\n"
28
+ end
29
+ }
30
+ gap.times{@tracking.shift}
31
+ #normalize the time
32
+ #@tracking.size.times{|i| @tracking[@tracking.size-1-i][2] -=@tracking[0][2]}
33
+ end
34
+ def velocity (e1,e2)
35
+ distance(e1[0],e1[1],e2[0],e2[1])/(e1[2]-e2[2]).abs
36
+ end
37
+ def distance (x1,y1,x2,y2)
38
+ ((x1-x2)**2 + (y1-y2)**2)**(0.5)
39
+ end
40
+ def findline
41
+ sx=sy=st=sxt=syt=stt=0.0
42
+ @tracking.each{|e|
43
+ debug " findline element = #{e.inspect}\n"
44
+ sx += e[0]
45
+ sxt += e[2]*e[0]
46
+ sy += e[1]
47
+ syt += e[2]*e[1]
48
+ st += e[2]
49
+ stt += e[2]*e[2]
50
+ }
51
+ n=@tracking.size
52
+ c2 = (sxt/st-sx/n)/(stt/st-st/n)
53
+ c1 = sx/n-(st/n)*c2
54
+ f2 = (syt/st-sy/n)/(stt/st-st/n)
55
+ f1 = sy/n-(st/n)*f2
56
+ debug "x = #{c2}t + #{c1}\n"
57
+ debug "y = #{f2}t + #{f1}\n"
58
+ [c2,c1,f2,f1]
59
+ end
60
+ def predict(x,y,time)
61
+ trim(time)
62
+ if @tracking.size < 1
63
+ return false
64
+ elsif @tracking.size == 1
65
+ interceptx,intercepty = @tracking[0][0],@tracking[0][1]
66
+ else
67
+ a,b,c,d = findline
68
+ intercepttime = time + distance(a*time+b,c*time+d,x,y)/60.0
69
+ #interceptx,intercepty = limitcoord(intercepttime*a + b, intercepttime*c+d)
70
+ interceptx,intercepty = intercepttime*a + b, intercepttime*c+d
71
+ end
72
+ debug"intercept at (#{interceptx},#{intercepty},#{intercepttime})\n"
73
+ angle = (Math.atan2(intercepty - y,interceptx - x) * 180 / Math::PI)%360
74
+ debug "firing angle is #{angle}\n"
75
+ angle
76
+ end
77
+ def limitcoord(x,y)
78
+ nx=[x,0.0].max
79
+ nx = [nx,@width.to_f].min
80
+ ny = [ny,@height.to_f].min
81
+ [nx,ny]
82
+ end
83
+ end
84
+
85
+ class DuckBill
86
+ include Robot
87
+ @@ScanRes = [1,2,4,8,16,32,60]
88
+ # ###########
89
+ # # Initialize
90
+ # ###########
91
+ def initialize *bf
92
+ if bf.size != 0
93
+ super(bf[0])
94
+ @tourney = false
95
+ else
96
+ super
97
+ @tourney = true
98
+ end
99
+ @mode = 0 # mode of high level logic
100
+ @stage = 0 # sequences within mode
101
+ @dir = 0 # direction we are going
102
+ @walldir = 1 # dirrection we travel around perimeter in, 1 = ccw,-1=cw
103
+ @hit_filter = 0 #low pass filter tells how much damage we are taking
104
+ @sincehit = 100 #how long since we were hit
105
+ @sinceblip = 100 #how long since we saw someone in our radar
106
+ @since_evade = 0 #time since we took evasive action
107
+ @closest = 0 #distance to closest robot scanned since last tick
108
+ @range = 10000 #distance of closest robot
109
+ @mytrack = Tracking.new(battlefield_width,battlefield_height)
110
+ @turns = [0,0,0,0,0,0,0,0,0,0,0,0] # holds data for turn/aim calculations
111
+ end
112
+ # ###########
113
+ # # Controls
114
+ # ###########
115
+ def min(a,b)
116
+ (a < b)? a : b
117
+ end
118
+ def max(a,b)
119
+ (a > b)? a : b
120
+ end
121
+ #dir is 1 for ccw, -1 for cw, and 0 for whichever is quickest
122
+ def aimtank(angle,rate=10,dir=0)
123
+ @turns[0,3] = angle%360,rate,dir
124
+ angle%360 == heading
125
+ end
126
+ def aimgun(angle,rate=30,dir=0)
127
+ @turns[4,3] = angle%360,rate,dir
128
+ angle%360 == gun_heading
129
+ end
130
+ def aimrad(angle,rate=60,dir=0)
131
+ @turns[8,3] = angle%360,rate,dir
132
+ angle%360 == radar_heading
133
+ end
134
+ def doturns
135
+ #this translates directional commands from robot into motor actions
136
+ #turns: 0=desired heading, 1=max speed,2=dir[1=ccw,-1=cw,0=fastest],
137
+ # 3=computed turn, 0-3 for tank, 4-7 for gun, 8-11 for radar
138
+ #compute turns for tank, gun, and radar headings
139
+ #print "computed turns = #{@turns.inspect}\n"
140
+ ccw = (@turns[0] - heading) % 360
141
+ cw = 360 - ccw
142
+ dir = (@turns[2] == 0)? ((ccw<cw)? 1 : -1) : @turns[2]
143
+ @turns[3] = dir * min((dir==1)? ccw : cw,@turns[1])
144
+ ccw = (@turns[4] - @turns[3] - gun_heading) % 360
145
+ cw = 360 - ccw
146
+ dir = (@turns[6] == 0)? ((ccw<cw)? 1 : -1) : @turns[6]
147
+ @turns[7] = dir * min((dir==1)? ccw : cw,@turns[5])
148
+ ccw = (@turns[8] - @turns[7] - @turns[3] - radar_heading) % 360
149
+ cw = 360 - ccw
150
+ dir = (@turns[10] == 0)? ((ccw<cw)? 1 : -1) : @turns[10]
151
+ @turns[11] = dir * min(((dir==1)? ccw : cw),@turns[9])
152
+ #print "computed turns = #{@turns.inspect}\n"
153
+ turn @turns[3]
154
+ turn_gun @turns[7]
155
+ turn_radar @turns[11]
156
+ end
157
+
158
+ # ###########
159
+ # # TICK, the Robot code
160
+ # ###########
161
+ def tick events
162
+ @outerlimit = (battlefield_width + battlefield_height) * 3
163
+ #print "mode=#{@mode},stage=#{@stage},dir=#{@dir},walldir=#{@walldir}\n"
164
+ #print "at (#{x},#{y}) at time #{time},res=#{@trk_res}\n"
165
+ #mode nil is startup and initialize variables
166
+ #STDOUT.flush
167
+ # ###########
168
+ # # Sensors
169
+ # ###########
170
+ @since_evade += 1
171
+ @sincehit += 1
172
+ @sincehit = 0 if not events['got_hit'].empty?
173
+ events['got_hit'].each{|e| @hit_filter += e.first}
174
+ @hit_filter *= 0.99
175
+ if events['robot_scanned'].empty?
176
+ @sinceblip += 1
177
+ @closest = @outerlimit
178
+ #print"\n"
179
+ else
180
+ @closest = events['robot_scanned'].collect{|e| e.first}.sort.first
181
+ @sinceblip = 0
182
+ #print ",blip=#{@closest}\n"
183
+ end
184
+ # ###########
185
+ # # High level logic - state machine
186
+ # ###########
187
+ #print "sincehit=#{@sincehit},closest=#{@closest},range=#{@range}\n"
188
+ #mode 0 is orient tank
189
+ if @mode == 0
190
+ @stage = 0
191
+ @range = @outerlimit
192
+ @mode = 1 if aimrad(@dir*90)
193
+ #mode 1 find range of nearest target
194
+ elsif @mode == 1
195
+ #setup radar for a scan
196
+ if @stage==0
197
+ aimrad(@dir*90 + 180,60,1)
198
+ @range = min(@range,@closest)
199
+ @stage +=1
200
+ #continue around for full circle
201
+ elsif @stage == 1
202
+ @range = min(@range,@closest)
203
+ if aimrad(@dir*90,60,1)
204
+ #did we see a bot?
205
+ if @range == @outerlimit
206
+ @stage = 0
207
+ else
208
+ @mode = 2
209
+ @stage = 0
210
+ end
211
+ end
212
+ end
213
+ #mode 2: find the nearestbot
214
+ elsif @mode == 2
215
+ #start next circle to re find the closest bot
216
+ if @stage == 0
217
+ #print "range is #{@range}\n"
218
+ aimrad(@dir*90 + 180,60,1)
219
+ @stage +=1
220
+ #continue scan for the closest bot
221
+ elsif @stage == 1
222
+ #print "dir=#{@dir},angle=#{radar_heading}, closest=#{@closest}\n"
223
+ if @closest < @range * 1.25
224
+ @range = @closest
225
+ @mode = 3
226
+ @stage = 0
227
+ @tangle = radar_heading
228
+ #print "found target at angle #{@tangle}\n"
229
+ #if we finished the scan, and didn't find close target, recompute range
230
+ elsif aimrad(@dir*90,60,1)
231
+ @mode = 0
232
+ @stage =0
233
+ end
234
+ end
235
+ #mode 3 is tracking bot
236
+ elsif @mode == 3
237
+ #entry from previous mode, determine whether to scan ccw or cw
238
+ if @stage == 0
239
+ @trk_dir,@trk_res,@stage = -1,4,2
240
+ #first scan in this direction
241
+ elsif @stage == 1
242
+ if @closest < @range * 1.25
243
+ @range = @closest
244
+ @trk_dir = -@trk_dir
245
+ @trk_res = max(@trk_res - 1,0)
246
+ @mytrack.add(x,y,@radar_heading, @range , time) if @trk_res < 3
247
+ else
248
+ @stage = 2
249
+ end
250
+ #second scan in this direction
251
+ elsif @stage == 2
252
+ if @closest < @range * 1.25
253
+ @range = @closest
254
+ @trk_dir = -@trk_dir
255
+ @trk_res = max(@trk_res - 1,0)
256
+ @mytrack.add(x,y,@radar_heading, @range , time) if @trk_res < 3
257
+ @stage = 1
258
+ else
259
+ @trk_dir = -@trk_dir
260
+ @trk_res = min(@trk_res + 2,4)
261
+ @stage = 3
262
+ end
263
+ #the target bot has moved out of our window, expand the window
264
+ elsif @stage == 3
265
+ if @closest < @range * 1.25
266
+ @range = @closest
267
+ @trk_dir = - @trk_dir
268
+ @trk_res = max(@trk_res - 2,0)
269
+ @mytrack.add(x,y,@radar_heading, @range , time) if @trk_res < 3
270
+ @stage = 1
271
+ elsif @trk_res < 6
272
+ @trk_dir = - @trk_dir
273
+ @trk_res = @trk_res +1
274
+ else
275
+ #we lost our target, reaquire from scratch
276
+ @mode = 0
277
+ @stage = 0
278
+ end
279
+ end
280
+ @tangle += @@ScanRes[@trk_res] * @trk_dir
281
+ aimrad(@tangle)
282
+ #print"tangle=#{@tangle}, res=#{@@ScanRes[@trk_res]}, rot=#{@trk_dir}\n"
283
+ elsif @mode == 4
284
+ #determine which corner to go to from a corner
285
+ if @stage == 0
286
+ @stage += 1 if aimrad(@dir*90 + 95*@walldir)
287
+ #first scan in direction of prev corner
288
+ elsif @stage == 1
289
+ aimrad(@dir*90 + 60*@walldir)
290
+ @stage += 1
291
+ #save count of robots in next corner, and swing radar to previous corner
292
+ elsif @stage == 2
293
+ @prevCorner = events['robot_scanned'].size
294
+ aimrad(@dir*90 + 30*@walldir)
295
+ @stage += 1
296
+ elsif @stage == 3
297
+ aimrad(@dir*90 -5*@walldir)
298
+ @stage += 1
299
+ elsif @stage == 4
300
+ @nextCorner = events['robot_scanned'].size
301
+ #print "next corner=#{@nextCorner}, prev corner=#{@prevCorner}\n"
302
+ if @nextCorner > @prevCorner
303
+ @dir = (@dir + @walldir)%4
304
+ @walldir *= -1
305
+ end
306
+ @stage = 0
307
+ @mode = 0
308
+ end
309
+ elsif @mode == 5
310
+ #determine which corner to go to from middle of wall
311
+ if @stage == 0
312
+ @stage += 1 if aimrad(@dir*90 - 5*@walldir)
313
+ #first scan in direction of prev corner
314
+ elsif @stage == 1
315
+ aimrad(@dir*90 + 30*@walldir)
316
+ @stage += 1
317
+ #save count of robots in next corner, and swing radar to previous corner
318
+ elsif @stage == 2
319
+ @nextCorner = events['robot_scanned'].size
320
+ aimrad(@dir*90 + 150*@walldir)
321
+ @stage += 1
322
+ elsif @stage == 3
323
+ @stage += 1
324
+ aimrad(@dir*90 -150*@walldir)
325
+ elsif @stage == 4
326
+ aimrad(@dir*90 -185*@walldir)
327
+ @stage += 1
328
+ elsif @stage == 5
329
+ @prevCorner = events['robot_scanned'].size
330
+ #print "next corner=#{@nextCorner}, prev corner=#{@prevCorner}\n"
331
+ if @nextCorner > @prevCorner
332
+ @dir = (@dir + 2)%4
333
+ @walldir *= -1
334
+ end
335
+ @stage = 0
336
+ @mode = 0
337
+ end
338
+ end
339
+ #compute the distances to the four walls
340
+ walls = [battlefield_width - x,y,x,battlefield_height - y]
341
+ #hug the wall, if we are slightly off the wall, than move back to the wall
342
+ toleftwall,torightwall = walls[(@dir+1)%4],walls[(@dir-1)%4]
343
+ #print "wallroom left=#{toleftwall}, right=#{torightwall}\n"
344
+ if toleftwall > 80 and toleftwall < 200
345
+ aimtank(@dir * 90 + 20)
346
+ elsif torightwall > 80 and torightwall < 200
347
+ aimtank(@dir * 90 - 20)
348
+ else
349
+ aimtank(@dir * 90)
350
+ end
351
+ #If we reach a corner or wall, turn towards farthest corner on this wall
352
+ if walls[@dir] < 100
353
+ if toleftwall > torightwall
354
+ @walldir = 1 #we are now going ccw
355
+ @dir = (@dir+1)%4 # turn ccw
356
+ #print "turn left\n"
357
+ else
358
+ @walldir = -1 #we are now going cw
359
+ @dir = (@dir-1)%4 #turn cw
360
+ #print "turn right\n"
361
+ end
362
+ #don't check corners at T junction
363
+ if toleftwall > 100 and torightwall > 100
364
+ @mode = 5 # determin weather it is safer ahead or behind
365
+ @stage = 0
366
+ else
367
+ @mode = 4 # determin if previous corner was safer
368
+ @stage = 0
369
+ end
370
+ #If we are getting hammered, turn now to evade damage
371
+ # once we evade, avoid making another evasive manuver or we will turn in circles
372
+ elsif @hit_filter > 400 and @since_evade > 100
373
+ @dir = (@dir+@walldir)%4
374
+ @hit_filter = 0
375
+ @since_evade = 0
376
+ end
377
+ accelerate 1
378
+ aim = @mytrack.predict(x,y,time) || (@dir * 90)%360
379
+ aimgun(aim)
380
+ fire 0.1
381
+ doturns #we already computed our turns, now execute them
382
+ STDOUT.flush
383
+ end
384
+ end