Program implements a "bug" which can be "driven" using arrow keys
around a circular track with a number of gates. The objective is
to navigate the course in the minimum time and/or distance. .
Background & Techniques
The program evolved from a prototype vehicle simulation program written
at the request of a viewer. I had added a second overlapping circle to
the original circular vehicle in order to investigate "pointing" the vehicle
in the direction of travel. The result looked like a bug, so I
decided to make an obstacle course to practice driving the thing. Bug
Time Trials is the result.
The bug is most easily controlled by using arrow keys. Up and down
arrows control speed; left and right arrows control the turning angle.
||"Up" increases positive velocities (bug moving forward) or reduces negative
velocities (bug moving backwards) .
||"Down" decreases positive velocities or
increases negative velocities.
|| "Right" increases the
angle at which the bug is turning in a clockwise direction, up to 90°
(as if the bug were an automobile rotating about it's rear wheels with it's
front wheels turned 90° to the right.) .
increases the angle of turning in the counterclockwise direction.
the number keypad is used, the "5" key (in the middle of the 4 arrow keys)
can be used to reset the turn angle back to zero and improves
maneuverability at higher speeds. For keyboards without a
keypad, the "Z" key can be pressed with same "stop turning" effect.
After you click the Start button, the required course blinks a few
times then the clock starts. The clock stops when the bug touches the
finish line after passing through the four 4 gates positioned at 9, 12, 3,
and 6 o'clock positions.
Non-programmers are welcome to read on, but may
want to skip to the bottom of this page to download
executable version of the program.
The three parts of the program that were the most challenging (and the
most fun) were figuring how to control the vehicle, collision detection, and
rescaling when the form is resized.
The "bug" is built from two overlapping circles
(TShape controls) with the head 1/2 the size of the body and
positioned so that 2/3 of the head is exposed in whatever direction the bug
is pointing at the moment. Bug movement is controlled by a loop that
runs about 100 times per second. "Sleep(10)" pauses 10 milliseconds
at the start of each pass through the loop. .
An "OnKeyDown" exit sets the appropriate increment to +1 or - 1 and "OnKeyUp"
resets the increment back to 0. So while the key is depressed, the
speed or angle is being changed at the rate of about 100 steps per second.
Since the range of changes is -100 to +100 pixels per second for velocity
and -90 degrees to +90 degrees for turn angle, it takes about 1 second
to from 0 to either of the extremes.
The geometry to calculate the angle to turn each time
through the loop is a bit tricky and needs a diagram to do it justice.
The calculation code is:
I'm not good at diagrams so here it is in words.
If you really need it and get stuck, use feedback to send me a note and I'll
spend more time on it.
Using the distance traveled by the bug is a
good estimate of the arc length when the vehicle is turning and for small
turning angles. The idea is that the front of the vehicle moves at an
angle controlled by the turning angle relative to the angle of the vehicle
(or bug). While that is going on the rear of the vehicle (or tail of the
bug) is moving approximately up the line toward where the nose was when the
move started. If we draw the triangle N1,N2,T where N1 is the starting
position of the bug's nose, N2 is the ending position of the nose and T is
the Tail position of the bug at the end of the move, then
N1N2 is the arc of
length "dd", the distance traveled during the current time interval,
radius = line TN2, the length of the bug,
angle TN1N2 is anglebar.position (the angle of the wheels, call it
angle N1TN2 is the desired "dtheta" change in
the heading of the bug.
By the law of sines from trigonometry, sin(alpha)/radius = sin(dtheta)/dd
or sin(dtheta) = sin(alpha)*dd/radius. Since dtheta is a very
small angle, dtheta and sin(dtheta) are nearly equal so we can
use dtheta = sin(alpha)*dd/radius. The minus sign in the equation above reflects
the fact that right turns are negative angles but are represented
in the anglebar slider as positive values.
is considerably simpler; it just takes more code than one might think before
they have done it once. After position change of the bug has been calculated, we'll check the distance of the body and of the head to
each of the things it might collide with (the 10 "gate posts" and the edges
o0f the form in this case. The gate posts and the bug and head and
body are all circular Tshapes and are copied to a "Shapes" array
at FormCreate time. Thus lets us use a loop to check for
collisions. When found, I decided to just beep and reverse the
direction of the bug. Oh, and, as I found out the hard way,
undo the position change calculations made.
Another area that has lots of steps, none very complex. The
OnResize exit is called after the dimensions have been changed. I save
the dimensions before the resize and calculate scale factors for the
change in the x and y directions at resize time. The smaller one is
used to ensure that the course still fits the form after scaling. Then
again the Shapes array is used to calculate new
top, left, width, and height values for the bug, and the gate posts. Then we
also do some juggling of the label and countdown timer positions.
- There are lines calculated across each of the gates
(only the finish line is drawn). As the bug moves we check off
each gate passed through so the driver cannot cheat by skipping a
gate and so we can tell when the bug reaches the finish line.
countdown timer class is used to display the run time.
- The UGeometry unit provides the "LinesIntersect"
function used here to determine when the bug crosses through a
gate. UGeometry is contained in our
DFF library zip file
(DFFLIBV04 or later) which must be downloaded one time if you plan to
recompile the program.
I've been doing this for more than 40 years and it still is mildly
surprising how many actual operations are involved in a seemingly simple
project such as this one. About 500 lines worth in this
May 24, 2014: It's been a number of years since I looked at
this program but conversations with a viewer about robot control recently led me
to refresh this to Version 2. The main change is allowing the "bug"
to be resized smaller to make passing through the gates a little easier. However
the default full size bug now has less gate clearance.
My new best times are 19 seconds for the the "Easy" level, high 20's for the
"Harder level, and low 30's for the "Expert" level. The bug now
reverses slowly in a straight line after a gate collision instead of reversing
at the collision vehicle speed and turn angle. Hitting a gate post is
still guaranteed to add several seconds to your lap time! The
best technique I've found uses the arrow keys with right hand for bug control
and the "Z" key with the right hand to straighten out and zip through the gates
when the bug is lined up.
Running/Exploring the Program
Suggestions for Further Explorations
||Add flexible obstacle course support by saving
and loading course description files.
||Save "high scores"
||Use average speed through the course as a
third performance measure.
Original Date: September 28, 2006
February 18, 2016