7.2 Collecting Data About Opponents Continued

The opponents.cpp File

Here we discuss the concrete implementation of the classes defined in opponent.h. Put all this code into opponent.cpp in the bt directory. We start with the header and the definition of some constants.

/***************************************************************************

    file                 : opponent.cpp
    created              : Thu Apr 22 01:20:19 CET 2003
    copyright            : (C) 2003 Bernhard Wymann

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "opponent.h"

/* class variables and constants */
tTrack* Opponent::track;
float Opponent::FRONTCOLLDIST = 200.0;  /* [m] distance to check for other cars */
float Opponent::BACKCOLLDIST = 50.0;    /* [m] distance to check for other cars */
float Opponent::LENGTH_MARGIN = 2.0;    /* [m] safety margin */
float Opponent::SIDE_MARGIN = 1.0;      /* [m] safety margin */

To avoid collisions it is not necessary to take into account cars that are farther away than a certain distance. With FRONTCOLLDIST and BACKCOLLDIST you can set the range you want to check for opponents in front of your car and behind your car. The safety margins define the minimal distance you would like to maintain to opponents. If you would like to be able to change these parameters without recompilation, you could also put them into the setup XML file and load them at startup.

Opponent::Opponent()
{
}

/* compute speed component parallel to the track */
float Opponent::getSpeed(tCarElt *car)
{
    v2d speed, dir;
    float trackangle = RtTrackSideTgAngleL(&(car->_trkPos));

    speed.x = car->_speed_X;
    speed.y = car->_speed_Y;
    dir.x = cos(trackangle);
    dir.y = sin(trackangle);
    return speed*dir;
}

The Opponent::getSpeed(tCarElt *car) method computes the speed of the car in the direction of the track. First we get the direction of the track at the cars location. After that we compose the speed vector of the car. We use _speed_X and _speed_Y here, the uppercase X and Y means that the speed vector is in global coordinates, the lowercase x and y are the speeds in the "instantaneously coincident frame of reference", a coordinate system that is oriented like the cars body but doesn't move with the car (if it would move with the car the _speed_x and _speed_y would always be zero). From the trackangle we compute a direction vector of the track (its length is one). Finally we compute the dot product of the speed and the direction, which will give us the speed in the direction of the track.

/* Compute the length to the start of the segment */
float Opponent::getDistToSegStart()
{
    if (car->_trkPos.seg->type == TR_STR) {
        return car->_trkPos.toStart;
    } else {
        return car->_trkPos.toStart*car->_trkPos.seg->radius;
    }
}

This method computes the distance of the opponent to the segments start. The reason is that car->_trkPos.toStart contains the length of the segment for straight segments and for turns the arc, so we need a conversion to the length.

Now we will have a look at the update function. It is responsible for the update of the values in the Opponent instances.

