Example programs
The programs that come with the game: the STARTER, a few lessons, and the training opponents. Open one to read it; copy it into the editor to try it.
The league's house vehicles aren't here: they're there to be beaten by a better program, not by reading theirs. Each one's description, in the game's opponent list, says how it fights.
Lessons
StarterSearches in 15-degree steps, keeps moving, brings its turret round to a fresh contact and fires, and gets out of the way of incoming missiles. Needs drive, radar, missiles and a missile warning sensor.
from vehicle import drive, radar, missiles, warning, debug, hull
def update(state, memory):
if memory.scan_angle == None:
memory.scan_angle = 0
# With nothing to chase, carry straight on.
course = state.heading
throttle = 0.6
contact = radar.contact()
if contact and state.time - contact.seen_at <= 0.2:
radar.scan(contact.bearing)
missiles.aim(contact.bearing)
# Close in to one side of the contact, not straight at it.
course = contact.bearing + 35
throttle = 0.9
# Near enough: go round it instead.
if contact.range < 90:
course = contact.bearing + 90
# Fire only once the turret has come round to the contact.
on_target = abs(angle_diff(contact.bearing, missiles.turret())) <= 3
if on_target and missiles.ready():
missiles.fire()
debug.log("Shot requested")
else:
memory.scan_angle = (memory.scan_angle + 15) % 360
radar.scan(memory.scan_angle)
# A missile coming for us: get out of its way. Go across its path,
# not away along it, turning whichever way is nearer.
threat = warning.threat()
if threat and threat.eta > 0.2:
course = threat.heading + 90
if abs(angle_diff(course, state.heading)) > 90:
course = threat.heading - 90
throttle = 1
# Near a wall, turn back towards the middle of the arena.
margin = 40
near_x = state.x < margin or state.x > state.arena_width - margin
near_y = state.y < margin or state.y > state.arena_height - margin
if near_x or near_y:
course = bearing_to(state.x, state.y, state.arena_width / 2, state.arena_height / 2)
# Up against something: turn off to one side until it is clear.
if state.blocked:
course = state.heading + 90
# Steer onto the course: the further off it is, the harder the turn.
turn = clamp(angle_diff(course, state.heading) / 45, -1, 1)
drive.set(throttle=throttle, turn=turn)
# Resupply. A scan's echo can find a supply crate: keep the nearest
# one found. Out of missiles, go to it, stop on it and reload.
echo = radar.echo()
if echo and echo.supply:
nearer = memory.crate_x == None
if not nearer:
kx = abs(memory.crate_x - state.x)
ky = abs(memory.crate_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.crate_x = echo.x
memory.crate_y = echo.y
if missiles.ammo() == 0:
if memory.crate_x == None:
# None found yet: sweep the radar round for one.
radar.scan(wrap(state.time * 250))
else:
kx = memory.crate_x - state.x
ky = memory.crate_y - state.y
near = kx * kx + ky * ky
if near < 6 * 6:
# There: stop, and once stopped, reload.
drive.set(throttle=0, turn=0)
if memory.arrived == None:
memory.arrived = state.time
if abs(state.speed) < 1:
missiles.reload()
# Still empty after a while: someone else emptied this
# crate. Forget it, and look for another.
if state.time - memory.arrived > 4:
memory.crate_x = None
memory.arrived = None
else:
memory.arrived = None
course = bearing_to(state.x, state.y, memory.crate_x, memory.crate_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Repair. A scan's echo can find a repair depot: keep the nearest
# found. Badly hurt, go to it, stop on it and repair; then leave it
# alone for a while, since it is empty once used.
if echo and echo.repair:
nearer = memory.depot_x == None
if not nearer:
kx = abs(memory.depot_x - state.x)
ky = abs(memory.depot_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.depot_x = echo.x
memory.depot_y = echo.y
hurt = state.health < 50 and memory.depot_x != None
if memory.skip_until != None and state.time < memory.skip_until:
hurt = False
# Mid-repair: stay put until it is done.
if memory.mend_until != None and state.time < memory.mend_until:
hurt = True
if hurt:
kx = memory.depot_x - state.x
ky = memory.depot_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
hull.repair()
if memory.mend_until == None or state.time > memory.mend_until:
memory.mend_until = state.time + 4.5
memory.skip_until = state.time + 25
else:
course = bearing_to(state.x, state.y, memory.depot_x, memory.depot_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Recharge. Energy comes back only at a charging point, and a scan's
# echo can find one: keep the nearest found. Running low, go to it,
# stop on it and charge until the tank is full.
if echo and echo.charger:
nearer = memory.charger_x == None
if not nearer:
kx = abs(memory.charger_x - state.x)
ky = abs(memory.charger_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.charger_x = echo.x
memory.charger_y = echo.y
if state.energy < state.energy_max * 0.3:
memory.charging = True
elif state.energy > state.energy_max * 0.99:
memory.charging = False
# Getting low and none found yet: sweep the radar round for one.
if memory.charger_x == None and state.energy < state.energy_max * 0.6:
radar.scan(wrap(state.time * 250))
if memory.charging and memory.charger_x != None:
kx = memory.charger_x - state.x
ky = memory.charger_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
# There (it reaches 6 m): stop, and once stopped, charge.
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
drive.charge()
else:
course = bearing_to(state.x, state.y, memory.charger_x, memory.charger_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)Energy-aware hunterTurns towards contacts and lowers its shield when energy is short. Needs a shield generator too.
from vehicle import drive, radar, missiles, shield
def update(state, memory):
if memory.scan_angle == None:
memory.scan_angle = state.heading
if state.energy > 60 and state.heat < 70:
shield.set_level(0.5)
else:
shield.set_level(0)
contact = radar.contact()
if contact and state.time - contact.seen_at <= 0.2:
error = angle_diff(contact.bearing, state.heading)
turn = clamp(error / 45, -1, 1)
throttle = 0.7
if contact.range < 80:
throttle = 0.2
drive.set(throttle=throttle, turn=turn)
radar.scan(contact.bearing)
missiles.aim(contact.bearing)
memory.scan_angle = contact.bearing
on_target = abs(angle_diff(contact.bearing, missiles.turret())) <= 3
if on_target and missiles.ready() and state.heat < 80:
missiles.fire()
else:
memory.scan_angle = wrap(memory.scan_angle + 20)
radar.scan(memory.scan_angle)
drive.set(throttle=0.4, turn=0.35)Scan schedulingKeeps a timetable in memory. It only drives and scans: add your own aiming and firing.
from vehicle import drive, radar
def update(state, memory):
if memory.next_scan == None:
memory.next_scan = 0
memory.scan_angle = 0
drive.set(throttle=0.5, turn=0.2)
if state.time >= memory.next_scan:
radar.scan(memory.scan_angle)
memory.scan_angle = wrap(memory.scan_angle + 30)
memory.next_scan = state.time + 0.3Taking coverThe starter's program, plus cover: it remembers the nearest obstacle its radar has echoed off, and gets behind it when hit. Needs a missile warning sensor too, as the starter does.
# Taking cover: the starter's program, plus a memory of the nearest
# obstacle the radar has found. When hit, it gets behind it for a while.
from vehicle import drive, radar, missiles, warning, hull
def update(state, memory):
if memory.scan_angle == None:
memory.scan_angle = 0
memory.cover = False
memory.hide_until = -1
memory.health = state.health
# Every scan also says what it ran into. Keep the nearest obstacle (not
# a wall) as cover.
echo = radar.echo()
if echo and not echo.wall and state.time - echo.seen_at < 0.2:
far = 1000
if memory.cover:
cx = abs(memory.cover_x - state.x)
cy = abs(memory.cover_y - state.y)
far = max(cx, cy) + 0.4 * min(cx, cy)
if echo.range < far:
memory.cover = True
memory.cover_x = echo.x
memory.cover_y = echo.y
# Hit: time to hide.
if state.health < memory.health:
memory.hide_until = state.time + 2.5
memory.health = state.health
# With nothing to chase, carry straight on.
course = state.heading
throttle = 0.6
contact = radar.contact()
if contact and state.time - contact.seen_at <= 0.2:
memory.enemy_x = contact.x
memory.enemy_y = contact.y
radar.scan(contact.bearing)
missiles.aim(contact.bearing)
# Close in to one side of the contact, not straight at it.
course = contact.bearing + 35
throttle = 0.9
# Near enough: go round it instead.
if contact.range < 90:
course = contact.bearing + 90
# Fire only once the turret has come round to the contact.
on_target = abs(angle_diff(contact.bearing, missiles.turret())) <= 3
if on_target and missiles.ready():
missiles.fire()
else:
memory.scan_angle = (memory.scan_angle + 15) % 360
radar.scan(memory.scan_angle)
# Hiding: make for the far side of the cover from the opponent.
if memory.cover and memory.enemy_x != None and state.time < memory.hide_until:
dx = memory.cover_x - memory.enemy_x
dy = memory.cover_y - memory.enemy_y
length = max(abs(dx), abs(dy)) + 0.4 * min(abs(dx), abs(dy))
if length > 1:
course = bearing_to(state.x, state.y, memory.cover_x + dx / length * 15, memory.cover_y + dy / length * 15)
throttle = 1
# A missile coming for us: get out of its way. Go across its path,
# not away along it, turning whichever way is nearer.
threat = warning.threat()
if threat and threat.eta > 0.2:
course = threat.heading + 90
if abs(angle_diff(course, state.heading)) > 90:
course = threat.heading - 90
throttle = 1
# Near a wall, turn back towards the middle of the arena.
margin = 40
near_x = state.x < margin or state.x > state.arena_width - margin
near_y = state.y < margin or state.y > state.arena_height - margin
if near_x or near_y:
course = bearing_to(state.x, state.y, state.arena_width / 2, state.arena_height / 2)
# Up against something: turn off to one side until it is clear.
if state.blocked:
course = state.heading + 90
# Steer onto the course: the further off it is, the harder the turn.
turn = clamp(angle_diff(course, state.heading) / 45, -1, 1)
drive.set(throttle=throttle, turn=turn)
# Resupply. A scan's echo can find a supply crate: keep the nearest
# one found. Out of missiles, go to it, stop on it and reload.
echo = radar.echo()
if echo and echo.supply:
nearer = memory.crate_x == None
if not nearer:
kx = abs(memory.crate_x - state.x)
ky = abs(memory.crate_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.crate_x = echo.x
memory.crate_y = echo.y
if missiles.ammo() == 0:
if memory.crate_x == None:
# None found yet: sweep the radar round for one.
radar.scan(wrap(state.time * 250))
else:
kx = memory.crate_x - state.x
ky = memory.crate_y - state.y
near = kx * kx + ky * ky
if near < 6 * 6:
# There: stop, and once stopped, reload.
drive.set(throttle=0, turn=0)
if memory.arrived == None:
memory.arrived = state.time
if abs(state.speed) < 1:
missiles.reload()
# Still empty after a while: someone else emptied this
# crate. Forget it, and look for another.
if state.time - memory.arrived > 4:
memory.crate_x = None
memory.arrived = None
else:
memory.arrived = None
course = bearing_to(state.x, state.y, memory.crate_x, memory.crate_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Repair. A scan's echo can find a repair depot: keep the nearest
# found. Badly hurt, go to it, stop on it and repair; then leave it
# alone for a while, since it is empty once used.
if echo and echo.repair:
nearer = memory.depot_x == None
if not nearer:
kx = abs(memory.depot_x - state.x)
ky = abs(memory.depot_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.depot_x = echo.x
memory.depot_y = echo.y
hurt = state.health < 50 and memory.depot_x != None
if memory.skip_until != None and state.time < memory.skip_until:
hurt = False
# Mid-repair: stay put until it is done.
if memory.mend_until != None and state.time < memory.mend_until:
hurt = True
if hurt:
kx = memory.depot_x - state.x
ky = memory.depot_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
hull.repair()
if memory.mend_until == None or state.time > memory.mend_until:
memory.mend_until = state.time + 4.5
memory.skip_until = state.time + 25
else:
course = bearing_to(state.x, state.y, memory.depot_x, memory.depot_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Recharge. Energy comes back only at a charging point, and a scan's
# echo can find one: keep the nearest found. Running low, go to it,
# stop on it and charge until the tank is full.
if echo and echo.charger:
nearer = memory.charger_x == None
if not nearer:
kx = abs(memory.charger_x - state.x)
ky = abs(memory.charger_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.charger_x = echo.x
memory.charger_y = echo.y
if state.energy < state.energy_max * 0.3:
memory.charging = True
elif state.energy > state.energy_max * 0.99:
memory.charging = False
# Getting low and none found yet: sweep the radar round for one.
if memory.charger_x == None and state.energy < state.energy_max * 0.6:
radar.scan(wrap(state.time * 250))
if memory.charging and memory.charger_x != None:
kx = memory.charger_x - state.x
ky = memory.charger_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
# There (it reaches 6 m): stop, and once stopped, charge.
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
drive.charge()
else:
course = bearing_to(state.x, state.y, memory.charger_x, memory.charger_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)Training opponents
SentryThe SENTRY's own program.
# SENTRY: holds its ground, sweeps its radar and fires at what it finds.
from vehicle import drive, radar, missiles, shield, hull
def update(state, memory):
if memory.angle == None:
memory.angle = state.heading
drive.set(throttle=0, turn=0)
if state.energy > 55 and state.heat < 60:
shield.set_level(0.5)
else:
shield.set_level(0)
contact = radar.contact()
if contact and state.time - contact.seen_at <= 0.25:
memory.angle = contact.bearing
radar.scan(contact.bearing)
missiles.aim(contact.bearing)
on_target = abs(angle_diff(contact.bearing, missiles.turret())) <= 3
# Keep a reserve: an empty tank cuts the power to everything.
if on_target and missiles.ready() and state.heat < 85 and state.energy > 20:
missiles.fire()
else:
memory.angle = wrap(memory.angle + 28)
radar.scan(memory.angle)
# Resupply. A scan's echo can find a supply crate: keep the nearest
# one found. Out of missiles, go to it, stop on it and reload.
echo = radar.echo()
if echo and echo.supply:
nearer = memory.crate_x == None
if not nearer:
kx = abs(memory.crate_x - state.x)
ky = abs(memory.crate_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.crate_x = echo.x
memory.crate_y = echo.y
if missiles.ammo() == 0:
if memory.crate_x == None:
# None found yet: sweep the radar round for one.
radar.scan(wrap(state.time * 250))
else:
kx = memory.crate_x - state.x
ky = memory.crate_y - state.y
near = kx * kx + ky * ky
if near < 6 * 6:
# There: stop, and once stopped, reload.
drive.set(throttle=0, turn=0)
if memory.arrived == None:
memory.arrived = state.time
if abs(state.speed) < 1:
missiles.reload()
# Still empty after a while: someone else emptied this
# crate. Forget it, and look for another.
if state.time - memory.arrived > 4:
memory.crate_x = None
memory.arrived = None
else:
memory.arrived = None
course = bearing_to(state.x, state.y, memory.crate_x, memory.crate_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Repair. A scan's echo can find a repair depot: keep the nearest
# found. Badly hurt, go to it, stop on it and repair; then leave it
# alone for a while, since it is empty once used.
if echo and echo.repair:
nearer = memory.depot_x == None
if not nearer:
kx = abs(memory.depot_x - state.x)
ky = abs(memory.depot_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.depot_x = echo.x
memory.depot_y = echo.y
hurt = state.health < 50 and memory.depot_x != None
if memory.skip_until != None and state.time < memory.skip_until:
hurt = False
# Mid-repair: stay put until it is done.
if memory.mend_until != None and state.time < memory.mend_until:
hurt = True
if hurt:
kx = memory.depot_x - state.x
ky = memory.depot_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
hull.repair()
if memory.mend_until == None or state.time > memory.mend_until:
memory.mend_until = state.time + 4.5
memory.skip_until = state.time + 25
else:
course = bearing_to(state.x, state.y, memory.depot_x, memory.depot_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Recharge. Energy comes back only at a charging point, and a scan's
# echo can find one: keep the nearest found. Running low, go to it,
# stop on it and charge until the tank is full.
if echo and echo.charger:
nearer = memory.charger_x == None
if not nearer:
kx = abs(memory.charger_x - state.x)
ky = abs(memory.charger_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.charger_x = echo.x
memory.charger_y = echo.y
if state.energy < state.energy_max * 0.3:
memory.charging = True
elif state.energy > state.energy_max * 0.99:
memory.charging = False
# Getting low and none found yet: sweep the radar round for one.
if memory.charger_x == None and state.energy < state.energy_max * 0.6:
radar.scan(wrap(state.time * 250))
if memory.charging and memory.charger_x != None:
kx = memory.charger_x - state.x
ky = memory.charger_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
# There (it reaches 6 m): stop, and once stopped, charge.
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
drive.charge()
else:
course = bearing_to(state.x, state.y, memory.charger_x, memory.charger_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)HunterThe HUNTER's own program.
# HUNTER: searches, closes in on a contact and keeps firing.
from vehicle import drive, radar, missiles, hull
def update(state, memory):
if memory.scan == None:
memory.scan = state.heading
memory.unstick_until = 0
memory.mark_t = -10
contact = radar.contact()
if contact and state.time - contact.seen_at <= 0.25:
memory.scan = contact.bearing
radar.scan(contact.bearing)
# Lead the target: work out its speed from where it was a moment
# ago, and aim at where it will be when a missile gets there.
if state.time - memory.mark_t > 1.5:
memory.mark_x = contact.x
memory.mark_y = contact.y
memory.mark_t = state.time
memory.vx = 0
memory.vy = 0
elif state.time - memory.mark_t >= 0.5:
passed = state.time - memory.mark_t
memory.vx = (contact.x - memory.mark_x) / passed
memory.vy = (contact.y - memory.mark_y) / passed
memory.mark_x = contact.x
memory.mark_y = contact.y
memory.mark_t = state.time
flight = contact.range / 140
aim = bearing_to(state.x, state.y, contact.x + memory.vx * flight, contact.y + memory.vy * flight)
missiles.aim(aim)
# Spiral in rather than driving straight down a missile's path.
course = contact.bearing + 30
# Close enough: circle the target, always the same way round.
if contact.range < 110:
course = contact.bearing + 80
# The turbo drive, the radar and the launcher together use more
# energy than the chassis makes: ease off when it runs low. It is
# not enough. HUNTER spends what it has, and in a long fight it
# runs dry and loses power: that is the moment to hit it.
throttle = 1
if state.energy < 45:
throttle = 0.4
error = angle_diff(course, state.heading)
drive.set(throttle=throttle, turn=clamp(error / 40, -1, 1))
on_target = abs(angle_diff(aim, missiles.turret())) <= 4
if on_target and missiles.ready() and state.heat < 80 and state.energy > 20:
missiles.fire()
else:
memory.scan = wrap(memory.scan + 28)
radar.scan(memory.scan)
# Turn hard for a moment after bumping into something.
if state.blocked:
memory.unstick_until = state.time + 0.8
if state.time < memory.unstick_until:
drive.set(throttle=0.6, turn=1)
else:
drive.set(throttle=0.6, turn=0.2)
# Resupply. A scan's echo can find a supply crate: keep the nearest
# one found. Out of missiles, go to it, stop on it and reload.
echo = radar.echo()
if echo and echo.supply:
nearer = memory.crate_x == None
if not nearer:
kx = abs(memory.crate_x - state.x)
ky = abs(memory.crate_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.crate_x = echo.x
memory.crate_y = echo.y
if missiles.ammo() == 0:
if memory.crate_x == None:
# None found yet: sweep the radar round for one.
radar.scan(wrap(state.time * 250))
else:
kx = memory.crate_x - state.x
ky = memory.crate_y - state.y
near = kx * kx + ky * ky
if near < 6 * 6:
# There: stop, and once stopped, reload.
drive.set(throttle=0, turn=0)
if memory.arrived == None:
memory.arrived = state.time
if abs(state.speed) < 1:
missiles.reload()
# Still empty after a while: someone else emptied this
# crate. Forget it, and look for another.
if state.time - memory.arrived > 4:
memory.crate_x = None
memory.arrived = None
else:
memory.arrived = None
course = bearing_to(state.x, state.y, memory.crate_x, memory.crate_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Repair. A scan's echo can find a repair depot: keep the nearest
# found. Badly hurt, go to it, stop on it and repair; then leave it
# alone for a while, since it is empty once used.
if echo and echo.repair:
nearer = memory.depot_x == None
if not nearer:
kx = abs(memory.depot_x - state.x)
ky = abs(memory.depot_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.depot_x = echo.x
memory.depot_y = echo.y
hurt = state.health < 50 and memory.depot_x != None
if memory.skip_until != None and state.time < memory.skip_until:
hurt = False
# Mid-repair: stay put until it is done.
if memory.mend_until != None and state.time < memory.mend_until:
hurt = True
if hurt:
kx = memory.depot_x - state.x
ky = memory.depot_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
hull.repair()
if memory.mend_until == None or state.time > memory.mend_until:
memory.mend_until = state.time + 4.5
memory.skip_until = state.time + 25
else:
course = bearing_to(state.x, state.y, memory.depot_x, memory.depot_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Recharge. Energy comes back only at a charging point, and a scan's
# echo can find one: keep the nearest found. Running low, go to it,
# stop on it and charge until the tank is full.
if echo and echo.charger:
nearer = memory.charger_x == None
if not nearer:
kx = abs(memory.charger_x - state.x)
ky = abs(memory.charger_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.charger_x = echo.x
memory.charger_y = echo.y
if state.energy < state.energy_max * 0.3:
memory.charging = True
elif state.energy > state.energy_max * 0.99:
memory.charging = False
# Getting low and none found yet: sweep the radar round for one.
if memory.charger_x == None and state.energy < state.energy_max * 0.6:
radar.scan(wrap(state.time * 250))
if memory.charging and memory.charger_x != None:
kx = memory.charger_x - state.x
ky = memory.charger_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
# There (it reaches 6 m): stop, and once stopped, charge.
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
drive.charge()
else:
course = bearing_to(state.x, state.y, memory.charger_x, memory.charger_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)EvaderThe EVADER's own program.
# EVADER: never stops moving, weaves, and turns away when it is hit.
from vehicle import drive, radar, missiles, shield, hull
def update(state, memory):
if memory.scan == None:
memory.scan = state.heading
memory.weave = 1
memory.flip_at = 1.2
memory.health = state.health
memory.alarm_until = 0
memory.mark_t = -10
# Damage taken: weave the other way and raise the shield for a while.
if state.health < memory.health:
memory.weave = 0 - memory.weave
memory.alarm_until = state.time + 2
memory.health = state.health
if state.time >= memory.flip_at:
memory.weave = 0 - memory.weave
memory.flip_at = state.time + 1.2
if state.time < memory.alarm_until and state.energy > 30 and state.heat < 75:
shield.set_level(1)
else:
shield.set_level(0)
# Near a wall, steer for the middle of the arena.
margin = 45
near_x = state.x < margin or state.x > state.arena_width - margin
near_y = state.y < margin or state.y > state.arena_height - margin
turn = memory.weave * 0.55
if near_x or near_y or state.blocked:
home = bearing_to(state.x, state.y, state.arena_width / 2, state.arena_height / 2)
turn = clamp(angle_diff(home, state.heading) / 30, -1, 1)
# Flat out, firing and shielded, it spends more than the chassis makes,
# and an empty tank would stop it dead: ease off when it runs low.
throttle = 1
if state.energy < 25:
throttle = 0.5
drive.set(throttle=throttle, turn=turn)
contact = radar.contact()
if contact and state.time - contact.seen_at <= 0.25:
memory.scan = contact.bearing
radar.scan(contact.bearing)
# Lead the target: work out its speed from where it was a moment
# ago, and aim at where it will be when a missile gets there.
if state.time - memory.mark_t > 1.5:
memory.mark_x = contact.x
memory.mark_y = contact.y
memory.mark_t = state.time
memory.vx = 0
memory.vy = 0
elif state.time - memory.mark_t >= 0.5:
passed = state.time - memory.mark_t
memory.vx = (contact.x - memory.mark_x) / passed
memory.vy = (contact.y - memory.mark_y) / passed
memory.mark_x = contact.x
memory.mark_y = contact.y
memory.mark_t = state.time
flight = contact.range / 140
aim = bearing_to(state.x, state.y, contact.x + memory.vx * flight, contact.y + memory.vy * flight)
missiles.aim(aim)
on_target = abs(angle_diff(aim, missiles.turret())) <= 3
if on_target and missiles.ready() and state.heat < 70 and state.energy > 20:
missiles.fire()
else:
memory.scan = wrap(memory.scan + 18)
radar.scan(memory.scan)
# Resupply. A scan's echo can find a supply crate: keep the nearest
# one found. Out of missiles, go to it, stop on it and reload.
echo = radar.echo()
if echo and echo.supply:
nearer = memory.crate_x == None
if not nearer:
kx = abs(memory.crate_x - state.x)
ky = abs(memory.crate_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.crate_x = echo.x
memory.crate_y = echo.y
if missiles.ammo() == 0:
if memory.crate_x == None:
# None found yet: sweep the radar round for one.
radar.scan(wrap(state.time * 250))
else:
kx = memory.crate_x - state.x
ky = memory.crate_y - state.y
near = kx * kx + ky * ky
if near < 6 * 6:
# There: stop, and once stopped, reload.
drive.set(throttle=0, turn=0)
if memory.arrived == None:
memory.arrived = state.time
if abs(state.speed) < 1:
missiles.reload()
# Still empty after a while: someone else emptied this
# crate. Forget it, and look for another.
if state.time - memory.arrived > 4:
memory.crate_x = None
memory.arrived = None
else:
memory.arrived = None
course = bearing_to(state.x, state.y, memory.crate_x, memory.crate_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Repair. A scan's echo can find a repair depot: keep the nearest
# found. Badly hurt, go to it, stop on it and repair; then leave it
# alone for a while, since it is empty once used.
if echo and echo.repair:
nearer = memory.depot_x == None
if not nearer:
kx = abs(memory.depot_x - state.x)
ky = abs(memory.depot_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.depot_x = echo.x
memory.depot_y = echo.y
hurt = state.health < 50 and memory.depot_x != None
if memory.skip_until != None and state.time < memory.skip_until:
hurt = False
# Mid-repair: stay put until it is done.
if memory.mend_until != None and state.time < memory.mend_until:
hurt = True
if hurt:
kx = memory.depot_x - state.x
ky = memory.depot_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
hull.repair()
if memory.mend_until == None or state.time > memory.mend_until:
memory.mend_until = state.time + 4.5
memory.skip_until = state.time + 25
else:
course = bearing_to(state.x, state.y, memory.depot_x, memory.depot_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)
# Recharge. Energy comes back only at a charging point, and a scan's
# echo can find one: keep the nearest found. Running low, go to it,
# stop on it and charge until the tank is full.
if echo and echo.charger:
nearer = memory.charger_x == None
if not nearer:
kx = abs(memory.charger_x - state.x)
ky = abs(memory.charger_y - state.y)
nearer = echo.range < max(kx, ky) + 0.4 * min(kx, ky)
if nearer:
memory.charger_x = echo.x
memory.charger_y = echo.y
if state.energy < state.energy_max * 0.3:
memory.charging = True
elif state.energy > state.energy_max * 0.99:
memory.charging = False
# Getting low and none found yet: sweep the radar round for one.
if memory.charger_x == None and state.energy < state.energy_max * 0.6:
radar.scan(wrap(state.time * 250))
if memory.charging and memory.charger_x != None:
kx = memory.charger_x - state.x
ky = memory.charger_y - state.y
near = kx * kx + ky * ky
if near < 5 * 5:
# There (it reaches 6 m): stop, and once stopped, charge.
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
drive.charge()
else:
course = bearing_to(state.x, state.y, memory.charger_x, memory.charger_y)
turn = clamp(angle_diff(course, state.heading) / 30, -1, 1)
throttle = 0.7
if near < 30 * 30:
throttle = 0.35
drive.set(throttle=throttle, turn=turn)