This racing driver is written in Forth (racer.fs), the language of
choice for a lone hacker (that's the reputation it already has). I use
my own implementation, bigFORTH, which is available from
bigforth.sf.net (for IA32 Linux, and as never-finished beta version
also for Win32). To satisfy that reputation, I did the whole contest
alone, with a few comments from Albert van der Horst.

After implementing the fixed point math and the simulation step, I
decided to also implement a visualization (template in racer.m, actual
use of the turtle graphics in racer.fs). Having speeds and indication
to hit the goal is sufficient to see if your path works, but for sure
not to check if it is good.

For this task, I choose MINOS, which provides an advanced turtle
graphics (I use only the x,y drawing tool and the icon display
tool). I wrote a small program that converts a track into a MINOS icon
(a PPM followed by a PBM mask) so that the track can be displayed. The
car trace itself is drawn in yellow, and red when off-road (actually,
the simulator catches crashes, so red will never happen).

I wrote a simulator for the file format used, but I don't use it
internally. You call the simulator with

race1 s" 1_Simple.trc" run-file

and check how many steps it took with

step# ?

The next task is the internal representation of the track: I choose a
simple 2D array with three values: 0 for road, 2 for goal, 1 for the
rest. A second array then is computed that contains nearly-euclidean
distance to the goal (starting from the left side of the goal, with
the goal itself being impassable). I use the same flood-fill approach
I used in last year's contest for the robot, modified to
pseudo-eclidean coordinates (diagonals are weighted sqrt(2), and there
are three arrays for updating; this flood fills the field with an
octagonal growing rule).

To evaluate how good a path is, I check how many steps it took, and
how far it is still to the goal. Steps weight more than distance to
goal (since there are several steps per pixel necessary). Pathes that
actually hit the goal get a favour. Generally, the lower the score,
the better.

For the actual path search, I use something that remotely resembles a
genetic algorithm. A "gene" is a command that contains a fractional
accelleration, a fractional turn, and deltas for these fractions, and
a number. The execution of such a command dithers accelleration and
turns. On the top node of each path, I "conventionally" search for a
good continuation by going 500 steps with a number of random path
commands (limits reasonable set). The steps number the best path
managed to drive before crashing (or ending on the road) is then
reduced by a random amount (at least to 60%) before appending it as
gene. Non-crashing paths and paths that hit the goal get preferrence.

There's a population (default 16, I used 32 and finally 64 for running
the actual game - the higher the population, the better. Twice the
population gives a better result than the best of two runs). After
each run, the population is sorted by score, and the lower half is
killed and replaced by mutations of the upper half. Since "good" paths
tend to duplicate, I also mutate them, so that there's only one unique
path in the population. There's no sexual interbreeding. The
population has to be as large as posible, since competition is global,
when the goal is reached. The path stabilize on the start first. The
populations are there to avoid being stuck in local minima.

Paths can mutate in a number of ways: First, parameters can change
randomly. This is restricted to the last few genes, since changes in
earlier ones will quite likely throw the path off road. Paths can also
mutate by reducing the last command length to half, and by dropping a
few genes, and mutating a bit further.

Due to the shorting of the path, it takes quite a while to have the
best path actually finish. Therefore, I added another step that uses
the best-path search, but doesn't reduce the path length at the end of
the game. This finalizes the path.

I also added skidding for the racing contest; I didn't invest a lot of
time to get it good, especially since it's only a tie braker. It does
reach the goal in the simulator, and it does use skidding (don't know
if it actually helps ;-).

This sort of heuristic random solution can in fact make use of an
infinite amount of computation, and still won't give an optimal
solution. I only have a single Athlon 600. Tuning the algorithm took
quite some time, and an important part of tuning was to get it solving
as many tracks as possible by itself (without adjustment of the
constants). I have some ideas to improve speed (e.g. caching the
result of the unchanged part of the path), but didn't implement these
ideas. As always, there have to be some compromises between meeting
the deadline and the quality of the implementation.

The simulation data takes more heap space than provided by default, so
you have to use the -m option to start xbigforth:

xbigforth -m 64M racer.fs

A race is played with the following command from the xbigforth dialog
prompt:

race1 init-genes 200 mutations

Hit Esc to stop (when you think that it's "good enoug"). Each race
(race1 to 9 and raceX) know where to save the result. You should give
a big enough number for the mutations, since this program stops after
this many rounds. If you want to increase the population, do

$20 to population#

right at the start (definitely before "init-genes").

The program crashes occasionally with an "Invalid Handle" message
short after startup, which may result for the heat today in Munich (it
didn't do that until noon, and then crashed at an unchanged
source). If it's a systematic problem, it may come from the MINOS icon
loading. In any case, just kill xbigforth, and start it anew.