/* Update the values in Opponent this */
void Opponent::update(tSituation *s, Driver *driver)
{
    tCarElt *mycar = driver->getCarPtr();

    /* init state of opponent to ignore */
    state = OPP_IGNORE;

    /* if the car is out of the simulation ignore it */
    if (car->_state & RM_CAR_STATE_NO_SIMU) {
        return;
    }

First we get a pointer to the drivers car, set the initial state to ignore the opponent and if the opponent is not longer part of the simulation we return.

    /* updating distance along the middle */
    float oppToStart = car->_trkPos.seg->lgfromstart + getDistToSegStart();
    distance = oppToStart - mycar->_distFromStartLine;
    if (distance > track->length/2.0) {
        distance -= track->length;
    } else if (distance < -track->length/2.0) {
        distance += track->length;
    }

Now we compute the distance of the center of the drivers car to the center of the opponents car. We achieve that by computing the distances to the start line and taking the difference. If you think about that you will recognize that this is just an approximation of the real distance. If you want a more accurate value you have to compute it with the cars corners (look up car.h). The if part is to "normalize" the distance. From our position up to a half track length the opponent is in front of us (distance is positive), else it is behind (distance is negative). A detail is that we can't use _distFromStartLine of the opponent, because it is a private field.

    /* update speed in track direction */
    speed = Opponent::getSpeed(car);
    float cosa = speed/sqrt(car->_speed_X*car->_speed_X + car->_speed_Y*car->_speed_Y);
    float alpha = acos(cosa);
    width = car->_dimension_x*sin(alpha) + car->_dimension_y*cosa;
    float SIDECOLLDIST = MIN(car->_dimension_x, mycar->_dimension_x);

We update the speed with the previously introduced getSpeed() method. Then we compute the width of the opponents car on the track (think the car is turned 90 degrees on the track, then the needed "width" is its length).

    /* is opponent in relevant range -50..200 m */
    if (distance > -BACKCOLLDIST && distance < FRONTCOLLDIST) {
        /* is opponent in front and slower */
        if (distance > SIDECOLLDIST && speed < driver->getSpeed()) {
            catchdist = driver->getSpeed()*distance/(driver->getSpeed() - speed);
            state |= OPP_FRONT;
            distance -= MAX(car->_dimension_x, mycar->_dimension_x);
            distance -= LENGTH_MARGIN;
            float cardist = car->_trkPos.toMiddle - mycar->_trkPos.toMiddle;
            sidedist = cardist;
            cardist = fabs(cardist) - fabs(width/2.0) - mycar->_dimension_y/2.0;
            if (cardist < SIDE_MARGIN) state |= OPP_COLL;
        } else

Here we check if the opponent is in the range we defined as relevant. After that the classification of the opponent starts. In this part we check if it is in front of us and slower. If that is the case we compute the distance we need to drive to catch the opponent (catchdist, we assume the speeds are constant) and set the opponents flag OPP_FRONT. Because the "distance" contains the value of the cars centers we need to subtract a car length and the safety margin. At the end we check if we could collide with to opponent. If yes, we set the flag OPP_COLL.

        /* is opponent behind and faster */
        if (distance < -SIDECOLLDIST && speed > driver->getSpeed()) {
            catchdist = driver->getSpeed()*distance/(speed - driver->getSpeed());
            state |= OPP_BACK;
            distance -= MAX(car->_dimension_x, mycar->_dimension_x);
            distance -= LENGTH_MARGIN;
        } else

Here we check if the opponent is behind us and faster. We won't use that in the tutorial robot, but you will need it if you want to let overlap faster opponents.

        /* is opponent aside */
        if (distance > -SIDECOLLDIST &&
            distance < SIDECOLLDIST) {
            sidedist = car->_trkPos.toMiddle - mycar->_trkPos.toMiddle;
            state |= OPP_SIDE;
        }
    }
}

This part is responsible to check if the opponent is aside of us. If yes we compute the distance (sideways) and set the flag OPP_SIDE.

Now the implementation of the Opponents class follows.

/* Initialize the list of opponents */
Opponents::Opponents(tSituation *s, Driver *driver)
{
    opponent = new Opponent[s->_ncars - 1];
    int i, j = 0;
    for (i = 0; i < s->_ncars; i++) {
        if (s->cars[i] != driver->getCarPtr()) {
            opponent[j].setCarPtr(s->cars[i]);
            j++;
        }
    }
    Opponent::setTrackPtr(driver->getTrackPtr());
    nopponents = s->_ncars - 1;
}

Opponents::~Opponents()
{
    delete [] opponent;
}

The constructor allocates memory and generates the Opponent instances. The destructor deletes the instances and frees the memory.

/* Updates all the Opponent instances */
void Opponents::update(tSituation *s, Driver *driver)
{
    int i;
    for (i = 0; i < s->_ncars - 1; i++) {
        opponent[i].update(s, driver);
    }
}

The update method simply iterates through the Opponent instances and calls update on them.

Modifying driver.h

To access some data of Driver from Opponent we need to add some methods and variables to driver.h. Put the following methods into the public section.

        tCarElt *getCarPtr() { return car; }
        tTrack *getTrackPtr() { return track; }
        float getSpeed() { return speed; }

We need also the speed of our car. For that we add a variable to the private section.

        float speed;    /* speed in track direction */

Compiling opponent.cpp

To check if everything is ok so far you should compile opponent.cpp now. You need to change the Makefile in the bt directory. Change the line

SOURCES     = ${ROBOT}.cpp driver.cpp

to

SOURCES     = ${ROBOT}.cpp driver.cpp opponent.cpp

and run "make".

Downloads

In case you got lost, you can download my robot for TORCS 1.2.0 or later.

Summary

  • You have created opponent.cpp.
  • You know how it works.