S
Stefan Walk
Hi,
here is my solution...
RubberDuckLinear is a linear shooting variant of RubberDuck and runs
significantly faster, but the extra code was neccessary to hit some of
the dodgy robots (Kite2 for example). Rubberduck-debug includes debug
code, for interested people (not a tournament entry).
Regards,
Stefan
require 'robot'
require 'ostruct'
module RubberRobot
MEMO_SIZE = 250
GUN_POWER = 0.1
ACC_WHILE_MOVING = false
class Memo # (forgetting old values) {{{
def initialize(n)
@array = []
@n = n
end
def << (arg)
@array.unshift arg
@array = @array.first(@n)
end
def [](arg)
@array[arg]
end
def to_a
@array
end
def nearest_non_nil(idx)
0.upto(51) do |offs|
next if @array[idx + ((offs % 2 == 0) ? -1 : 1)*offs].nil?
return idx + ((offs % 2 == 0) ? -1 : 1)*offs
end
nil
end
end # }}}
class Brain # {{{
include Math # This robot knows Math!
def time
@robot.time
end
def battlefield_width
@robot.battlefield_width
end
def battlefield_height
@robot.battlefield_height
end
def size
@robot.size
end
def say(string)
@robot.say(string)
end
def trim(min, val, max) # {{{
return min if val < min
return max if val > max
return val
end # }}}
def strim(val, border) # {{{
return -border if val < -border
return border if val > border
return val
end # }}}
def initialize(robot) # {{{
@memory = OpenStruct.new
@memory.rheading = Memo.new(MEMO_SIZE)
@memory.pos = Memo.new(MEMO_SIZE)
@memory.enemypos = Memo.new(MEMO_SIZE*4)
@memory.enemyrange = Memo.new(MEMO_SIZE)
@memory.enemyangle = Memo.new(MEMO_SIZE)
@memory.lastseen = nil
@robot = robot
@tlock = 0
@lasthit = 0
@looking_since = 0
@aim_mode = :linear
end # }}}
# def method_missing(name, *args) # {{{
# @robot.__send__(name, *args)
# end # }}}
def refresh # {{{
@x = @robot.x
@y = @robot.y
@turn_body = 0
@turn_turret = 0
@turn_radar = 0
@power = GUN_POWER
@accelerate = 0
@body_heading = @robot.heading
@turret_heading = @robot.gun_heading
@radar_heading = @robot.radar_heading
end # }}}
def tick(events) # {{{
refresh
save_memory
if events.has_key? 'robot_scanned'
r = events['robot_scanned'][0][0]
phi0 = @memory.rheading[0]
phi1 = @memory.rheading[1]
if (phi0 < 90 and phi1 > 270) or (phi0 > 270 and phi1 < 90)
phim = (phi0 + phi1 + 90) % 360 - 90
else
phim = (phi0 + phi1)/2
end
phi0, phi1, phim = phi0.to_rad, phi1.to_rad, phim.to_rad
dx = r*(cos(phi0) - cos(phi1)).abs/2
dy = r*(sin(phi0) - sin(phi1)).abs/2
x = @x + r * cos(phim)
y = @y - r * sin(phim)
@memory.lastseen = 0
@memory.enemypos << [x,dx,y,dy]
@memory.enemyrange << r
@memory.enemyangle << phim
@memory.enemy
else
@memory.lastseen += 1 if @memory.lastseen
@memory.enemypos << nil
@memory.enemyrange << nil
@memory.enemyangle << nil
end
if events.has_key? 'got_hit'
@lasthit = 0
else
@lasthit += 1
end
if time == 50
say "<Patrician|Away> what does your robot do, sam"
elsif time == 100
say "<bovril> it collects data about the surrounding environment, ..."
elsif time == 150
say "... then discards it and drives into walls"
end
do_radar_aiming
do_turret_aiming
do_movement
execute
end # }}}
def do_turret_aiming # {{{
if (time >= 200 and time % 100 == 0) or @schedule_calc
@schedule_calc = false
corr = check_correlation
begin
max = corr.max{|a,b| a[1] <=> b[1]}
rescue ArgumentError => e
p corr.sort
raise e
end
if max[0] > 35 and max[1] > 0.35
@aim_mode = :correlation
@corr_diff = max[0]
else
@aim_mode = :linear
end
end
case @aim_mode
when :linear
@turn_turret = 1
dist = 800
i = 0
angle = nil
x, y = nil, nil
catch :tads do
loop do
x,y = predict(dist/30.0 + 1) # TODO ???
throw :tads unless x and y
angle, distn = get_angle_and_dist(x,y)
break if (distn - dist).abs < 15 or (i += 1) > 20
dist = distn
end
@turn_turret = deg_diff(angle, @turret_heading)
end
when :correlation
=begin
``Those who cannot remember the past are condemned to repeat it.''
-- George Santayana (1863-1952)
=end
idx = @memory.enemypos.nearest_non_nil(@corr_diff - 25)
unless idx
@turn_turret = 0
@schedule_calc = true
break
end
pos = nil
angle = nil
dist = nil
i = 0
loop do
pos = @memory.enemypos[idx]
angle, dist = get_angle_and_dist(pos[0], pos[2])
idxn = @memory.enemypos.nearest_non_nil(@corr_diff - (dist/30.0).to_i)
break unless idxn
break if idxn == idx
idx = idxn
break if i >= 20
i += 1
end
x,dx,y,dy = @memory.enemypos[idx]
angle, dist = get_angle_and_dist(x, y)
@turn_turret = deg_diff(angle, @turret_heading)
else
raise 'wtf?'
end
@angle = angle
end # }}}
def do_radar_aiming # {{{
@turn_radar = 10 and return unless @memory.lastseen
@radar_turn_speed ||= 60
if @memory.lastseen == 0
@radar_turn_speed *= -0.5
else @memory.lastseen != 2
@radar_turn_speed *= -2 if @radar_turn_speed.abs < 60
end
@turn_radar = @radar_turn_speed
end # }}}
def do_movement # {{{
=begin
http://bash.org/?240849
<Patrician|Away> what does your robot do, sam
<bovril> it collects data about the surrounding environment, then discards it
and drives into walls
=end
@accelerate = 1 # Energie!
@move_dur ||= 0
@turn_dur ||= 0
@acc_dur ||= 0
if @move_dur == 0
wangle, wdist = get_revangle_and_dist_wall
if wdist < 100
@turn_dir = strim(deg_diff(wangle, @body_heading), 10)
else
@turn_dir = (rand > 0.5) ? 10 : -10
end
@turn_dur = 7 + (rand 12)
@move_dur = @turn_dur + 5 + (rand 10)
@turn_body = @turn_dir
if ACC_WHILE_MOVING
@acc_dur = rand 7
end
elsif @turn_dur == 0
@move_dur -= 1
elsif @acc_dur == 0
@move_dur -= 1
@turn_dur -= 1
@turn_body = @turn_dir
else
@acc_dur -= 1
@move_dur -= 1
@turn_dur -= 1
@accelerate = -1
@turn_body = @turn_dir
end
end # }}}
def sign(x) # {{{
return 0 if x == 0
return 1 if x > 1
return -1
end # }}}
def dist_to_center # {{{
hypot(battlefield_height/2 - @y, battlefield_width/2 - @x)
end # }}}
def angle_to_center
end
def get_revangle_and_dist_wall
return [[@x,0], [@y,270], [battlefield_width - @x, 180], [battlefield_height - @y, 90]].min.reverse
end
def approaching_center # {{{
angle = (atan2(@x, @y).to_deg + 90) % 360
diff = deg_diff(angle, @body_heading).abs
diff < 80
end # }}}
def deg_diff(ang1, ang2) # {{{
(ang1 - ang2 + 180) % 360 - 180
end # }}}
def get_angle_and_dist(x,y) # {{{
dx = x - @x
dy = y - @y
return [(atan2(dx, dy).to_deg - 90) % 360, hypot(dx, dy)]
end # }}}
def predict(ticks, maxhist = 70) # {{{
i = 0
lx = 0.0
mx = 0.0
nx = 0.0
ox = 0.0
px = 0.0
ly = 0.0
my = 0.0
ny = 0.0
oy = 0.0
py = 0.0
@memory.enemypos.to_a.each_with_index do |ev, idx|
next unless ev
break if idx > maxhist
break if i > 5
x, dx, y, dy = ev
dx *= 1 + (idx / 50.to_f) ** 2
dy *= 1 + (idx / 50.to_f) ** 2
dx = 1.0 if dx < 1.0
dy = 1.0 if dy < 1.0
t = -idx-1
i += 1
sx = dx**2
lx += t**2/sx
mx += t/sx
nx += t*x/sx
ox += x/sx
px += 1/sx
sy = dy**2
ly += t**2/sy
my += t/sy
ny += t*y/sy
oy += y/sy
py += 1/sy
end
return unless i >= 2
ax = (px*nx - mx*ox)/(px*lx - mx**2)
bx = 1/px*(mx*ax-ox)
ay = (py*ny - my*oy)/(py*ly - my**2)
by = 1/py*(my*ay-oy)
bx *= -1
by *= -1
factor = hypot(ax, ay)/8.0
if factor > 1
ax /= factor
ay /= factor
end
bx = battlefield_width - size if bx > battlefield_width - size
by = battlefield_width - size if by > battlefield_width - size
bx = size if bx < size
by = size if by < size
px = bx+ax*ticks
py = by+ay*ticks
return if px.infinite? or px.nan? or py.infinite? or py.nan?
px = [battlefield_width - size, [size, px].max].min
py = [battlefield_height - size, [size, py].max].min
[px, py]
end # }}}
def execute # {{{
@turn_turret -= @turn_body
@turn_radar -= @turn_body + @turn_turret
@turn_body = trim(-10, @turn_body, 10)
@turn_turret = trim(-30, @turn_turret, 30)
@turn_radar = trim(-60, @turn_radar, 60)
@robot.accelerate(@accelerate)
@robot.turn(@turn_body)
@robot.turn_gun(@turn_turret)
@robot.turn_radar(@turn_radar)
@robot.fire(@power)
end # }}}
def save_memory # {{{
@memory.rheading << @radar_heading
@memory.pos << [@x, @y]
end # }}}
def check_correlation # {{{
pos = @memory.enemypos.to_a[0..500]
size = @robot.size
corr_values = {}
30.upto(150) do |diff|
hits = 0
tries = 0
0.upto(pos.size - diff) do |idx|
next unless pos[idx] and pos[idx + diff]
next unless hypot(pos[idx][1],pos[idx][3]) < 100
next unless hypot(pos[idx + diff][1],pos[idx + diff][3]) < 100
dist = hypot(pos[idx][0] - pos[idx + diff][0],
pos[idx][2] - pos[idx + diff][2])
tries += 1
hits += 1 if dist < size
end
ratio = hits.to_f/tries
ratio = 0.0 unless ratio.finite?
corr_values[diff] = ratio
end
corr_values
end # }}}
end # }}}
class Robot # {{{
include ::Robot
def initialize
@my_brain = Brain.new(self)
end
def tick(events)
@my_brain.tick(events)
end
end # }}}
end
class RubberDuck < RubberRobot::Robot; end
require 'robot'
require 'ostruct'
module RubberRobot
RUBBERDEBUG = true
MEMO_SIZE = 250
GUN_POWER = 0.1
ACC_WHILE_MOVING = true
class Memo # (forgetting old values) {{{
def initialize(n)
@array = []
@n = n
end
def << (arg)
@array.unshift arg
@array = @array.first(@n)
end
def [](arg)
@array[arg]
end
def to_a
@array
end
def nearest_non_nil(idx)
0.upto(51) do |offs|
next if @array[idx + ((offs % 2 == 0) ? -1 : 1)*offs].nil?
return idx + ((offs % 2 == 0) ? -1 : 1)*offs
end
nil
end
end # }}}
class Brain # {{{
include Math # This robot knows Math!
def time
@robot.time
end
def battlefield_width
@robot.battlefield_width
end
def battlefield_height
@robot.battlefield_height
end
def size
@robot.size
end
def say(string)
@robot.say(string)
end
def trim(min, val, max) # {{{
return min if val < min
return max if val > max
return val
end # }}}
def strim(val, border) # {{{
return -border if val < -border
return border if val > border
return val
end # }}}
def initialize(robot) # {{{
@memory = OpenStruct.new
@memory.rheading = Memo.new(MEMO_SIZE)
@memory.pos = Memo.new(MEMO_SIZE)
@memory.enemypos = Memo.new(MEMO_SIZE*4)
@memory.enemyrange = Memo.new(MEMO_SIZE)
@memory.enemyangle = Memo.new(MEMO_SIZE)
@memory.lastseen = nil
@robot = robot
@tlock = 0
@lasthit = 0
@looking_since = 0
@aim_mode = :linear
end # }}}
# def method_missing(name, *args) # {{{
# @robot.__send__(name, *args)
# end # }}}
if RUBBERDEBUG # {{{ paints detection and prediction onto the canvas
def check_canvas
return false unless defined? TkCanvas
unless @canvas
ObjectSpace.each_object(TkCanvas) {|o| @canvas = o }
end
return false unless @canvas
return true
end
def detect_at(xmin, xmax, ymin, ymax)
return unless check_canvas
unless @detect_p
@detect_p = TkcOval.new(@canvas, [-5,-5], [6,6], utline => "#ff0000")
end
@detect_p.coords(xmin/2,ymin/2,xmax/2,ymax/2)
end
def predict_at(x,y)
return unless check_canvas
unless @predict_p
@predict_p = TkcOval.new(@canvas, [-5,-5], [6,6], utline => "#00ff00")
end
@predict_p.coords(x/2-3,y/2-3,x/2+3,y/2+3)
end
end # }}}
def refresh # {{{
@x = @robot.x
@y = @robot.y
@turn_body = 0
@turn_turret = 0
@turn_radar = 0
@power = GUN_POWER
@accelerate = 0
@body_heading = @robot.heading
@turret_heading = @robot.gun_heading
@radar_heading = @robot.radar_heading
end # }}}
def tick(events) # {{{
refresh
save_memory
if events.has_key? 'robot_scanned'
r = events['robot_scanned'][0][0]
phi0 = @memory.rheading[0]
phi1 = @memory.rheading[1]
if (phi0 < 90 and phi1 > 270) or (phi0 > 270 and phi1 < 90)
phim = (phi0 + phi1 + 90) % 360 - 90
else
phim = (phi0 + phi1)/2
end
phi0, phi1, phim = phi0.to_rad, phi1.to_rad, phim.to_rad
dx = r*(cos(phi0) - cos(phi1)).abs/2
dy = r*(sin(phi0) - sin(phi1)).abs/2
x = @x + r * cos(phim)
y = @y - r * sin(phim)
detect_at(x-dx,x+dx,y-dy,y+dy) if RUBBERDEBUG
@memory.lastseen = 0
@memory.enemypos << [x,dx,y,dy]
@memory.enemyrange << r
@memory.enemyangle << phim
@memory.enemy
else
@memory.lastseen += 1 if @memory.lastseen
@memory.enemypos << nil
@memory.enemyrange << nil
@memory.enemyangle << nil
end
if events.has_key? 'got_hit'
@lasthit = 0
else
@lasthit += 1
end
if time == 50
say "<Patrician|Away> what does your robot do, sam"
elsif time == 100
say "<bovril> it collects data about the surrounding environment, ..."
elsif time == 150
say "... then discards it and drives into walls"
end
do_radar_aiming
do_turret_aiming
do_movement
execute
end # }}}
def do_turret_aiming # {{{
if (time >= 200 and time % 100 == 0) or @schedule_calc
@schedule_calc = false
corr = check_correlation
begin
max = corr.max{|a,b| a[1] <=> b[1]}
rescue ArgumentError => e
p corr.sort
raise e
end
if max[0] > 35 and max[1] > 0.35
@aim_mode = :correlation
@corr_diff = max[0]
else
@aim_mode = :linear
end
end
case @aim_mode
when :linear
@turn_turret = 1
dist = 800
i = 0
angle = nil
x, y = nil, nil
catch :tads do
loop do
x,y = predict(dist/30.0 + 1) # TODO ???
throw :tads unless x and y
angle, distn = get_angle_and_dist(x,y)
break if (distn - dist).abs < 15 or (i += 1) > 20
dist = distn
end
@turn_turret = deg_diff(angle, @turret_heading)
end
if x and y
predict_at(x, y) if RUBBERDEBUG
else
predict_at(0,0) if RUBBERDEBUG
end
when :correlation
=begin
``Those who cannot remember the past are condemned to repeat it.''
-- George Santayana (1863-1952)
=end
idx = @memory.enemypos.nearest_non_nil(@corr_diff - 25)
unless idx
@turn_turret = 0
@schedule_calc = true
break
end
pos = nil
angle = nil
dist = nil
i = 0
loop do
pos = @memory.enemypos[idx]
angle, dist = get_angle_and_dist(pos[0], pos[2])
idxn = @memory.enemypos.nearest_non_nil(@corr_diff - (dist/30.0).to_i)
break unless idxn
break if idxn == idx
idx = idxn
break if i >= 20
i += 1
end
x,dx,y,dy = @memory.enemypos[idx]
angle, dist = get_angle_and_dist(x, y)
@turn_turret = deg_diff(angle, @turret_heading)
if x and y
predict_at(x, y) if RUBBERDEBUG
else
predict_at(0,0) if RUBBERDEBUG
end
else
raise 'wtf?'
end
@angle = angle
end # }}}
def do_radar_aiming # {{{
@turn_radar = 10 and return unless @memory.lastseen
@radar_turn_speed ||= 60
if @memory.lastseen == 0
@radar_turn_speed *= -0.5
else @memory.lastseen != 2
@radar_turn_speed *= -2 if @radar_turn_speed.abs < 60
end
@turn_radar = @radar_turn_speed
end # }}}
def do_movement # {{{
=begin
http://bash.org/?240849
<Patrician|Away> what does your robot do, sam
<bovril> it collects data about the surrounding environment, then discards it
and drives into walls
=end
@accelerate = 1 # Energie!
@move_dur ||= 0
@turn_dur ||= 0
@acc_dur ||= 0
if @move_dur == 0
wangle, wdist = get_revangle_and_dist_wall
if wdist < 100
@turn_dir = strim(deg_diff(wangle, @body_heading), 10)
else
@turn_dir = (rand > 0.5) ? 10 : -10
end
@turn_dur = 7 + (rand 12)
@move_dur = @turn_dur + 5 + (rand 10)
@turn_body = @turn_dir
if ACC_WHILE_MOVING
@acc_dur = rand 7
end
elsif @turn_dur == 0
@move_dur -= 1
elsif @acc_dur == 0
@move_dur -= 1
@turn_dur -= 1
@turn_body = @turn_dir
else
@acc_dur -= 1
@move_dur -= 1
@turn_dur -= 1
@accelerate = -1
@turn_body = @turn_dir
end
end # }}}
def sign(x) # {{{
return 0 if x == 0
return 1 if x > 1
return -1
end # }}}
def dist_to_center # {{{
hypot(battlefield_height/2 - @y, battlefield_width/2 - @x)
end # }}}
def angle_to_center
end
def get_revangle_and_dist_wall
return [[@x,0], [@y,270], [battlefield_width - @x, 180], [battlefield_height - @y, 90]].min.reverse
end
def approaching_center # {{{
angle = (atan2(@x, @y).to_deg + 90) % 360
diff = deg_diff(angle, @body_heading).abs
diff < 80
end # }}}
def deg_diff(ang1, ang2) # {{{
(ang1 - ang2 + 180) % 360 - 180
end # }}}
def get_angle_and_dist(x,y) # {{{
dx = x - @x
dy = y - @y
return [(atan2(dx, dy).to_deg - 90) % 360, hypot(dx, dy)]
end # }}}
def predict(ticks, maxhist = 70) # {{{
i = 0
lx = 0.0
mx = 0.0
nx = 0.0
ox = 0.0
px = 0.0
ly = 0.0
my = 0.0
ny = 0.0
oy = 0.0
py = 0.0
@memory.enemypos.to_a.each_with_index do |ev, idx|
next unless ev
break if idx > maxhist
break if i > 5
x, dx, y, dy = ev
dx *= 1 + (idx / 50.to_f) ** 2
dy *= 1 + (idx / 50.to_f) ** 2
dx = 1.0 if dx < 1.0
dy = 1.0 if dy < 1.0
t = -idx-1
i += 1
sx = dx**2
lx += t**2/sx
mx += t/sx
nx += t*x/sx
ox += x/sx
px += 1/sx
sy = dy**2
ly += t**2/sy
my += t/sy
ny += t*y/sy
oy += y/sy
py += 1/sy
end
return unless i >= 2
ax = (px*nx - mx*ox)/(px*lx - mx**2)
bx = 1/px*(mx*ax-ox)
ay = (py*ny - my*oy)/(py*ly - my**2)
by = 1/py*(my*ay-oy)
bx *= -1
by *= -1
factor = hypot(ax, ay)/8.0
if factor > 1
ax /= factor
ay /= factor
end
bx = battlefield_width - size if bx > battlefield_width - size
by = battlefield_width - size if by > battlefield_width - size
bx = size if bx < size
by = size if by < size
px = bx+ax*ticks
py = by+ay*ticks
return if px.infinite? or px.nan? or py.infinite? or py.nan?
px = [battlefield_width - size, [size, px].max].min
py = [battlefield_height - size, [size, py].max].min
[px, py]
end # }}}
def execute # {{{
@turn_turret -= @turn_body
@turn_radar -= @turn_body + @turn_turret
@turn_body = trim(-10, @turn_body, 10)
@turn_turret = trim(-30, @turn_turret, 30)
@turn_radar = trim(-60, @turn_radar, 60)
@robot.accelerate(@accelerate)
@robot.turn(@turn_body)
@robot.turn_gun(@turn_turret)
@robot.turn_radar(@turn_radar)
@robot.fire(@power)
end # }}}
def save_memory # {{{
@memory.rheading << @radar_heading
@memory.pos << [@x, @y]
end # }}}
def check_correlation # {{{
pos = @memory.enemypos.to_a[0..500]
size = @robot.size
corr_values = {}
30.upto(150) do |diff|
hits = 0
tries = 0
0.upto(pos.size - diff) do |idx|
next unless pos[idx] and pos[idx + diff]
next unless hypot(pos[idx][1],pos[idx][3]) < 100
next unless hypot(pos[idx + diff][1],pos[idx + diff][3]) < 100
dist = hypot(pos[idx][0] - pos[idx + diff][0],
pos[idx][2] - pos[idx + diff][2])
tries += 1
hits += 1 if dist < size
end
ratio = hits.to_f/tries
ratio = 0.0 unless ratio.finite?
corr_values[diff] = ratio
end
corr_values
end # }}}
end # }}}
class Robot # {{{
include ::Robot
def initialize
@my_brain = Brain.new(self)
end
def tick(events)
@my_brain.tick(events)
end
end # }}}
end
class RubberDuck < RubberRobot::Robot; end
require 'robot'
require 'ostruct'
#same as
module RubberRobotLinear
MEMO_SIZE = 250
GUN_POWER = 0.1
ACC_WHILE_MOVING = true
class Memo # (forgetting old values) {{{
def initialize(n)
@array = []
@n = n
end
def << (arg)
@array.unshift arg
@array = @array.first(@n)
end
def [](arg)
@array[arg]
end
def to_a
@array
end
def nearest_non_nil(idx)
0.upto(51) do |offs|
next if @array[idx + ((offs % 2 == 0) ? -1 : 1)*offs].nil?
return idx + ((offs % 2 == 0) ? -1 : 1)*offs
end
nil
end
end # }}}
class Brain # {{{
include Math # This robot knows Math!
def time
@robot.time
end
def battlefield_width
@robot.battlefield_width
end
def battlefield_height
@robot.battlefield_height
end
def size
@robot.size
end
def say(string)
@robot.say(string)
end
def trim(min, val, max) # {{{
return min if val < min
return max if val > max
return val
end # }}}
def strim(val, border) # {{{
return -border if val < -border
return border if val > border
return val
end # }}}
def initialize(robot) # {{{
@memory = OpenStruct.new
@memory.rheading = Memo.new(MEMO_SIZE)
@memory.pos = Memo.new(MEMO_SIZE)
@memory.enemypos = Memo.new(MEMO_SIZE*4)
@memory.enemyrange = Memo.new(MEMO_SIZE)
@memory.enemyangle = Memo.new(MEMO_SIZE)
@memory.lastseen = nil
@robot = robot
@tlock = 0
@lasthit = 0
@looking_since = 0
@aim_mode = :linear
end # }}}
# def method_missing(name, *args) # {{{
# @robot.__send__(name, *args)
# end # }}}
def refresh # {{{
@x = @robot.x
@y = @robot.y
@turn_body = 0
@turn_turret = 0
@turn_radar = 0
@power = GUN_POWER
@accelerate = 0
@body_heading = @robot.heading
@turret_heading = @robot.gun_heading
@radar_heading = @robot.radar_heading
end # }}}
def tick(events) # {{{
refresh
save_memory
if events.has_key? 'robot_scanned'
r = events['robot_scanned'][0][0]
phi0 = @memory.rheading[0]
phi1 = @memory.rheading[1]
if (phi0 < 90 and phi1 > 270) or (phi0 > 270 and phi1 < 90)
phim = (phi0 + phi1 + 90) % 360 - 90
else
phim = (phi0 + phi1)/2
end
phi0, phi1, phim = phi0.to_rad, phi1.to_rad, phim.to_rad
dx = r*(cos(phi0) - cos(phi1)).abs/2
dy = r*(sin(phi0) - sin(phi1)).abs/2
x = @x + r * cos(phim)
y = @y - r * sin(phim)
@memory.lastseen = 0
@memory.enemypos << [x,dx,y,dy]
@memory.enemyrange << r
@memory.enemyangle << phim
@memory.enemy
else
@memory.lastseen += 1 if @memory.lastseen
@memory.enemypos << nil
@memory.enemyrange << nil
@memory.enemyangle << nil
end
if events.has_key? 'got_hit'
@lasthit = 0
else
@lasthit += 1
end
if time == 50
say "<Patrician|Away> what does your robot do, sam"
elsif time == 100
say "<bovril> it collects data about the surrounding environment, ..."
elsif time == 150
say "... then discards it and drives into walls"
end
do_radar_aiming
do_turret_aiming
do_movement
execute
end # }}}
def do_turret_aiming # {{{
@turn_turret = 1
dist = 800
i = 0
angle = nil
x, y = nil, nil
catch :tads do
loop do
x,y = predict(dist/30.0 + 1) # TODO ???
throw :tads unless x and y
angle, distn = get_angle_and_dist(x,y)
break if (distn - dist).abs < 15 or (i += 1) > 20
dist = distn
end
@turn_turret = deg_diff(angle, @turret_heading)
end
@angle = angle
end # }}}
def do_radar_aiming # {{{
@turn_radar = 10 and return unless @memory.lastseen
@radar_turn_speed ||= 60
if @memory.lastseen == 0
@radar_turn_speed *= -0.5
else @memory.lastseen != 2
@radar_turn_speed *= -2 if @radar_turn_speed.abs < 60
end
@turn_radar = @radar_turn_speed
end # }}}
def do_movement # {{{
=begin
http://bash.org/?240849
<Patrician|Away> what does your robot do, sam
<bovril> it collects data about the surrounding environment, then discards it
and drives into walls
=end
@accelerate = 1 # Energie!
@move_dur ||= 0
@turn_dur ||= 0
@acc_dur ||= 0
if @move_dur == 0
wangle, wdist = get_revangle_and_dist_wall
if wdist < 100
@turn_dir = strim(deg_diff(wangle, @body_heading), 10)
else
@turn_dir = (rand > 0.5) ? 10 : -10
end
@turn_dur = 7 + (rand 12)
@move_dur = @turn_dur + 5 + (rand 10)
@turn_body = @turn_dir
if ACC_WHILE_MOVING
@acc_dur = rand 7
end
elsif @turn_dur == 0
@move_dur -= 1
elsif @acc_dur == 0
@move_dur -= 1
@turn_dur -= 1
@turn_body = @turn_dir
else
@acc_dur -= 1
@move_dur -= 1
@turn_dur -= 1
@accelerate = -1
@turn_body = @turn_dir
end
end # }}}
def sign(x) # {{{
return 0 if x == 0
return 1 if x > 1
return -1
end # }}}
def dist_to_center # {{{
hypot(battlefield_height/2 - @y, battlefield_width/2 - @x)
end # }}}
def angle_to_center
end
def get_revangle_and_dist_wall
return [[@x,0], [@y,270], [battlefield_width - @x, 180], [battlefield_height - @y, 90]].min.reverse
end
def approaching_center # {{{
angle = (atan2(@x, @y).to_deg + 90) % 360
diff = deg_diff(angle, @body_heading).abs
diff < 80
end # }}}
def deg_diff(ang1, ang2) # {{{
(ang1 - ang2 + 180) % 360 - 180
end # }}}
def get_angle_and_dist(x,y) # {{{
dx = x - @x
dy = y - @y
return [(atan2(dx, dy).to_deg - 90) % 360, hypot(dx, dy)]
end # }}}
def predict(ticks, maxhist = 70) # {{{
i = 0
lx = 0.0
mx = 0.0
nx = 0.0
ox = 0.0
px = 0.0
ly = 0.0
my = 0.0
ny = 0.0
oy = 0.0
py = 0.0
@memory.enemypos.to_a.each_with_index do |ev, idx|
next unless ev
break if idx > maxhist
break if i > 5
x, dx, y, dy = ev
dx *= 1 + (idx / 50.to_f) ** 2
dy *= 1 + (idx / 50.to_f) ** 2
dx = 1.0 if dx < 1.0
dy = 1.0 if dy < 1.0
t = -idx-1
i += 1
sx = dx**2
lx += t**2/sx
mx += t/sx
nx += t*x/sx
ox += x/sx
px += 1/sx
sy = dy**2
ly += t**2/sy
my += t/sy
ny += t*y/sy
oy += y/sy
py += 1/sy
end
return unless i >= 2
ax = (px*nx - mx*ox)/(px*lx - mx**2)
bx = 1/px*(mx*ax-ox)
ay = (py*ny - my*oy)/(py*ly - my**2)
by = 1/py*(my*ay-oy)
bx *= -1
by *= -1
factor = hypot(ax, ay)/8.0
if factor > 1
ax /= factor
ay /= factor
end
bx = battlefield_width - size if bx > battlefield_width - size
by = battlefield_width - size if by > battlefield_width - size
bx = size if bx < size
by = size if by < size
px = bx+ax*ticks
py = by+ay*ticks
return if px.infinite? or px.nan? or py.infinite? or py.nan?
px = [battlefield_width - size, [size, px].max].min
py = [battlefield_height - size, [size, py].max].min
[px, py]
end # }}}
def execute # {{{
@turn_turret -= @turn_body
@turn_radar -= @turn_body + @turn_turret
@turn_body = trim(-10, @turn_body, 10)
@turn_turret = trim(-30, @turn_turret, 30)
@turn_radar = trim(-60, @turn_radar, 60)
@robot.accelerate(@accelerate)
@robot.turn(@turn_body)
@robot.turn_gun(@turn_turret)
@robot.turn_radar(@turn_radar)
@robot.fire(@power)
end # }}}
def save_memory # {{{
@memory.rheading << @radar_heading
@memory.pos << [@x, @y]
end # }}}
end # }}}
class Robot # {{{
include ::Robot
def initialize
@my_brain = Brain.new(self)
end
def tick(events)
@my_brain.tick(events)
end
end # }}}
end
class RubberDuckLinear < RubberRobotLinear::Robot; end
here is my solution...
RubberDuckLinear is a linear shooting variant of RubberDuck and runs
significantly faster, but the extra code was neccessary to hit some of
the dodgy robots (Kite2 for example). Rubberduck-debug includes debug
code, for interested people (not a tournament entry).
Regards,
Stefan
require 'robot'
require 'ostruct'
module RubberRobot
MEMO_SIZE = 250
GUN_POWER = 0.1
ACC_WHILE_MOVING = false
class Memo # (forgetting old values) {{{
def initialize(n)
@array = []
@n = n
end
def << (arg)
@array.unshift arg
@array = @array.first(@n)
end
def [](arg)
@array[arg]
end
def to_a
@array
end
def nearest_non_nil(idx)
0.upto(51) do |offs|
next if @array[idx + ((offs % 2 == 0) ? -1 : 1)*offs].nil?
return idx + ((offs % 2 == 0) ? -1 : 1)*offs
end
nil
end
end # }}}
class Brain # {{{
include Math # This robot knows Math!
def time
@robot.time
end
def battlefield_width
@robot.battlefield_width
end
def battlefield_height
@robot.battlefield_height
end
def size
@robot.size
end
def say(string)
@robot.say(string)
end
def trim(min, val, max) # {{{
return min if val < min
return max if val > max
return val
end # }}}
def strim(val, border) # {{{
return -border if val < -border
return border if val > border
return val
end # }}}
def initialize(robot) # {{{
@memory = OpenStruct.new
@memory.rheading = Memo.new(MEMO_SIZE)
@memory.pos = Memo.new(MEMO_SIZE)
@memory.enemypos = Memo.new(MEMO_SIZE*4)
@memory.enemyrange = Memo.new(MEMO_SIZE)
@memory.enemyangle = Memo.new(MEMO_SIZE)
@memory.lastseen = nil
@robot = robot
@tlock = 0
@lasthit = 0
@looking_since = 0
@aim_mode = :linear
end # }}}
# def method_missing(name, *args) # {{{
# @robot.__send__(name, *args)
# end # }}}
def refresh # {{{
@x = @robot.x
@y = @robot.y
@turn_body = 0
@turn_turret = 0
@turn_radar = 0
@power = GUN_POWER
@accelerate = 0
@body_heading = @robot.heading
@turret_heading = @robot.gun_heading
@radar_heading = @robot.radar_heading
end # }}}
def tick(events) # {{{
refresh
save_memory
if events.has_key? 'robot_scanned'
r = events['robot_scanned'][0][0]
phi0 = @memory.rheading[0]
phi1 = @memory.rheading[1]
if (phi0 < 90 and phi1 > 270) or (phi0 > 270 and phi1 < 90)
phim = (phi0 + phi1 + 90) % 360 - 90
else
phim = (phi0 + phi1)/2
end
phi0, phi1, phim = phi0.to_rad, phi1.to_rad, phim.to_rad
dx = r*(cos(phi0) - cos(phi1)).abs/2
dy = r*(sin(phi0) - sin(phi1)).abs/2
x = @x + r * cos(phim)
y = @y - r * sin(phim)
@memory.lastseen = 0
@memory.enemypos << [x,dx,y,dy]
@memory.enemyrange << r
@memory.enemyangle << phim
@memory.enemy
else
@memory.lastseen += 1 if @memory.lastseen
@memory.enemypos << nil
@memory.enemyrange << nil
@memory.enemyangle << nil
end
if events.has_key? 'got_hit'
@lasthit = 0
else
@lasthit += 1
end
if time == 50
say "<Patrician|Away> what does your robot do, sam"
elsif time == 100
say "<bovril> it collects data about the surrounding environment, ..."
elsif time == 150
say "... then discards it and drives into walls"
end
do_radar_aiming
do_turret_aiming
do_movement
execute
end # }}}
def do_turret_aiming # {{{
if (time >= 200 and time % 100 == 0) or @schedule_calc
@schedule_calc = false
corr = check_correlation
begin
max = corr.max{|a,b| a[1] <=> b[1]}
rescue ArgumentError => e
p corr.sort
raise e
end
if max[0] > 35 and max[1] > 0.35
@aim_mode = :correlation
@corr_diff = max[0]
else
@aim_mode = :linear
end
end
case @aim_mode
when :linear
@turn_turret = 1
dist = 800
i = 0
angle = nil
x, y = nil, nil
catch :tads do
loop do
x,y = predict(dist/30.0 + 1) # TODO ???
throw :tads unless x and y
angle, distn = get_angle_and_dist(x,y)
break if (distn - dist).abs < 15 or (i += 1) > 20
dist = distn
end
@turn_turret = deg_diff(angle, @turret_heading)
end
when :correlation
=begin
``Those who cannot remember the past are condemned to repeat it.''
-- George Santayana (1863-1952)
=end
idx = @memory.enemypos.nearest_non_nil(@corr_diff - 25)
unless idx
@turn_turret = 0
@schedule_calc = true
break
end
pos = nil
angle = nil
dist = nil
i = 0
loop do
pos = @memory.enemypos[idx]
angle, dist = get_angle_and_dist(pos[0], pos[2])
idxn = @memory.enemypos.nearest_non_nil(@corr_diff - (dist/30.0).to_i)
break unless idxn
break if idxn == idx
idx = idxn
break if i >= 20
i += 1
end
x,dx,y,dy = @memory.enemypos[idx]
angle, dist = get_angle_and_dist(x, y)
@turn_turret = deg_diff(angle, @turret_heading)
else
raise 'wtf?'
end
@angle = angle
end # }}}
def do_radar_aiming # {{{
@turn_radar = 10 and return unless @memory.lastseen
@radar_turn_speed ||= 60
if @memory.lastseen == 0
@radar_turn_speed *= -0.5
else @memory.lastseen != 2
@radar_turn_speed *= -2 if @radar_turn_speed.abs < 60
end
@turn_radar = @radar_turn_speed
end # }}}
def do_movement # {{{
=begin
http://bash.org/?240849
<Patrician|Away> what does your robot do, sam
<bovril> it collects data about the surrounding environment, then discards it
and drives into walls
=end
@accelerate = 1 # Energie!
@move_dur ||= 0
@turn_dur ||= 0
@acc_dur ||= 0
if @move_dur == 0
wangle, wdist = get_revangle_and_dist_wall
if wdist < 100
@turn_dir = strim(deg_diff(wangle, @body_heading), 10)
else
@turn_dir = (rand > 0.5) ? 10 : -10
end
@turn_dur = 7 + (rand 12)
@move_dur = @turn_dur + 5 + (rand 10)
@turn_body = @turn_dir
if ACC_WHILE_MOVING
@acc_dur = rand 7
end
elsif @turn_dur == 0
@move_dur -= 1
elsif @acc_dur == 0
@move_dur -= 1
@turn_dur -= 1
@turn_body = @turn_dir
else
@acc_dur -= 1
@move_dur -= 1
@turn_dur -= 1
@accelerate = -1
@turn_body = @turn_dir
end
end # }}}
def sign(x) # {{{
return 0 if x == 0
return 1 if x > 1
return -1
end # }}}
def dist_to_center # {{{
hypot(battlefield_height/2 - @y, battlefield_width/2 - @x)
end # }}}
def angle_to_center
end
def get_revangle_and_dist_wall
return [[@x,0], [@y,270], [battlefield_width - @x, 180], [battlefield_height - @y, 90]].min.reverse
end
def approaching_center # {{{
angle = (atan2(@x, @y).to_deg + 90) % 360
diff = deg_diff(angle, @body_heading).abs
diff < 80
end # }}}
def deg_diff(ang1, ang2) # {{{
(ang1 - ang2 + 180) % 360 - 180
end # }}}
def get_angle_and_dist(x,y) # {{{
dx = x - @x
dy = y - @y
return [(atan2(dx, dy).to_deg - 90) % 360, hypot(dx, dy)]
end # }}}
def predict(ticks, maxhist = 70) # {{{
i = 0
lx = 0.0
mx = 0.0
nx = 0.0
ox = 0.0
px = 0.0
ly = 0.0
my = 0.0
ny = 0.0
oy = 0.0
py = 0.0
@memory.enemypos.to_a.each_with_index do |ev, idx|
next unless ev
break if idx > maxhist
break if i > 5
x, dx, y, dy = ev
dx *= 1 + (idx / 50.to_f) ** 2
dy *= 1 + (idx / 50.to_f) ** 2
dx = 1.0 if dx < 1.0
dy = 1.0 if dy < 1.0
t = -idx-1
i += 1
sx = dx**2
lx += t**2/sx
mx += t/sx
nx += t*x/sx
ox += x/sx
px += 1/sx
sy = dy**2
ly += t**2/sy
my += t/sy
ny += t*y/sy
oy += y/sy
py += 1/sy
end
return unless i >= 2
ax = (px*nx - mx*ox)/(px*lx - mx**2)
bx = 1/px*(mx*ax-ox)
ay = (py*ny - my*oy)/(py*ly - my**2)
by = 1/py*(my*ay-oy)
bx *= -1
by *= -1
factor = hypot(ax, ay)/8.0
if factor > 1
ax /= factor
ay /= factor
end
bx = battlefield_width - size if bx > battlefield_width - size
by = battlefield_width - size if by > battlefield_width - size
bx = size if bx < size
by = size if by < size
px = bx+ax*ticks
py = by+ay*ticks
return if px.infinite? or px.nan? or py.infinite? or py.nan?
px = [battlefield_width - size, [size, px].max].min
py = [battlefield_height - size, [size, py].max].min
[px, py]
end # }}}
def execute # {{{
@turn_turret -= @turn_body
@turn_radar -= @turn_body + @turn_turret
@turn_body = trim(-10, @turn_body, 10)
@turn_turret = trim(-30, @turn_turret, 30)
@turn_radar = trim(-60, @turn_radar, 60)
@robot.accelerate(@accelerate)
@robot.turn(@turn_body)
@robot.turn_gun(@turn_turret)
@robot.turn_radar(@turn_radar)
@robot.fire(@power)
end # }}}
def save_memory # {{{
@memory.rheading << @radar_heading
@memory.pos << [@x, @y]
end # }}}
def check_correlation # {{{
pos = @memory.enemypos.to_a[0..500]
size = @robot.size
corr_values = {}
30.upto(150) do |diff|
hits = 0
tries = 0
0.upto(pos.size - diff) do |idx|
next unless pos[idx] and pos[idx + diff]
next unless hypot(pos[idx][1],pos[idx][3]) < 100
next unless hypot(pos[idx + diff][1],pos[idx + diff][3]) < 100
dist = hypot(pos[idx][0] - pos[idx + diff][0],
pos[idx][2] - pos[idx + diff][2])
tries += 1
hits += 1 if dist < size
end
ratio = hits.to_f/tries
ratio = 0.0 unless ratio.finite?
corr_values[diff] = ratio
end
corr_values
end # }}}
end # }}}
class Robot # {{{
include ::Robot
def initialize
@my_brain = Brain.new(self)
end
def tick(events)
@my_brain.tick(events)
end
end # }}}
end
class RubberDuck < RubberRobot::Robot; end
require 'robot'
require 'ostruct'
module RubberRobot
RUBBERDEBUG = true
MEMO_SIZE = 250
GUN_POWER = 0.1
ACC_WHILE_MOVING = true
class Memo # (forgetting old values) {{{
def initialize(n)
@array = []
@n = n
end
def << (arg)
@array.unshift arg
@array = @array.first(@n)
end
def [](arg)
@array[arg]
end
def to_a
@array
end
def nearest_non_nil(idx)
0.upto(51) do |offs|
next if @array[idx + ((offs % 2 == 0) ? -1 : 1)*offs].nil?
return idx + ((offs % 2 == 0) ? -1 : 1)*offs
end
nil
end
end # }}}
class Brain # {{{
include Math # This robot knows Math!
def time
@robot.time
end
def battlefield_width
@robot.battlefield_width
end
def battlefield_height
@robot.battlefield_height
end
def size
@robot.size
end
def say(string)
@robot.say(string)
end
def trim(min, val, max) # {{{
return min if val < min
return max if val > max
return val
end # }}}
def strim(val, border) # {{{
return -border if val < -border
return border if val > border
return val
end # }}}
def initialize(robot) # {{{
@memory = OpenStruct.new
@memory.rheading = Memo.new(MEMO_SIZE)
@memory.pos = Memo.new(MEMO_SIZE)
@memory.enemypos = Memo.new(MEMO_SIZE*4)
@memory.enemyrange = Memo.new(MEMO_SIZE)
@memory.enemyangle = Memo.new(MEMO_SIZE)
@memory.lastseen = nil
@robot = robot
@tlock = 0
@lasthit = 0
@looking_since = 0
@aim_mode = :linear
end # }}}
# def method_missing(name, *args) # {{{
# @robot.__send__(name, *args)
# end # }}}
if RUBBERDEBUG # {{{ paints detection and prediction onto the canvas
def check_canvas
return false unless defined? TkCanvas
unless @canvas
ObjectSpace.each_object(TkCanvas) {|o| @canvas = o }
end
return false unless @canvas
return true
end
def detect_at(xmin, xmax, ymin, ymax)
return unless check_canvas
unless @detect_p
@detect_p = TkcOval.new(@canvas, [-5,-5], [6,6], utline => "#ff0000")
end
@detect_p.coords(xmin/2,ymin/2,xmax/2,ymax/2)
end
def predict_at(x,y)
return unless check_canvas
unless @predict_p
@predict_p = TkcOval.new(@canvas, [-5,-5], [6,6], utline => "#00ff00")
end
@predict_p.coords(x/2-3,y/2-3,x/2+3,y/2+3)
end
end # }}}
def refresh # {{{
@x = @robot.x
@y = @robot.y
@turn_body = 0
@turn_turret = 0
@turn_radar = 0
@power = GUN_POWER
@accelerate = 0
@body_heading = @robot.heading
@turret_heading = @robot.gun_heading
@radar_heading = @robot.radar_heading
end # }}}
def tick(events) # {{{
refresh
save_memory
if events.has_key? 'robot_scanned'
r = events['robot_scanned'][0][0]
phi0 = @memory.rheading[0]
phi1 = @memory.rheading[1]
if (phi0 < 90 and phi1 > 270) or (phi0 > 270 and phi1 < 90)
phim = (phi0 + phi1 + 90) % 360 - 90
else
phim = (phi0 + phi1)/2
end
phi0, phi1, phim = phi0.to_rad, phi1.to_rad, phim.to_rad
dx = r*(cos(phi0) - cos(phi1)).abs/2
dy = r*(sin(phi0) - sin(phi1)).abs/2
x = @x + r * cos(phim)
y = @y - r * sin(phim)
detect_at(x-dx,x+dx,y-dy,y+dy) if RUBBERDEBUG
@memory.lastseen = 0
@memory.enemypos << [x,dx,y,dy]
@memory.enemyrange << r
@memory.enemyangle << phim
@memory.enemy
else
@memory.lastseen += 1 if @memory.lastseen
@memory.enemypos << nil
@memory.enemyrange << nil
@memory.enemyangle << nil
end
if events.has_key? 'got_hit'
@lasthit = 0
else
@lasthit += 1
end
if time == 50
say "<Patrician|Away> what does your robot do, sam"
elsif time == 100
say "<bovril> it collects data about the surrounding environment, ..."
elsif time == 150
say "... then discards it and drives into walls"
end
do_radar_aiming
do_turret_aiming
do_movement
execute
end # }}}
def do_turret_aiming # {{{
if (time >= 200 and time % 100 == 0) or @schedule_calc
@schedule_calc = false
corr = check_correlation
begin
max = corr.max{|a,b| a[1] <=> b[1]}
rescue ArgumentError => e
p corr.sort
raise e
end
if max[0] > 35 and max[1] > 0.35
@aim_mode = :correlation
@corr_diff = max[0]
else
@aim_mode = :linear
end
end
case @aim_mode
when :linear
@turn_turret = 1
dist = 800
i = 0
angle = nil
x, y = nil, nil
catch :tads do
loop do
x,y = predict(dist/30.0 + 1) # TODO ???
throw :tads unless x and y
angle, distn = get_angle_and_dist(x,y)
break if (distn - dist).abs < 15 or (i += 1) > 20
dist = distn
end
@turn_turret = deg_diff(angle, @turret_heading)
end
if x and y
predict_at(x, y) if RUBBERDEBUG
else
predict_at(0,0) if RUBBERDEBUG
end
when :correlation
=begin
``Those who cannot remember the past are condemned to repeat it.''
-- George Santayana (1863-1952)
=end
idx = @memory.enemypos.nearest_non_nil(@corr_diff - 25)
unless idx
@turn_turret = 0
@schedule_calc = true
break
end
pos = nil
angle = nil
dist = nil
i = 0
loop do
pos = @memory.enemypos[idx]
angle, dist = get_angle_and_dist(pos[0], pos[2])
idxn = @memory.enemypos.nearest_non_nil(@corr_diff - (dist/30.0).to_i)
break unless idxn
break if idxn == idx
idx = idxn
break if i >= 20
i += 1
end
x,dx,y,dy = @memory.enemypos[idx]
angle, dist = get_angle_and_dist(x, y)
@turn_turret = deg_diff(angle, @turret_heading)
if x and y
predict_at(x, y) if RUBBERDEBUG
else
predict_at(0,0) if RUBBERDEBUG
end
else
raise 'wtf?'
end
@angle = angle
end # }}}
def do_radar_aiming # {{{
@turn_radar = 10 and return unless @memory.lastseen
@radar_turn_speed ||= 60
if @memory.lastseen == 0
@radar_turn_speed *= -0.5
else @memory.lastseen != 2
@radar_turn_speed *= -2 if @radar_turn_speed.abs < 60
end
@turn_radar = @radar_turn_speed
end # }}}
def do_movement # {{{
=begin
http://bash.org/?240849
<Patrician|Away> what does your robot do, sam
<bovril> it collects data about the surrounding environment, then discards it
and drives into walls
=end
@accelerate = 1 # Energie!
@move_dur ||= 0
@turn_dur ||= 0
@acc_dur ||= 0
if @move_dur == 0
wangle, wdist = get_revangle_and_dist_wall
if wdist < 100
@turn_dir = strim(deg_diff(wangle, @body_heading), 10)
else
@turn_dir = (rand > 0.5) ? 10 : -10
end
@turn_dur = 7 + (rand 12)
@move_dur = @turn_dur + 5 + (rand 10)
@turn_body = @turn_dir
if ACC_WHILE_MOVING
@acc_dur = rand 7
end
elsif @turn_dur == 0
@move_dur -= 1
elsif @acc_dur == 0
@move_dur -= 1
@turn_dur -= 1
@turn_body = @turn_dir
else
@acc_dur -= 1
@move_dur -= 1
@turn_dur -= 1
@accelerate = -1
@turn_body = @turn_dir
end
end # }}}
def sign(x) # {{{
return 0 if x == 0
return 1 if x > 1
return -1
end # }}}
def dist_to_center # {{{
hypot(battlefield_height/2 - @y, battlefield_width/2 - @x)
end # }}}
def angle_to_center
end
def get_revangle_and_dist_wall
return [[@x,0], [@y,270], [battlefield_width - @x, 180], [battlefield_height - @y, 90]].min.reverse
end
def approaching_center # {{{
angle = (atan2(@x, @y).to_deg + 90) % 360
diff = deg_diff(angle, @body_heading).abs
diff < 80
end # }}}
def deg_diff(ang1, ang2) # {{{
(ang1 - ang2 + 180) % 360 - 180
end # }}}
def get_angle_and_dist(x,y) # {{{
dx = x - @x
dy = y - @y
return [(atan2(dx, dy).to_deg - 90) % 360, hypot(dx, dy)]
end # }}}
def predict(ticks, maxhist = 70) # {{{
i = 0
lx = 0.0
mx = 0.0
nx = 0.0
ox = 0.0
px = 0.0
ly = 0.0
my = 0.0
ny = 0.0
oy = 0.0
py = 0.0
@memory.enemypos.to_a.each_with_index do |ev, idx|
next unless ev
break if idx > maxhist
break if i > 5
x, dx, y, dy = ev
dx *= 1 + (idx / 50.to_f) ** 2
dy *= 1 + (idx / 50.to_f) ** 2
dx = 1.0 if dx < 1.0
dy = 1.0 if dy < 1.0
t = -idx-1
i += 1
sx = dx**2
lx += t**2/sx
mx += t/sx
nx += t*x/sx
ox += x/sx
px += 1/sx
sy = dy**2
ly += t**2/sy
my += t/sy
ny += t*y/sy
oy += y/sy
py += 1/sy
end
return unless i >= 2
ax = (px*nx - mx*ox)/(px*lx - mx**2)
bx = 1/px*(mx*ax-ox)
ay = (py*ny - my*oy)/(py*ly - my**2)
by = 1/py*(my*ay-oy)
bx *= -1
by *= -1
factor = hypot(ax, ay)/8.0
if factor > 1
ax /= factor
ay /= factor
end
bx = battlefield_width - size if bx > battlefield_width - size
by = battlefield_width - size if by > battlefield_width - size
bx = size if bx < size
by = size if by < size
px = bx+ax*ticks
py = by+ay*ticks
return if px.infinite? or px.nan? or py.infinite? or py.nan?
px = [battlefield_width - size, [size, px].max].min
py = [battlefield_height - size, [size, py].max].min
[px, py]
end # }}}
def execute # {{{
@turn_turret -= @turn_body
@turn_radar -= @turn_body + @turn_turret
@turn_body = trim(-10, @turn_body, 10)
@turn_turret = trim(-30, @turn_turret, 30)
@turn_radar = trim(-60, @turn_radar, 60)
@robot.accelerate(@accelerate)
@robot.turn(@turn_body)
@robot.turn_gun(@turn_turret)
@robot.turn_radar(@turn_radar)
@robot.fire(@power)
end # }}}
def save_memory # {{{
@memory.rheading << @radar_heading
@memory.pos << [@x, @y]
end # }}}
def check_correlation # {{{
pos = @memory.enemypos.to_a[0..500]
size = @robot.size
corr_values = {}
30.upto(150) do |diff|
hits = 0
tries = 0
0.upto(pos.size - diff) do |idx|
next unless pos[idx] and pos[idx + diff]
next unless hypot(pos[idx][1],pos[idx][3]) < 100
next unless hypot(pos[idx + diff][1],pos[idx + diff][3]) < 100
dist = hypot(pos[idx][0] - pos[idx + diff][0],
pos[idx][2] - pos[idx + diff][2])
tries += 1
hits += 1 if dist < size
end
ratio = hits.to_f/tries
ratio = 0.0 unless ratio.finite?
corr_values[diff] = ratio
end
corr_values
end # }}}
end # }}}
class Robot # {{{
include ::Robot
def initialize
@my_brain = Brain.new(self)
end
def tick(events)
@my_brain.tick(events)
end
end # }}}
end
class RubberDuck < RubberRobot::Robot; end
require 'robot'
require 'ostruct'
#same as
module RubberRobotLinear
MEMO_SIZE = 250
GUN_POWER = 0.1
ACC_WHILE_MOVING = true
class Memo # (forgetting old values) {{{
def initialize(n)
@array = []
@n = n
end
def << (arg)
@array.unshift arg
@array = @array.first(@n)
end
def [](arg)
@array[arg]
end
def to_a
@array
end
def nearest_non_nil(idx)
0.upto(51) do |offs|
next if @array[idx + ((offs % 2 == 0) ? -1 : 1)*offs].nil?
return idx + ((offs % 2 == 0) ? -1 : 1)*offs
end
nil
end
end # }}}
class Brain # {{{
include Math # This robot knows Math!
def time
@robot.time
end
def battlefield_width
@robot.battlefield_width
end
def battlefield_height
@robot.battlefield_height
end
def size
@robot.size
end
def say(string)
@robot.say(string)
end
def trim(min, val, max) # {{{
return min if val < min
return max if val > max
return val
end # }}}
def strim(val, border) # {{{
return -border if val < -border
return border if val > border
return val
end # }}}
def initialize(robot) # {{{
@memory = OpenStruct.new
@memory.rheading = Memo.new(MEMO_SIZE)
@memory.pos = Memo.new(MEMO_SIZE)
@memory.enemypos = Memo.new(MEMO_SIZE*4)
@memory.enemyrange = Memo.new(MEMO_SIZE)
@memory.enemyangle = Memo.new(MEMO_SIZE)
@memory.lastseen = nil
@robot = robot
@tlock = 0
@lasthit = 0
@looking_since = 0
@aim_mode = :linear
end # }}}
# def method_missing(name, *args) # {{{
# @robot.__send__(name, *args)
# end # }}}
def refresh # {{{
@x = @robot.x
@y = @robot.y
@turn_body = 0
@turn_turret = 0
@turn_radar = 0
@power = GUN_POWER
@accelerate = 0
@body_heading = @robot.heading
@turret_heading = @robot.gun_heading
@radar_heading = @robot.radar_heading
end # }}}
def tick(events) # {{{
refresh
save_memory
if events.has_key? 'robot_scanned'
r = events['robot_scanned'][0][0]
phi0 = @memory.rheading[0]
phi1 = @memory.rheading[1]
if (phi0 < 90 and phi1 > 270) or (phi0 > 270 and phi1 < 90)
phim = (phi0 + phi1 + 90) % 360 - 90
else
phim = (phi0 + phi1)/2
end
phi0, phi1, phim = phi0.to_rad, phi1.to_rad, phim.to_rad
dx = r*(cos(phi0) - cos(phi1)).abs/2
dy = r*(sin(phi0) - sin(phi1)).abs/2
x = @x + r * cos(phim)
y = @y - r * sin(phim)
@memory.lastseen = 0
@memory.enemypos << [x,dx,y,dy]
@memory.enemyrange << r
@memory.enemyangle << phim
@memory.enemy
else
@memory.lastseen += 1 if @memory.lastseen
@memory.enemypos << nil
@memory.enemyrange << nil
@memory.enemyangle << nil
end
if events.has_key? 'got_hit'
@lasthit = 0
else
@lasthit += 1
end
if time == 50
say "<Patrician|Away> what does your robot do, sam"
elsif time == 100
say "<bovril> it collects data about the surrounding environment, ..."
elsif time == 150
say "... then discards it and drives into walls"
end
do_radar_aiming
do_turret_aiming
do_movement
execute
end # }}}
def do_turret_aiming # {{{
@turn_turret = 1
dist = 800
i = 0
angle = nil
x, y = nil, nil
catch :tads do
loop do
x,y = predict(dist/30.0 + 1) # TODO ???
throw :tads unless x and y
angle, distn = get_angle_and_dist(x,y)
break if (distn - dist).abs < 15 or (i += 1) > 20
dist = distn
end
@turn_turret = deg_diff(angle, @turret_heading)
end
@angle = angle
end # }}}
def do_radar_aiming # {{{
@turn_radar = 10 and return unless @memory.lastseen
@radar_turn_speed ||= 60
if @memory.lastseen == 0
@radar_turn_speed *= -0.5
else @memory.lastseen != 2
@radar_turn_speed *= -2 if @radar_turn_speed.abs < 60
end
@turn_radar = @radar_turn_speed
end # }}}
def do_movement # {{{
=begin
http://bash.org/?240849
<Patrician|Away> what does your robot do, sam
<bovril> it collects data about the surrounding environment, then discards it
and drives into walls
=end
@accelerate = 1 # Energie!
@move_dur ||= 0
@turn_dur ||= 0
@acc_dur ||= 0
if @move_dur == 0
wangle, wdist = get_revangle_and_dist_wall
if wdist < 100
@turn_dir = strim(deg_diff(wangle, @body_heading), 10)
else
@turn_dir = (rand > 0.5) ? 10 : -10
end
@turn_dur = 7 + (rand 12)
@move_dur = @turn_dur + 5 + (rand 10)
@turn_body = @turn_dir
if ACC_WHILE_MOVING
@acc_dur = rand 7
end
elsif @turn_dur == 0
@move_dur -= 1
elsif @acc_dur == 0
@move_dur -= 1
@turn_dur -= 1
@turn_body = @turn_dir
else
@acc_dur -= 1
@move_dur -= 1
@turn_dur -= 1
@accelerate = -1
@turn_body = @turn_dir
end
end # }}}
def sign(x) # {{{
return 0 if x == 0
return 1 if x > 1
return -1
end # }}}
def dist_to_center # {{{
hypot(battlefield_height/2 - @y, battlefield_width/2 - @x)
end # }}}
def angle_to_center
end
def get_revangle_and_dist_wall
return [[@x,0], [@y,270], [battlefield_width - @x, 180], [battlefield_height - @y, 90]].min.reverse
end
def approaching_center # {{{
angle = (atan2(@x, @y).to_deg + 90) % 360
diff = deg_diff(angle, @body_heading).abs
diff < 80
end # }}}
def deg_diff(ang1, ang2) # {{{
(ang1 - ang2 + 180) % 360 - 180
end # }}}
def get_angle_and_dist(x,y) # {{{
dx = x - @x
dy = y - @y
return [(atan2(dx, dy).to_deg - 90) % 360, hypot(dx, dy)]
end # }}}
def predict(ticks, maxhist = 70) # {{{
i = 0
lx = 0.0
mx = 0.0
nx = 0.0
ox = 0.0
px = 0.0
ly = 0.0
my = 0.0
ny = 0.0
oy = 0.0
py = 0.0
@memory.enemypos.to_a.each_with_index do |ev, idx|
next unless ev
break if idx > maxhist
break if i > 5
x, dx, y, dy = ev
dx *= 1 + (idx / 50.to_f) ** 2
dy *= 1 + (idx / 50.to_f) ** 2
dx = 1.0 if dx < 1.0
dy = 1.0 if dy < 1.0
t = -idx-1
i += 1
sx = dx**2
lx += t**2/sx
mx += t/sx
nx += t*x/sx
ox += x/sx
px += 1/sx
sy = dy**2
ly += t**2/sy
my += t/sy
ny += t*y/sy
oy += y/sy
py += 1/sy
end
return unless i >= 2
ax = (px*nx - mx*ox)/(px*lx - mx**2)
bx = 1/px*(mx*ax-ox)
ay = (py*ny - my*oy)/(py*ly - my**2)
by = 1/py*(my*ay-oy)
bx *= -1
by *= -1
factor = hypot(ax, ay)/8.0
if factor > 1
ax /= factor
ay /= factor
end
bx = battlefield_width - size if bx > battlefield_width - size
by = battlefield_width - size if by > battlefield_width - size
bx = size if bx < size
by = size if by < size
px = bx+ax*ticks
py = by+ay*ticks
return if px.infinite? or px.nan? or py.infinite? or py.nan?
px = [battlefield_width - size, [size, px].max].min
py = [battlefield_height - size, [size, py].max].min
[px, py]
end # }}}
def execute # {{{
@turn_turret -= @turn_body
@turn_radar -= @turn_body + @turn_turret
@turn_body = trim(-10, @turn_body, 10)
@turn_turret = trim(-30, @turn_turret, 30)
@turn_radar = trim(-60, @turn_radar, 60)
@robot.accelerate(@accelerate)
@robot.turn(@turn_body)
@robot.turn_gun(@turn_turret)
@robot.turn_radar(@turn_radar)
@robot.fire(@power)
end # }}}
def save_memory # {{{
@memory.rheading << @radar_heading
@memory.pos << [@x, @y]
end # }}}
end # }}}
class Robot # {{{
include ::Robot
def initialize
@my_brain = Brain.new(self)
end
def tick(events)
@my_brain.tick(events)
end
end # }}}
end
class RubberDuckLinear < RubberRobotLinear::Robot; end