▚ BATTLE CODE ARENA // BCAS REFERENCE PLAY ▶

Your first program

A program is one function that the game calls over and over. Here is how that works, and a first program that finds its opponent, chases it and shoots, explained a part at a time.

How a program runs

Physics runs 30 ticks / s; your program runs 10 updates / s. Each time, the game calls update(state, memory):

Your program doesn't move the vehicle itself. It gives orders, through the libraries it imports: drive.set() to move, radar.scan() to look, missiles.fire() to shoot. The game carries them out until the next update.

Two kinds of order. Some last until you change them: drive.set(), missiles.aim(), shield.set_level(). Others happen once: radar.scan() and missiles.fire(). Give a one-off order again every update you want it.

The smallest program

Every program starts by importing the libraries it uses, then defines update. This one drives in a circle and nothing else:

circle.bcas
from vehicle import drive

def update(state, memory):
    drive.set(throttle=0.6, turn=0.3)

throttle runs from -1 (full reverse) to 1 (full ahead), and turn from -1 (left) to 1 (right). A library can be imported only if the vehicle carries the equipment for it: drive needs a drive unit. The equipment page says which is which.

Looking: the radar

The radar sees along one bearing at a time. radar.scan(bearing) asks for a scan; the answer comes on the next update, from radar.contact(). Bearings are degrees clockwise from north (up): 0 is up, 90 is right.

To find an opponent you haven't seen yet, sweep: scan a little further round each update. The built-in wrap() keeps an angle between 0 and 360.

A sweep
if memory.sweep == None:
    memory.sweep = 0

memory.sweep = wrap(memory.sweep + 15)
radar.scan(memory.sweep)

A field of memory that has never been set reads as None, which is how the program knows it's the first update.

Following what you've found

When a scan finds the opponent, radar.contact() returns a contact: its bearing, range, position, and seen_at, the time it was seen. A contact stays the latest answer until the next scan replaces it, so check how fresh it is. Scanning straight back at it every update keeps it in view:

Keeping it in view
contact = radar.contact()

if contact and state.time - contact.seen_at <= 0.2:
    radar.scan(contact.bearing)

Turning towards it

To steer onto a bearing, turn by how far off it is. angle_diff(target, current) gives the shortest turn, from -180 to 180; dividing by 45 and clamping to -1..1 turns hard when far off and gently when nearly there.

Steering onto a bearing
turn = clamp(angle_diff(contact.bearing, state.heading) / 45, -1, 1)
drive.set(throttle=0.8, turn=turn)

Shooting

The launcher sits on a turret. missiles.aim(bearing) turns it, which takes time; missiles.fire() launches along wherever it points now. So fire once the turret has come round, and when the launcher is ready: missiles.ready() says it's loaded, has missiles, and there's the energy for a shot.

Aim, then fire
missiles.aim(contact.bearing)
on_target = abs(angle_diff(contact.bearing, missiles.turret())) <= 3

if on_target and missiles.ready():
    missiles.fire()

All together

first.bcas
from vehicle import drive, radar, missiles, debug

def update(state, memory):
    if memory.sweep == None:
        memory.sweep = 0

    contact = radar.contact()

    if contact and state.time - contact.seen_at <= 0.2:
        # Found: keep the radar on it, close in, aim and fire.
        radar.scan(contact.bearing)
        turn = clamp(angle_diff(contact.bearing, state.heading) / 45, -1, 1)
        drive.set(throttle=0.8, turn=turn)
        missiles.aim(contact.bearing)
        on_target = abs(angle_diff(contact.bearing, missiles.turret())) <= 3

        if on_target and missiles.ready():
            missiles.fire()
            debug.log("Fired")
    else:
        # Lost, or not found yet: sweep the radar round and keep moving.
        memory.sweep = wrap(memory.sweep + 15)
        radar.scan(memory.sweep)
        drive.set(throttle=0.5, turn=0.2)

debug.log() writes to the battle's timeline, so in the replay you can see when and why your program did something. A program may log up to 4 messages an update.

Try it

  1. Open Battle Code Arena and go to PROGRAM.
  2. Paste the program into the editor. It checks as you type: the bar under the editor says OK · READY TO FIGHT or what's wrong and where.
  3. Press ▶ RUN to fight a training opponent, and watch the replay.

It will miss a lot against anything that moves. The next tutorial fixes that.