[SOLUTION] Robot

E

Edward Faulkner

--OgqxwSJOaUobr8KG
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

Here's my robot. The coolest part is the predictive tracker, which
makes him a pretty good shot.

Kinda long for a quiz submission, I know. But it's a tough problem.

regards,
Ed

require 'robot'
require 'matrix'

BOT_MAX_SPEED =3D 8
BULLET_SPEED =3D 30

class NotEnoughData < RuntimeError; end

class PredictiveTracker
def initialize(size =3D 4)
@size =3D size || 4

@x =3D Array.new(@size,0)=20
@y =3D Array.new(@size,0)
@t =3D Array.new(@size,0)

@most_recent =3D 0
@solution =3D nil
end

def mark(x,y,time)
@most_recent =3D (@most_recent + 1) % @size
@x[@most_recent] =3D x
@y[@most_recent] =3D y
@t[@most_recent] =3D time

# Update solution, if possible
@solution =3D solve
rescue
end
=20
def solve
xoff, vx =3D linfit(@t, @x)
yoff, vy =3D linfit(@t, @y)
[vx,vy,xoff,yoff]
end

# predicts target location at the given time
def predict(time)
raise NotEnoughData unless @solution
vx, vy, xoff, yoff =3D @solution
[xoff + vx*time, yoff + vy*time]
end


def aim_point(my_x, my_y, time)
raise NotEnoughData unless @solution
t =3D 0
loop do
x,y =3D predict(time+t)
break if vs(vd([x,y],[my_x,my_y])) < BULLET_SPEED*BULLET_SPEED*t*t
t +=3D 1
raise NotEnoughData if t > 100
end
predict(time+t)
end

def firing_angle(my_x,my_y,time)
x,y =3D aim_point(my_x,my_y,time)
Math.atan2(my_y-y,x-my_x).to_deg
end


# returns [a,b] such that a + bx =3D y is least squares linear fit
def linfit(x,y)
sum_x =3D 0.0
sum_y =3D 0.0
sum_prod =3D 0.0
sum_x2 =3D 0.0
n =3D x.size
n.times {|i|=20
sum_x +=3D x
sum_y +=3D y
sum_prod +=3D x*y
sum_x2 +=3D x*x
}
b =3D (n*sum_prod - sum_x*sum_y) / (n*sum_x2 - sum_x*sum_x)
a =3D (sum_y - b*sum_x) / n
raise NotEnoughData if a.nan? or b.nan?
[a,b]
end

# Vector difference
def vd(a,b)
a.zip(b).map{|a,b| a - b}
end

# Vector square
def vs(a)
dp(a,a)
end

# Dot product
def dp(a,b)
a.zip(b).map{|a,b| a*b}.inject(0){|a,b| a+b}
end

end


class EdBot
include Robot

def tick events
startup if time =3D=3D 0
update_radar(events)
update_gun
update_heading

accelerate 1

turn_radar(radar_velocity - @gun_velocity - @angular_velocity)
turn_gun(@gun_velocity - @angular_velocity)
turn(@angular_velocity)
end

def update_radar(events)
if events['robot_scanned'].empty?
if @saw_target
high_low
else
low_low
end
@saw_target =3D false
else
td =3D events['robot_scanned'].min.first
if @saw_target
high_high(td)
else
low_high(td)
end
@saw_target =3D true
end
@older_radar_heading =3D @old_radar_heading
@old_radar_heading =3D radar_heading
end

def low_low
@radar_speed =3D clamp(@radar_speed + target_angular_speed, 0, 60)

if @downticks > 0
@downticks -=3D 1
if @downticks =3D=3D 0
@radar_direction *=3D -1
end
end
end

def low_high(dist)
@uptick_heading =3D beam_center
@uptick_dist =3D dist
end

def high_high(dist)
@uptick_dist =3D dist
end

def high_low
@radar_direction *=3D -1
@radar_speed =3D clamp(@radar_speed * 0.5, target_angular_speed, 60)
plot_target(angle_average(angle_average(@old_radar_heading,@older_radar=
_heading),@uptick_heading),@uptick_dist)
@downticks =3D 8
end

def beam_center
angle_average(radar_heading, @old_radar_heading)
end

def trigger(spread)
if spread < 1
fire 3
end
end

def update_gun
diff =3D angle_direction(gun_heading, @tracker.firing_angle(x,y,time))
@gun_velocity =3D clamp(diff,-30,30)
trigger(diff)
rescue NotEnoughData
end

def wall_force(range)
(2**((battlefield_width - range)/50.0))/(2**(battlefield_width/50.0))
end

def update_heading
ranges =3D [x-size, battlefield_height-size-y, battlefield_width-size-x=
, y-size]
normals =3D [0, 90, 180, 270]
forces =3D ranges.map {|r| wall_force(r)}

@xforce =3D forces[0] - forces[2]
@yforce =3D forces[3] - forces[1]
fa =3D Math.atan2(-@yforce,@xforce).to_deg

goal =3D target_heading + 90
unless angle_difference(heading, goal) < 90
goal =3D (goal + 180) % 360
end

