Missiles, energy and repairs
A launcher holds only a few missiles, energy only comes back at a charging point, and a damaged hull is only patched at a repair depot. A vehicle that can find and use them keeps fighting long after one that can't.
What's in the arena
| Supply crates | 2 in each arena. stopped within 8 m of a stocked crate, for 2 s. A crate restocks after 20 s. |
|---|---|
| Charging points | 4 in each arena. 80 energy / s, stopped within 6 m of one. |
| Repair depots | 2 in each arena. 25 % of full health over 4 s, stopped within 6 m of a stocked depot. A depot restocks after 30 s. |
Your program can't see them in state. It finds them with the radar.
Finding things with the echo
Every scan reports what it ran into along its bearing, apart from the opponent: radar.echo(). An echo has x and y, and says what it was: wall, supply, charger or repair (see the echo record). It comes free with every scan, so a sweeping radar maps the arena as it goes. Keep the nearest of each kind in memory:
echo = radar.echo()
if echo and echo.supply:
if memory.crate_x == None or echo.range < memory.crate_range:
memory.crate_x = echo.x
memory.crate_y = echo.y
memory.crate_range = echo.range(memory.crate_range is the range when it was seen; the full program below compares with how far away the remembered crate is now.)
Getting there, and stopping
Crates, charging points and depots all work the same way: drive to it, stop within reach, then ask. A vehicle still rolling doesn't count as stopped, so ease off as you arrive and wait until state.speed is nearly nothing.
dx = memory.crate_x - state.x
dy = memory.crate_y - state.y
near = dx * dx + dy * dy
if near < 5 * 5:
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
missiles.reload()
else:
course = bearing_to(state.x, state.y, memory.crate_x, memory.crate_y)
turn = clamp(angle_diff(course, state.heading) / 45, -1, 1)
throttle = 1
if near < 30 * 30:
throttle = 0.4
drive.set(throttle=throttle, turn=turn)Comparing squared distances (dx * dx + dy * dy against 5 * 5) avoids needing a square root, which BCAS doesn't have. The same pattern works for charging, with drive.charge(), and repairs, with hull.repair().
When to go
- Missiles: when
missiles.ammo()is 0. Going with a few left wastes the trip. - Energy: well before empty. Going at 30% of
state.energy_maxleaves enough to get there, even shooting on the way. A vehicle that runs the tank dry loses power: it can't drive, scan, shield or fire until it has a reserve again, and only charging gives it that. - Repairs: when badly hurt and the opponent isn't close. Repairing takes several seconds standing still.
The whole program
It fights as the earlier tutorials do, maps the arena with every scan, and goes for missiles, energy or repairs when it needs them. The trips come last, so they win over fighting.
from vehicle import drive, radar, missiles, shield, hull
def update(state, memory):
if memory.sweep == None:
memory.sweep = 0
course = state.heading
throttle = 0.5
shield.set_level(0)
contact = radar.contact()
if contact and state.time - contact.seen_at <= 0.2:
radar.scan(contact.bearing)
missiles.aim(contact.bearing)
course = contact.bearing + 35
throttle = 1
if contact.range < 200 and state.energy > 30:
shield.set_level(0.5)
on_target = abs(angle_diff(contact.bearing, missiles.turret())) <= 3
if on_target and contact.range < 170 and missiles.ready():
missiles.fire()
else:
memory.sweep = wrap(memory.sweep + 15)
radar.scan(memory.sweep)
turn = clamp(angle_diff(course, state.heading) / 45, -1, 1)
drive.set(throttle=throttle, turn=turn)
# Map the arena: every scan's echo, the nearest of each kind kept.
echo = radar.echo()
if echo and echo.supply:
memory.crate_x = echo.x
memory.crate_y = echo.y
if echo and echo.charger:
memory.charger_x = echo.x
memory.charger_y = echo.y
if echo and echo.repair:
memory.depot_x = echo.x
memory.depot_y = echo.y
# What it needs most, if anything: energy, then missiles, then repairs.
goal = None
if state.energy < state.energy_max * 0.3 and memory.charger_x != None:
goal = "charge"
gx = memory.charger_x
gy = memory.charger_y
elif missiles.ammo() == 0 and memory.crate_x != None:
goal = "reload"
gx = memory.crate_x
gy = memory.crate_y
elif state.health < 40 and memory.depot_x != None and not contact:
goal = "repair"
gx = memory.depot_x
gy = memory.depot_y
if goal != None:
shield.set_level(0)
dx = gx - state.x
dy = gy - state.y
near = dx * dx + dy * dy
if near < 5 * 5:
drive.set(throttle=0, turn=0)
if abs(state.speed) < 1:
if goal == "charge":
drive.charge()
elif goal == "reload":
missiles.reload()
else:
hull.repair()
else:
course = bearing_to(state.x, state.y, gx, gy)
turn = clamp(angle_diff(course, state.heading) / 45, -1, 1)
throttle = 1
if near < 30 * 30:
throttle = 0.4
drive.set(throttle=throttle, turn=turn)Two simplifications to improve on: it remembers the last of each kind it saw rather than the nearest, and it stays on an empty crate for ever. The Starter's program does both properly.