UG_RRobots 1.2

Sign up to get free protection for your applications and to get access to all the features.
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