diff =3D angle_direction(goal, fa)
goal +=3D diff*(forces.max)
@angular_velocity =3D clamp(angle_direction(heading, goal),-10,10)
end


def startup
@saw_target =3D false
@uptick_heading =3D 0

@target_x =3D @target_y =3D 0

@radar_speed =3D 60
@radar_direction =3D 1
@old_radar_heading =3D 0
@older_radar_heading =3D 0
@downticks =3D 0

@gun_velocity =3D 0
@angular_velocity =3D 0
=20
@tracker =3D PredictiveTracker.new

@log =3D File.open("edbot.log","a")
@log.write "Starting up!\n"

end

def angle_difference(a,b)
d =3D (a % 360 - b % 360).abs
d > 180 ? 360 - d : d
end

# To turn from a toward b, how should you turn?
def angle_direction(a,b)
magnitude =3D angle_difference(a,b)
if angle_difference(a + 1, b) < magnitude
magnitude
else
-magnitude
end
end

def radar_velocity
@radar_speed * @radar_direction
end

def angle_average(a,b)
(angle_direction(a,b) / 2 + a) % 360
end

# How much can the angle to the target change in one tick?
def target_angular_speed
360 * BOT_MAX_SPEED / (2 * Math::pI * target_distance)
end

def target_heading
Math.atan2(y- @target_y, @target_x - x).to_deg
end

def target_distance
Math.sqrt((@target_x - x)**2 + (@target_y - y)**2)
end

def plot_target(heading, distance)
rads =3D heading.to_rad
@target_y =3D y - distance * Math.sin(rads)
@target_x =3D x + distance * Math.cos(rads)
@tracker.mark(@target_x,@target_y,time)
@log.write("#{@target_x}\t#{@target_y}\t#{time}\n")
end

def clamp(var, min, max)
val =3D 0 + var # to guard against poisoned vars
if val > max
max
elsif val < min
min
else
val
end
end

end



--OgqxwSJOaUobr8KG
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: Digital signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)

iD8DBQFDp7eCnhUz11p9MSARAr89AKCSn22IMlqPl0ZTkmjtokUckVtsvwCePvM2
MSzKExS29iOKWfrkIfp7LcM=
=yWrR
-----END PGP SIGNATURE-----

--OgqxwSJOaUobr8KG--
 
E

Edward Faulkner

--/WwmFnJnmDyWGHa4
Content-Type: multipart/mixed; boundary="J2SCkAp4GZ/dPZZf"
Content-Disposition: inline


--J2SCkAp4GZ/dPZZf
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I guess since filenames are significant I should have attached it
instead of inserting it. Here it is again.

regards,
Ed

--J2SCkAp4GZ/dPZZf
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="EdBot.rb"
Content-Transfer-Encoding: quoted-printable

require 'robot'
require 'matrix'

BOT_MAX_SPEED =3D 8
BULLET_SPEED =3D 30

class NotEnoughData < RuntimeError; end


class PredictiveTracker
def initialize(size =3D 4)
@size =3D size || 4

@x =3D Array.new(@size,0)=20
@y =3D Array.new(@size,0)
@t =3D Array.new(@size,0)

@most_recent =3D 0
@solution =3D nil
end

def mark(x,y,time)
@most_recent =3D (@most_recent + 1) % @size
@x[@most_recent] =3D x
@y[@most_recent] =3D y
@t[@most_recent] =3D time

# Update solution, if possible
@solution =3D solve
rescue
end
=20
def solve
xoff, vx =3D linfit(@t, @x)
yoff, vy =3D linfit(@t, @y)
[vx,vy,xoff,yoff]
end

# predicts target location at the given time
def predict(time)
raise NotEnoughData unless @solution
vx, vy, xoff, yoff =3D @solution
[xoff + vx*time, yoff + vy*time]
end


def aim_point(my_x, my_y, time)
raise NotEnoughData unless @solution
t =3D 0
loop do
x,y =3D predict(time+t)
break if vs(vd([x,y],[my_x,my_y])) < BULLET_SPEED*BULLET_SPEED*t*t
t +=3D 1
raise NotEnoughData if t > 100
end
predict(time+t)
end

def firing_angle(my_x,my_y,time)
x,y =3D aim_point(my_x,my_y,time)
Math.atan2(my_y-y,x-my_x).to_deg
end


# returns [a,b] such that a + bx =3D y is least squares linear fit
def linfit(x,y)
sum_x =3D 0.0
sum_y =3D 0.0
sum_prod =3D 0.0
sum_x2 =3D 0.0
n =3D x.size
n.times {|i|=20
sum_x +=3D x
sum_y +=3D y
sum_prod +=3D x*y
sum_x2 +=3D x*x
}
b =3D (n*sum_prod - sum_x*sum_y) / (n*sum_x2 - sum_x*sum_x)
a =3D (sum_y - b*sum_x) / n
raise NotEnoughData if a.nan? or b.nan?
[a,b]
end

