Neon Lab prototype
TankScript Arena
Test
Code
tactic libraryCoding Guide
fake Python-ish DSLTankScript is a deliberately small, fake Python-ish language. It is interpreted in the browser for the arena only. It is not real Python, cannot import packages, cannot touch files, and cannot run server code.
Basic shape
enemy = optics.scan()
if enemy:
aim(enemy["gun_angle"] / 45)
fire()
else:
turn(0.25)
move(0.45)
Indent with four spaces. Use if and else. The interpreter runs your script every simulation tick and converts it into one move, one hull turn, one turret aim, and an optional fire command.
Functions, repeats, and cleaner decisions
Use helper functions to name behavior. Functions are macro-like helpers expanded before each tick; they can take up to four simple arguments and do not return values. Keep names short and descriptive.
def keep_range(target, wanted):
if target["distance"] < wanted:
move(-0.45)
elif target["distance"] > wanted + 70:
turn(target["angle"] / 90)
move(0.60)
else:
move(0.20)
enemy = optics.scan()
if enemy:
keep_range(enemy, 180)
repeat N: repeats a block, capped at 20 repeats per block. Expanded scripts are capped too, so deeply nested repeats fail safely instead of freezing the browser.
def sweep():
repeat 3:
aim(0.25)
sweep()
Use elif, and, or, and not to avoid giant nested blocks. Conditions can compare expressions to expressions, not just literal numbers.
target_range = 180
if enemy and enemy["distance"] < target_range:
move(-0.5)
elif enemy and self.reload == 0:
fire()
elif not enemy and contacts.count > 0:
turn(closest["bearing"] / 90)
else:
move(0.4)
Math helpers, variables, and memory
Normal variables are numeric and persist between ticks. This is useful for modes, timers, sweep direction, and short-term plans.
if sweep == 0:
sweep = 1
if gun_wall_close:
sweep = 0 - sweep
aim(sweep * 0.35)
Available math helpers: abs(x), min(a,b), max(a,b), clamp(x,a,b), and sign(x).
if enemy:
aim(clamp(enemy["gun_angle"] / 45, -1, 1))
turn(sign(enemy["angle"]) * 0.35)
if abs(enemy["gun_angle"]) < 8:
fire()
Use explicit memory helpers when you want the script to read like a state machine. Memory values are numeric and persist for that tank during the run.
mode = memory.get("mode", 0)
if enemy:
memory.set("mode", 1)
elif mode == 1:
turn(0.50)
move(0.35)
Self fields
self exposes read-only tank state. Useful fields: self.hp, self.max_hp, self.reload, self.x, self.y, self.heading, self.turret_heading, self.speed, self.blocked, and self.loop.
if self.hp < self.max_hp * 0.35 and enemy:
turn(enemy["angle"] / -90)
move(0.85)
elif enemy and self.reload == 0:
aim(enemy["gun_angle"] / 45)
fire()
Debugging helpers
say("text") shows a short speech/status message above the tank and writes to the console. debug(value) writes a numeric value to the console. Both are rate-limited so a script cannot spam the page.
if self.blocked:
say("stuck")
debug(wall_ahead_distance)
Commands
move(-1..1)drives backward to forward.turn(-1..1)rotates the hull left/right.aim(-1..1)rotates the turret left/right.fire()fires the selected weapon if the turret is lined up, target is in range, reload is ready, and walls do not block the shot.
Optics, radar, sound, and contact memory
enemy = optics.scan()sees an enemy in a forward cone. Optics has longer range but walls block it.gun = gun.scan()checks the narrow turret cone and is useful beforefire().contacts = radar.scan()works only with advanced radar. Radar sees through cover, but at shorter range and with rougher data.noise = sound.heard()hears fake weapon noise through walls for a short time.last = memory.last_seen()returns the tank's automatic last-known enemy position.
Contact memory is automatic. Visual/gun contact is strongest, radar is medium confidence, and heard shots are weak. Fresh visual or radar memory is not overwritten by a random sound. Old memory fades and disappears after about 300 ticks.
enemy = optics.scan()
contacts = radar.scan()
noise = sound.heard()
last = memory.last_seen()
if enemy:
aim(enemy["gun_angle"] / 45)
fire()
elif contacts:
turn(contacts[0]["bearing"] / 90)
aim(contacts[0]["gun_bearing"] / 90)
elif last and last["confidence"] > 0.35:
turn(last["bearing"] / 90)
aim(last["gun_bearing"] / 90)
if last["age"] < 120:
move(0.45)
else:
move(0.20)
elif noise:
turn(noise["bearing"] / 90)
aim(noise["gun_bearing"] / 90)
Useful fields
enemy["angle"]points the hull toward the visible enemy.enemy["gun_angle"]points the turret toward the visible enemy.enemy["distance"]is distance in arena units.contacts[0]["bearing"],contacts[0]["gun_bearing"],contacts[0]["distance"]are radar contact fields.noise["bearing"],noise["gun_bearing"],noise["distance"],noise["age"]describe a heard shot.last["x"],last["y"],last["age"],last["distance"],last["bearing"],last["gun_bearing"],last["source"], andlast["confidence"]describe the remembered contact.
Closer or further away
Most sensor hits also report whether the distance changed since the last tick. Negative delta means closer. Positive delta means further away.
enemy["closing"]/enemy["opening"]work for visual optics.contacts[0]["closing"]/contacts[0]["opening"]work for radar pings.noise["closing"]/noise["opening"]work for heard shots.last_seen["closing"]/last_seen["opening"]estimate movement toward the last remembered position.enemy["distance_delta"],contacts[0]["distance_delta"], andnoise["distance_delta"]expose the raw distance change.
enemy = optics.scan()
contacts = radar.scan()
noise = sound.heard()
if enemy:
aim(enemy["gun_angle"] / 45)
fire()
if enemy["closing"]:
move(-0.45)
else:
if enemy["opening"]:
turn(enemy["angle"] / 90)
move(0.55)
else:
if contacts:
turn(contacts[0]["bearing"] / 90)
if contacts[0]["closing"]:
move(-0.25)
else:
if noise:
aim(noise["gun_bearing"] / 90)
if noise["opening"]:
move(0.65)
Walls and movement
wall_aheadandwall_close_aheadwarn about obstacles in front.wall_left/wall_righthelp simple wall-following.wall_ahead_distance,wall_left_distance, andwall_right_distancegive the measured wall distance.wall = wall.scan()finds the nearest visible wall direction. Usewall["angle"],wall["distance"], andwall["side"].gun_wall_distancemeasures how far the current turret/cannon sight can see before hitting a wall.gun_wall_closeis true when that sightline is short.wall_ahead_closing/wall_ahead_openingtell whether the front wall is getting closer or further away.self.blockedis true after the tank tried to move into a wall, edge, or another tank.
if wall_ahead_closing:
turn(0.65)
move(0.20)
else:
if wall_ahead_distance < 90:
turn(-0.75)
else:
move(0.60)
Variables and random behavior
You can store simple numeric variables and use random(min, max) so tanks do not drive in perfectly predictable lines.
wiggle = random(-0.4, 0.4)
turn(wiggle)
move(0.55)
Range bands
Good tactics usually avoid both extremes: do not just stand still and shoot, but also do not ram the enemy. Hold a useful weapon range, reverse when too close, and use turret aim independently from hull movement.
if enemy:
aim(enemy["gun_angle"] / 45)
fire()
if enemy["distance"] > 230:
turn(enemy["angle"] / 90)
move(0.60)
else:
if enemy["distance"] < 165:
move(-0.55)
else:
turn(random(0.35, 0.85))
move(0.18)
Safety limits
Unavailable on purpose: import, eval, exec, open, __, global, lambda, infinite loops, real file/network access, and repair/healing. Scripts are capped at 240 lines.
Console
[ready] Create a tank, save code, then run a challenge.
