Quickstart¶
A first race¶
Open a map, start a race, hold the accelerator for two seconds and look at the result.
from tickgym import Client, TelemetryGroup
# TICK on this machine opens the map as a session
with Client().open_map(r"C:\Maps\Example.Map.Gbx") as session:
# One connection is one gym. Ask for the telemetry groups you will read.
with session.connect(TelemetryGroup.RACE | TelemetryGroup.VEHICLE) as gym:
# The countdown is played; the first sample is at race tick -1.
t = gym.reset(seed=1)
# 200 ticks is two seconds. An input holds from the first tick of the step onwards.
t = gym.step(200, accelerate=True)
print(t.race.race_time_ms, t.vehicle.speed, t.vehicle.position)
A training loop steps a few ticks at a time and changes the inputs between steps; the next section does that.
Every step answers a Telemetry with the groups you chose as
attributes: t.race, t.vehicle and so on, each a typed view. See Telemetry for every field.
Steering and the control loop¶
Steering is an integer from -127 (full left) to 127 (full right). An input you do not pass keeps its value, as in the game.
def policy(t):
# Decide the steering from the telemetry: t.vehicle, t.wheels, t.lidar, ...
# This one drives straight.
return 0
t = gym.reset(seed=1)
while not t.finished:
t = gym.step(3, steer=policy(t), accelerate=True)
print("finished in", t.race.race_time_ms, "ms")
t.finished is true once the race is over, whatever groups were chosen. A finished gym answers its final state and
ignores inputs until a reset or a restore.
Snapshots¶
A snapshot saves the gym’s state on the server at the current race tick. Restoring it
puts the gym back there; the next step continues from the tick after.
Restore and step in one round trip, by passing the snapshot as restore=:
t = gym.step(300, accelerate=True)
corner = gym.snapshot()
# Try the corner twice from the same state.
left = gym.step(100, steer=-60, restore=corner)
right = gym.step(100, steer=60, restore=corner)
Or restore on its own, which answers the state at the snapshot:
t = gym.restore(corner)
t = gym.step(100, steer=-60)
Inputs you do not pass keep the values they had when the snapshot was taken. A gym holds a limited number of
snapshots; discard frees them, and a reset frees them all.
Exact inputs¶
Explicit Action records put an input at a chosen tick inside the step. The step’s window
starts at gym.next_tick.
from tickgym import Action
# Steer at the window's first tick, brake five ticks later.
t = gym.step(10, [Action.steer(gym.next_tick, -40), Action.brake(gym.next_tick + 5)])
A record outside the window is refused with BadActionError, and a refused step changes
nothing.
Saving and replaying a race¶
export gives the finish time and every input since the reset, the seed first.
InputText.write saves them as TICK input text, which TICK’s editor loads.
from tickgym import InputText
record = gym.export()
InputText.write("records/best.txt", record.actions)
InputText.read loads such a file. A record plays as it is: reset, then step with the
record. Its seed record starts the race with that seed, and the inputs follow at their ticks. The same record gives the
same race, in this gym or any other on the map.
record = InputText.read("records/best.txt")
t = gym.reset()
# One step whose window covers the whole record: up to the last input's tick.
t = gym.step(max(action.tick for action in record) + 1, record)
print(t.race.race_tick, t.vehicle.position)
This leaves the gym right after the last input, whether or not the race is finished; step on from there as you like.
Many gyms¶
Each Session.connect is an independent gym. Open one per thread or process; the same
seed and inputs give the same telemetry on every one of them.
When something fails¶
A request that fails or times out (60 seconds by default) closes the gym and raises TransportError, because whether it ran is then unknown. Open a new gym and reset; stepping the exported
record. Every error pickles, so a worker’s failure crosses a process pool intact. See Errors.