# Vector difference
def vd(a,b)
a.zip(b).map{|a,b| a - b}
end

# Vector square
def vs(a)
dp(a,a)
end

# Dot product
def dp(a,b)
a.zip(b).map{|a,b| a*b}.inject(0){|a,b| a+b}
end

end


class EdBot
include Robot

def tick events
startup if time =3D=3D 0
update_radar(events)
update_gun
update_heading

accelerate 1

turn_radar(radar_velocity - @gun_velocity - @angular_velocity)
turn_gun(@gun_velocity - @angular_velocity)
turn(@angular_velocity)
end

def update_radar(events)
if events['robot_scanned'].empty?
if @saw_target
high_low
else
low_low
end
@saw_target =3D false
else
td =3D events['robot_scanned'].min.first
if @saw_target
high_high(td)
else
low_high(td)
end
@saw_target =3D true
end
@older_radar_heading =3D @old_radar_heading
@old_radar_heading =3D radar_heading
end

def low_low
@radar_speed =3D clamp(@radar_speed + target_angular_speed, 0, 60)

if @downticks > 0
@downticks -=3D 1
if @downticks =3D=3D 0
@radar_direction *=3D -1
end
end
end

def low_high(dist)
@uptick_heading =3D beam_center
@uptick_dist =3D dist
end

def high_high(dist)
@uptick_dist =3D dist
end

def high_low
@radar_direction *=3D -1
@radar_speed =3D clamp(@radar_speed * 0.5, target_angular_speed, 60)
plot_target(angle_average(angle_average(@old_radar_heading,@older_radar=
_heading),@uptick_heading),@uptick_dist)
@downticks =3D 8
end

def beam_center
angle_average(radar_heading, @old_radar_heading)
end

def trigger(spread)
if spread < 1
fire 3
end
end

def update_gun
diff =3D angle_direction(gun_heading, @tracker.firing_angle(x,y,time))
@gun_velocity =3D clamp(diff,-30,30)
trigger(diff)
rescue NotEnoughData
end

def wall_force(range)
(2**((battlefield_width - range)/50.0))/(2**(battlefield_width/50.0))
end

def update_heading
ranges =3D [x-size, battlefield_height-size-y, battlefield_width-size-x=
, y-size]
normals =3D [0, 90, 180, 270]
forces =3D ranges.map {|r| wall_force(r)}

@xforce =3D forces[0] - forces[2]
@yforce =3D forces[3] - forces[1]
fa =3D Math.atan2(-@yforce,@xforce).to_deg

goal =3D target_heading + 90
unless angle_difference(heading, goal) < 90
goal =3D (goal + 180) % 360
end

diff =3D angle_direction(goal, fa)
goal +=3D diff*(forces.max)
@angular_velocity =3D clamp(angle_direction(heading, goal),-10,10)
end


def startup
@saw_target =3D false
@uptick_heading =3D 0

@target_x =3D @target_y =3D 0

@radar_speed =3D 60
@radar_direction =3D 1
@old_radar_heading =3D 0
@older_radar_heading =3D 0
@downticks =3D 0

@gun_velocity =3D 0
@angular_velocity =3D 0
=20
@tracker =3D PredictiveTracker.new

@log =3D File.open("edbot.log","a")
@log.write "Starting up!\n"

end

def angle_difference(a,b)
d =3D (a % 360 - b % 360).abs
d > 180 ? 360 - d : d
end

# To turn from a toward b, how should you turn?
def angle_direction(a,b)
magnitude =3D angle_difference(a,b)
if angle_difference(a + 1, b) < magnitude
magnitude
else
-magnitude
end
end

def radar_velocity
@radar_speed * @radar_direction
end

def angle_average(a,b)
(angle_direction(a,b) / 2 + a) % 360
end

# How much can the angle to the target change in one tick?
def target_angular_speed
360 * BOT_MAX_SPEED / (2 * Math::pI * target_distance)
end

def target_heading
Math.atan2(y- @target_y, @target_x - x).to_deg
end

def target_distance
Math.sqrt((@target_x - x)**2 + (@target_y - y)**2)
end

def plot_target(heading, distance)
rads =3D heading.to_rad
@target_y =3D y - distance * Math.sin(rads)
@target_x =3D x + distance * Math.cos(rads)
@tracker.mark(@target_x,@target_y,time)
@log.write("#{@target_x}\t#{@target_y}\t#{time}\n")
end

def clamp(var, min, max)
val =3D 0 + var # to guard against poisoned vars
if val > max
max
elsif val < min
min
else
val
end
end

end


--J2SCkAp4GZ/dPZZf--

--/WwmFnJnmDyWGHa4
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: Digital signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)

iD8DBQFDqCQInhUz11p9MSARAnd3AJ9gCgJJwbTtuMYEgVJmp6f+uSH9ewCaA9ky
2DIMKplnns+IDkSDqdGQrbw=
=6qYU
-----END PGP SIGNATURE-----

--/WwmFnJnmDyWGHa4--
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,077
Latest member
SangMoor21

Latest Threads

Top