Racing Overview
Up to eight karts line up on a grid just behind the start line and race a fixed number of laps around a closed track, finishing on the same line. Every tick your algo returns steering, throttle and brake, and a bicycle model with tyre saturation turns those into motion. There is no collision-free guarantee: karts can touch each other and the walls, and contact costs you speed.
Your algo does not get a map of the track. It gets a ray-cast view of its immediate surroundings — walls and other karts — expressed in your own kart’s frame. See the game state page for the full shape of that data.
Each lap has a time limit of 60 seconds. When a lap is due, 60 seconds after the one before it, every kart that has not completed it is disqualified and taken off the track, and its algo is told so on its console. A disqualified kart is placed behind every kart still racing. A race ends when every kart has finished or been disqualified, which is 120 seconds in at the latest.
A kart tops out at about 47 m/s on a long enough straight, and more throttle always gets it going faster. Close behind another kart it has less drag, because the kart ahead drags the air along with it: about 30% less a kart length back, 10% five lengths back, and next to none past ten, so in a tow it tops out at about 50 m/s. Floored from a standstill, its rear wheel spins for about two seconds before the tyre grips, which car.engine_rpm shows. The track’s longest straight is 75 m, enough to take a kart from a standstill to about 27 m/s.
Details
| Time per move | One tick is 1/30 of a second of simulated time, and the engine paces the race in real time. Late commands are not applied - the kart keeps its previous control inputs until you send new ones. |
| Ranking system | Each ranked version has a strength, worked out from the finishing order of every ranked race ever run. Karts that finish are placed in the order they cross the line, and your place is settled the moment you do. Karts that don't finish come after them, by distance covered. The gap between two strengths is the odds of one finishing ahead of the other, whoever else is in the race: equal strengths are even, a gap of 1 is about 73% to the stronger, and a gap of 2 about 88%. Zero is the average of the versions the leaderboard was last updated for: those set to Ranked at the time that had run a ranked race. So a strength above zero is better than the average of them. The leaderboard is ordered by strength, and its Uncertainty column says how far off a strength may still be; it shrinks as the version races. A newly ranked version is in trials until its uncertainty is 0.5 or less: its ranked races are run and you can see its numbers under My Algos, but it has no place on the leaderboard yet. That takes about five close races on a full grid of eight, more on a smaller grid, and more if they are one-sided. A ranked race seats as many versions as there are, up to eight, and each grid goes to the versions a race would teach the most about: ones of similar strength, some of them still uncertain. So a new version races a lot at first, against karts closer and closer to its own strength. Where each kart starts on the grid is chosen at random for each race. A version's code never changes, so every ranked race it has ever run counts, even if you take it out of ranking and put it back. |
setup(track) |
track is a Track describing the circuit: its name, half width, lap length, centerline, start position and start heading. |
| Valid moves | RacingInterface.set_controls(throttle=None, brake=None, steering=None). throttle and brake run 0 to 1, steering runs -1 to 1. Values outside those ranges are clamped, and a control you leave out keeps the value it had. |
Sample Algo
This is the starter algo the local app fills a seat with, so it is the same code you can run and edit.
import math
from algogames import RacingInterface
my_track = None
def setup(track):
global my_track
my_track = track
def on_update(state):
front = _ray_distance(state.surroundings, 0.0)
left = _ray_distance(state.surroundings, math.radians(55))
right = _ray_distance(state.surroundings, math.radians(-55))
best_angle = _best_open_angle(state.surroundings)
balance_angle = math.atan2(left - right, max(front, 1.0)) * 0.8
target_angle = _clamp(best_angle * 0.7 + balance_angle * 0.3, -1.0, 1.0)
speed = max(0.0, state.car.velocity[0])
target_speed = _clamp(front * 0.45, 3.0, 8.0)
RacingInterface.set_controls(
throttle=_clamp((target_speed - speed) * 0.35, 0.0, 0.65),
brake=_clamp((speed - target_speed) * 0.3, 0.0, 0.45),
steering=_clamp(-target_angle * 1.8 - state.car.yaw_rate * 0.35, -1.0, 1.0),
)
def _best_open_angle(surroundings):
best_angle = 0.0
best_score = -float('inf')
for degrees in range(-85, 86, 5):
angle = math.radians(degrees)
distance = _ray_distance(surroundings, angle)
forward_bias = math.cos(angle) * 4.0
turn_penalty = abs(angle) * 1.2
score = distance + forward_bias - turn_penalty
if score > best_score:
best_score = score
best_angle = angle
return best_angle
def _ray_distance(surroundings, angle):
direction = (math.cos(angle), math.sin(angle))
best = float('inf')
for entity in surroundings:
hit = _ray_segment_distance(direction, entity.end1, entity.end2)
if hit is not None:
best = min(best, hit)
return best if math.isfinite(best) else 30.0
def _ray_segment_distance(direction, start, end):
sx, sy = start
ex, ey = end
vx = ex - sx
vy = ey - sy
denominator = direction[0] * vy - direction[1] * vx
if abs(denominator) < 1e-9:
return None
distance = (sx * vy - sy * vx) / denominator
segment_t = (sx * direction[1] - sy * direction[0]) / denominator
if distance < 0.0 or segment_t < 0.0 or segment_t > 1.0:
return None
return distance
def _clamp(value, low, high):
return max(low, min(high, value))