UG_RRobots 1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rrobots +202 -0
- data/bin/tournament +413 -0
- data/config/rrobots.yml +15 -0
- data/contribs/allbots.rb +0 -0
- data/doc/manual.rdoc +126 -0
- data/doc/manual_fr.rdoc +129 -0
- data/images/explosion00.gif +0 -0
- data/images/explosion01.gif +0 -0
- data/images/explosion02.gif +0 -0
- data/images/explosion03.gif +0 -0
- data/images/explosion04.gif +0 -0
- data/images/explosion05.gif +0 -0
- data/images/explosion06.gif +0 -0
- data/images/explosion07.gif +0 -0
- data/images/explosion08.gif +0 -0
- data/images/explosion09.gif +0 -0
- data/images/explosion10.gif +0 -0
- data/images/explosion11.gif +0 -0
- data/images/explosion12.gif +0 -0
- data/images/explosion13.gif +0 -0
- data/images/explosion14.gif +0 -0
- data/images/red_body000.gif +0 -0
- data/images/red_body010.gif +0 -0
- data/images/red_body020.gif +0 -0
- data/images/red_body030.gif +0 -0
- data/images/red_body040.gif +0 -0
- data/images/red_body050.gif +0 -0
- data/images/red_body060.gif +0 -0
- data/images/red_body070.gif +0 -0
- data/images/red_body080.gif +0 -0
- data/images/red_body090.gif +0 -0
- data/images/red_body100.gif +0 -0
- data/images/red_body110.gif +0 -0
- data/images/red_body120.gif +0 -0
- data/images/red_body130.gif +0 -0
- data/images/red_body140.gif +0 -0
- data/images/red_body150.gif +0 -0
- data/images/red_body160.gif +0 -0
- data/images/red_body170.gif +0 -0
- data/images/red_body180.gif +0 -0
- data/images/red_body190.gif +0 -0
- data/images/red_body200.gif +0 -0
- data/images/red_body210.gif +0 -0
- data/images/red_body220.gif +0 -0
- data/images/red_body230.gif +0 -0
- data/images/red_body240.gif +0 -0
- data/images/red_body250.gif +0 -0
- data/images/red_body260.gif +0 -0
- data/images/red_body270.gif +0 -0
- data/images/red_body280.gif +0 -0
- data/images/red_body290.gif +0 -0
- data/images/red_body300.gif +0 -0
- data/images/red_body310.gif +0 -0
- data/images/red_body320.gif +0 -0
- data/images/red_body330.gif +0 -0
- data/images/red_body340.gif +0 -0
- data/images/red_body350.gif +0 -0
- data/images/red_radar000.gif +0 -0
- data/images/red_radar010.gif +0 -0
- data/images/red_radar020.gif +0 -0
- data/images/red_radar030.gif +0 -0
- data/images/red_radar040.gif +0 -0
- data/images/red_radar050.gif +0 -0
- data/images/red_radar060.gif +0 -0
- data/images/red_radar070.gif +0 -0
- data/images/red_radar080.gif +0 -0
- data/images/red_radar090.gif +0 -0
- data/images/red_radar100.gif +0 -0
- data/images/red_radar110.gif +0 -0
- data/images/red_radar120.gif +0 -0
- data/images/red_radar130.gif +0 -0
- data/images/red_radar140.gif +0 -0
- data/images/red_radar150.gif +0 -0
- data/images/red_radar160.gif +0 -0
- data/images/red_radar170.gif +0 -0
- data/images/red_radar180.gif +0 -0
- data/images/red_radar190.gif +0 -0
- data/images/red_radar200.gif +0 -0
- data/images/red_radar210.gif +0 -0
- data/images/red_radar220.gif +0 -0
- data/images/red_radar230.gif +0 -0
- data/images/red_radar240.gif +0 -0
- data/images/red_radar250.gif +0 -0
- data/images/red_radar260.gif +0 -0
- data/images/red_radar270.gif +0 -0
- data/images/red_radar280.gif +0 -0
- data/images/red_radar290.gif +0 -0
- data/images/red_radar300.gif +0 -0
- data/images/red_radar310.gif +0 -0
- data/images/red_radar320.gif +0 -0
- data/images/red_radar330.gif +0 -0
- data/images/red_radar340.gif +0 -0
- data/images/red_radar350.gif +0 -0
- data/images/red_turret000.gif +0 -0
- data/images/red_turret010.gif +0 -0
- data/images/red_turret020.gif +0 -0
- data/images/red_turret030.gif +0 -0
- data/images/red_turret040.gif +0 -0
- data/images/red_turret050.gif +0 -0
- data/images/red_turret060.gif +0 -0
- data/images/red_turret070.gif +0 -0
- data/images/red_turret080.gif +0 -0
- data/images/red_turret090.gif +0 -0
- data/images/red_turret100.gif +0 -0
- data/images/red_turret110.gif +0 -0
- data/images/red_turret120.gif +0 -0
- data/images/red_turret130.gif +0 -0
- data/images/red_turret140.gif +0 -0
- data/images/red_turret150.gif +0 -0
- data/images/red_turret160.gif +0 -0
- data/images/red_turret170.gif +0 -0
- data/images/red_turret180.gif +0 -0
- data/images/red_turret190.gif +0 -0
- data/images/red_turret200.gif +0 -0
- data/images/red_turret210.gif +0 -0
- data/images/red_turret220.gif +0 -0
- data/images/red_turret230.gif +0 -0
- data/images/red_turret240.gif +0 -0
- data/images/red_turret250.gif +0 -0
- data/images/red_turret260.gif +0 -0
- data/images/red_turret270.gif +0 -0
- data/images/red_turret280.gif +0 -0
- data/images/red_turret290.gif +0 -0
- data/images/red_turret300.gif +0 -0
- data/images/red_turret310.gif +0 -0
- data/images/red_turret320.gif +0 -0
- data/images/red_turret330.gif +0 -0
- data/images/red_turret340.gif +0 -0
- data/images/red_turret350.gif +0 -0
- data/images/toolbox.gif +0 -0
- data/lib/battlefield.rb +102 -0
- data/lib/bullets.rb +39 -0
- data/lib/configuration.rb +26 -0
- data/lib/explosions.rb +20 -0
- data/lib/overloads.rb +10 -0
- data/lib/robot.rb +122 -0
- data/lib/robotrunner.rb +260 -0
- data/lib/tkarena.rb +197 -0
- data/lib/toolboxes.rb +28 -0
- data/robots/BillDuck.rb +92 -0
- data/robots/BotOne.rb +39 -0
- data/robots/DuckBill.rb +384 -0
- data/robots/DuckBill04.rb +330 -0
- data/robots/DuckToEndAllDucks.rb +140 -0
- data/robots/EdgeBot.rb +203 -0
- data/robots/HuntingDuck.rb +74 -0
- data/robots/HyperactiveDuck.rb +15 -0
- data/robots/Killer.rb +58 -0
- data/robots/Kite.rb +193 -0
- data/robots/KoDuck.rb +57 -0
- data/robots/LinearShooter.rb +279 -0
- data/robots/LuckyDuck.rb +83 -0
- data/robots/MoxonoM.rb +85 -0
- data/robots/MsgBot.rb +13 -0
- data/robots/NervousDuck.rb +13 -0
- data/robots/Polisher.rb +15 -0
- data/robots/RomBot.rb +514 -0
- data/robots/RoomPainter.rb +205 -0
- data/robots/Rrrkele.rb +48 -0
- data/robots/Seeker.rb +57 -0
- data/robots/ShootingStation.rb +15 -0
- data/robots/SittingDuck.rb +18 -0
- data/robots/SniperDuck.rb +277 -0
- data/robots/WallPainter.rb +224 -0
- metadata +220 -0
data/lib/tkarena.rb
ADDED
@@ -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
|
data/lib/toolboxes.rb
ADDED
@@ -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
|
data/robots/BillDuck.rb
ADDED
@@ -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
|
data/robots/BotOne.rb
ADDED
@@ -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
|
+
|
data/robots/DuckBill.rb
ADDED
@@ -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
|