# Robot

**URL:** <https://www.ruby-forum.com/t/robot/225894>\
**Category:** Ruby\
**Created:** [December 20, 2005, 8:50am UTC](https://www.ruby-forum.com/t/robot/225894 "2005-12-20T08:50:37Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Edward\_F](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/e/ac8455/32.png) [@Edward\_F](https://www.ruby-forum.com/u/Edward_F)\
**Post date:** [December 20, 2005, 8:50am UTC](https://www.ruby-forum.com/t/robot/225894/1 "2005-12-20T08:50:37Z")

</div>

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 = 8  
BULLET\_SPEED = 30

class NotEnoughData \< RuntimeError; end

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

```
@x = Array.new(@size,0)
@y = Array.new(@size,0)
@t = Array.new(@size,0)

@most_recent = 0
@solution = nil

```

end

def mark(x,y,time)  
@most\_recent = (@most\_recent + 1) % @size  
@x[@most\_recent] = x  
@y[@most\_recent] = y  
@t[@most\_recent] = time

```
# Update solution, if possible
@solution = solve

```

rescue  
end

def solve  
xoff, vx = linfit(@t, @x)  
yoff, vy = 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 = @solution  
[xoff + vx_time, yoff + vy_time]  
end

def aim\_point(my\_x, my\_y, time)  
raise NotEnoughData unless @solution  
t = 0  
loop do  
x,y = predict(time+t)  
break if vs(vd([x,y],[my\_x,my\_y])) \< BULLET\_SPEED_BULLET\_SPEED_t\*t  
t += 1  
raise NotEnoughData if t \> 100  
end  
predict(time+t)  
end

def firing\_angle(my\_x,my\_y,time)  
x,y = 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 = y is least squares linear fit

def linfit(x,y)  
sum\_x = 0.0  
sum\_y = 0.0  
sum\_prod = 0.0  
sum\_x2 = 0.0  
n = x.size  
n.times {|i|  
sum\_x += x[i]  
sum\_y += y[i]  
sum\_prod += x[i]_y[i]  
sum\_x2 += x[i]x[i]  
}  
b = (nsum\_prod - sum\_x_sum\_y) / (n_sum\_x2 - sum\_x_sum\_x)  
a = (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 == 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 = false  
else  
td = events[‘robot\_scanned’].min.first  
if @saw\_target  
high\_high(td)  
else  
low\_high(td)  
end  
@saw\_target = true  
end  
@older\_radar\_heading = @old\_radar\_heading  
@old\_radar\_heading = radar\_heading  
end

def low\_low  
@radar\_speed = clamp(@radar\_speed + target\_angular\_speed, 0, 60)

```
if @downticks > 0
  @downticks -= 1
  if @downticks == 0
    @radar_direction *= -1
  end
end

```

end

def low\_high(dist)  
@uptick\_heading = beam\_center  
@uptick\_dist = dist  
end

def high\_high(dist)  
@uptick\_dist = dist  
end

def high\_low  
@radar\_direction \*= -1  
@radar\_speed = 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 = 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 = angle\_direction(gun\_heading, @tracker.firing\_angle(x,y,time))  
@gun\_velocity = 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 = [x-size, battlefield\_height-size-y,  
battlefield\_width-size-x, y-size]  
normals = [0, 90, 180, 270]  
forces = ranges.map {|r| wall\_force®}

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

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

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

```

end

def startup  
@saw\_target = false  
@uptick\_heading = 0

```
@target_x = @target_y = 0

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

@gun_velocity = 0
@angular_velocity = 0

@tracker = PredictiveTracker.new

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

```

end

def angle\_difference(a,b)  
d = (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 = 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 = heading.to\_rad  
@target\_y = y - distance \* Math.sin(rads)  
@target\_x = 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 = 0 + var # to guard against poisoned vars  
if val \> max  
max  
elsif val \< min  
min  
else  
val  
end  
end

end

---

<div class="post-metadata">

**Author:** ![Edward\_F](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/e/ac8455/32.png) [@Edward\_F](https://www.ruby-forum.com/u/Edward_F)\
**Post date:** [December 20, 2005, 4:33pm UTC](https://www.ruby-forum.com/t/robot/225894/2 "2005-12-20T16:33:44Z")

</div>

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

regards,  
Ed
