// Traffic.java, version 1.12, July 4, 2008.
// Applet for traffic simulation.
// Copyright 1998-2008 by Rick Wagner, all rights reserved.
// No use without attribution.

import java.applet.*;
import java.awt.*;

// This is an educational example of object oriented design for a traffic simulation applet.
// Use of this source code is authorized for educational purposes only. No use without
// proper attribution to Rick Wagner (wagner@pollux.usc.edu) at the University of Southern
// California.

// Compiled with the Sun JDK 1.6 and written for the JDK 1.0 so it will run in all browsers.

// The prefix naming convention used here is a modified Hungarian notation.
// "s" is string, "sf" is single precision floating point, "i" is integer, "b" is boolean,
// "d" is dimension, and "c" is color.

public class Traffic extends Applet
{
  // Applet instance variables:
  private String sVerNum = "1.12";                       // Only constructors can run here ("" is a constructor)
  private Dimension dApplet;                             // The applet panel size (read from the html file)
  private Image imOffScreen = null;                      // Offscreen image for double buffering
  private Graphics grOffScreen = null;                   // Offscreen graphics for double buffering
  private int iNumCars = 7;                              // Initial number of race cars
  private int iMaxNumCars = 50;
  private Car CarArray[] = new Car[iMaxNumCars];         // Array of race cars
  private float sfRadius;                                // Turn radius in car lengths
  private boolean bStartStop = false;                    // Flag to toggle mouse click state
  private Timer AnimationTimer;                          // Timer for control of the animation rate
  private int iTimerInterval;                            // Timer interval in milliseconds
  private Label MessageLabel;
  private int iTimeCounter = 0;                          // Time counter for measurement purposes
  private boolean iNewCarInhibit = false;                // Inhibit createing multiple cars at once


  // To allow browsers to get information about the applet:
  public String getAppletInfo()
  {
    return "Traffic applet, version " + sVerNum +
           ", by Rick Wagner, copyright 1998-2008,\nall rights reserved.\n\n" +
           "This is an educational example of object oriented design \n" +
           "using a timer to control animation rate.\n\n" +
           "Compiled July 4, 2008 with JDK 1.6. Source code use authorized for\n" +
           "educational purposes only. No use without attribution.\n";
  }

  // Initialize the applet
  public void init()
  {
    this.setBackground(Color.lightGray);
    dApplet = this.size();

    int i;
    sfRadius = 8;
    float sfTheta = -1 * (float) (Math.PI / 2);
    float x = 10;
    float y = -8;
    for (i = 0; i < iNumCars; i++)
    {
      if (x > 0)
      {
        x -= 2;
        CarArray[i] = new Car(x, y, 0);
      }
      else
      {
        sfTheta -= 2 / sfRadius;                           // Two car lengths behind
        x = (float) (sfRadius * Math.cos(sfTheta));
        y = (float) (sfRadius * Math.sin(sfTheta));
        CarArray[i] = new Car(x, y, sfTheta + (float) (Math.PI / 2));
      }
    }
    setCarColors();
    iTimerInterval = 33;                                    // 33 ms is 30 frames per sec (TV rate)
    AnimationTimer = new Timer(this, iTimerInterval);       // 40 ms is 25 frames per sec, 67 is 15
    MessageLabel = new Label("Welcome to Traffic Simulation. Click to start.");
    MessageLabel.setAlignment(Label.CENTER);
    add(MessageLabel);
  } // End of init()

  // Execute this code after initialization
  public void start()
  {
    this.requestFocus();                                    // So we can get keyboard input
    System.out.println("\n" + this.getAppletInfo());        // Identify self to the Java console-aware user
  } // End of start()

  // Execute this code when the browser leaves the page:
  public void stop()
  {
    bStartStop = false;
    MessageLabel.setText("Click to start.");
    AnimationTimer.stop();
  } // End of stop();

  public void onTimer()                                     // Called by the timer
  {
    int i;
    for (i = 0; i < iNumCars; i++)
    {
      CarArray[i].update(i);                                // Update the cars based on speed and position
    }
    repaint();
  }

  // The applet frame painting function
  public void paint(Graphics g)
  {
    // Code for displaying images or drawing in the applet frame
    g.clearRect(0, 0, dApplet.width, dApplet.height);
    this.setBackground(Color.lightGray);
    int i;

    // Paint the race track:
    g.setColor(Color.gray);
    g.fillArc((int) (1.5 * 20), (int) (1.5 * 20), (int) (17 * 20), (int) (17 * 20), 0, 360);
    g.fillArc((int) (1.5 * 20) + 200, (int) (1.5 * 20), (int) (17 * 20), (int) (17 * 20), 0, 360);
    g.fillRect((int) (10 * 20), (int) (1.5 * 20), (int) (10 * 20), (int) (17 * 20));

    g.setColor(Color.lightGray);
    g.fillArc((int) (2.5 * 20), (int) (2.5 * 20), (int) (15 * 20), (int) (15 * 20), 0, 360);
    g.fillArc((int) (2.5 * 20) + 200, (int) (2.5 * 20), (int) (15 * 20), (int) (15 * 20), 0, 360);
    g.fillRect((int) (10 * 20), (int) (2.5 * 20), (int) (10 * 20), (int) (15 * 20));


    // Paint the cars:
    for (i = 0; i < iNumCars; i++)
    {
      CarArray[i].paint(g);
    }

    // Draw a recessed frame around the applet border. Designed for gray-on-gray browser background.
    g.setColor(Color.black);
    g.drawLine(0, 0, dApplet.width - 1, 0);
    g.drawLine(0, 0, 0, dApplet.height - 1);
    g.setColor(Color.white);
    g.drawLine(0, dApplet.height - 1, dApplet.width - 1, dApplet.height - 1);
    g.drawLine(dApplet.width - 1, 1, dApplet.width - 1, dApplet.height - 1);

  } // End of paint()

  // Implements double buffering
  public void update(Graphics g)
  {
    if (imOffScreen == null)
    {
      // Make sure the offscreen and graphics exist
      imOffScreen = this.createImage(dApplet.width, dApplet.height);
      grOffScreen = imOffScreen.getGraphics();
      grOffScreen.clearRect(0, 0, dApplet.width, dApplet.height);
    }
    this.paint(grOffScreen);
    g.drawImage(imOffScreen, 0, 0, null);
  }

  public boolean mouseDown(Event e, int x, int y)
  {
    int i = 0;
    bStartStop = !bStartStop;
    if (bStartStop)
    {
      MessageLabel.setText("Click to stop.");
      this.showStatus("Click to stop.");
      AnimationTimer.start();
    }
    else
    {
      MessageLabel.setText("Click to start.");
      this.showStatus("Click to start.");
      AnimationTimer.stop();
      for (i = 0; i < iNumCars; i++) CarArray[i].setSpeed(0);
    }
    this.requestFocus();                                         // Added July 4, 2008: works around bug added in
    return true;                                                 // Java 2SE.
  }

  public boolean keyDown(Event e, int k)
  {
    float x = 0;
    float y = 0;
    float a = 0;
    int iRegime = 0;
    if (k == 32)
    {
      if (bStartStop)
      {
        if (iNumCars < iMaxNumCars && !iNewCarInhibit)
        {
          this.showStatus("Spacebar pressed, adding one car to " + String.valueOf(iNumCars) + " cars.");
          x = CarArray[iNumCars - 1].getPx();
          y = CarArray[iNumCars - 1].getPy();
          a = CarArray[iNumCars - 1].getDirection();
          iNumCars++;
          CarArray[iNumCars - 1] = new Car(x, y, a);
          CarArray[iNumCars - 1].setSpeed(CarArray[iNumCars - 2].getSpeed() * ((float) 0.9));
          CarArray[iNumCars - 1].setColor(get10Color((iNumCars - 1) % 10));
        }
        else
        {
          this.showStatus("Spacebar pressed, " + String.valueOf(iNumCars) + " cars.");
        }
      }
      else
      {
        this.showStatus("Spacebar pressed.");
      }
    }
    else
    {
      this.showStatus("Key pressed: " + String.valueOf(k));
    }
    return true;
  }

  private void setCarColors()
  {
    int i = 0;
    for (i = 0; i < iNumCars; i++)
    {
      CarArray[i].setColor(get10Color(i % 10));
    }
  }

  private Color get10Color(int i)
  {
    Color c = null;
    switch (i)
    {
      case 0:
      {
        c = Color.red;
        break;
      }
      case 1:
      {
        c = Color.blue;
        break;
      }
      case 2:
      {
        c = Color.white;
        break;
      }
      case 3:
      {
        c = Color.green;
        break;
      }
      case 4:
      {
        c = Color.yellow;
        break;
      }
      case 5:
      {
        c = Color.cyan;
        break;
      }
      case 6:
      {
        c = Color.orange;
        break;
      }
      case 7:
      {
        c = Color.pink;
        break;
      }
      case 8:
      {
        c = Color.magenta;
        break;
      }
      case 9:
      {
        c = Color.yellow.darker();
        break;
      }
    }
    return c;
  }

  class Car
  {
    private float sfCarLength;
    private float sfThrottle;           // Throttle setting (0 to 1)
    private float sfBrake;              // Brake setting (0 to 1)
    private float sfSpeed;              // Scalar speed of the car
    private float sfAccel;              // Scalar acceleration of the car
    private float sfDirection;          // Direction of travel in radians from east.
    private float sfPx;                 // Position
    private float sfPy;
    private float sfX[] = new float[4];
    private float sfY[] = new float[4];
    private int iX[] = new int[4];      // Integer arrays for constructing the polygon for painting
    private int iY[] = new int[4];
    private float sfPaintScaleFactor;
    private Color cCar;
    private Dimension d;

    public Car()                        // Default constructor
    {
      cCar = Color.white;
      sfCarLength = 1;                  // Car is one car length long
      sfThrottle = 0;
      sfBrake = 0;
      sfSpeed = 0;
      sfAccel = 0;
      sfPx = 0;
      sfPy = 0;
      sfPaintScaleFactor = 20;          // Car is painted 20 pixels long
      pose(0, 0, 0);
    }

    public Car(float px, float py, float theta)         // Position and direction constructor
    {
      cCar = Color.white;
      sfCarLength = 1;                  // Car is one car length long
      sfThrottle = 0;
      sfBrake = 0;
      sfSpeed = 0;
      sfAccel = 0;
      sfPx = px;
      sfPy = py;
      sfPaintScaleFactor = 20;          // Car is painted 20 pixels long
      pose(sfPx, sfPy, normalizeAngle(theta));
    }

    public void setColor(Color c)
    {
      cCar = c;
    }

    public void setSpeed(float s)
    {
      sfSpeed = s;
    }

    public float getSpeed()
    {
      return sfSpeed;
    }

    private void pose(float x, float y, float a)
    {
      // Sets the position and orientation of the car:
      int i;
      float sfTempX;
      float sfTempY;
      sfX[0] = (float) -.5; sfY[0] = (float) -.25;
      sfX[1] = (float) .5; sfY[1] = (float) -.25;
      sfX[2] = (float) .5; sfY[2] = (float) .25;
      sfX[3] = (float) -.5; sfY[3] = (float) .25;
      float sfSin = (float) Math.sin(a);                          // Compute these just once
      float sfCos = (float) Math.cos(a);
      for (i = 0; i <= 3; i++)
      {
        sfTempX = sfX[i]; sfTempY = sfY[i];
        sfX[i] = x + sfTempX * sfCos - sfTempY * sfSin;
        sfY[i] = y + sfTempX * sfSin + sfTempY * sfCos;
      }
      sfPx = x;
      sfPy = y;
      sfDirection = a;
    }

    private float getPx()
    {
      return sfPx;
    }

    private float getPy()
    {
      return sfPy;
    }

    private float getDirection()
    {
      return sfDirection;
    }

    public void update(int iMyIndex)
    {
      float sfDeltaDistance = 0;
      float sfDeltaTheta = 0;
      float sfAngleToCar = 0;
      float sfDistanceToCar = 0;
      float sfTargetDistance = 0;
      float sfMyAngle;
      float sfLeadAngle;
      float sfMaxSpeed = (float) .25;    // Quarter of a car length per update is the fastest
      float sfSpeedAttenuation = 0;
      float sfTempDirection = 0;
      float sfTempX = 0;
      float sfTempY = 0;
      int iLeadIndex = (iMyIndex - 1);
      if (iLeadIndex < 0) iLeadIndex = iNumCars - 1;
      int iMyRegime;
      int iLeadRegime;
      float sfDeltaSpeed = 0;

      iNewCarInhibit = false;

      // Determine the regime for applying updates:
      iMyRegime = getRegime(iMyIndex);
      iLeadRegime = getRegime(iLeadIndex);
      sfDistanceToCar = getDistance(iMyRegime, iLeadRegime, iLeadIndex);

      // Compute the ideal following distance:
      sfTargetDistance = (float) 17.0 * sfSpeed + (float) 1.0; // Linear function of my speed (about 4 center-to-center, max)

      if (sfDistanceToCar > sfTargetDistance)
      {
        // We need to catch up to the car:
        sfThrottle = (float) 1.0 * ((sfDistanceToCar - sfTargetDistance) / sfDistanceToCar);
        sfBrake = (float) 0.0;
      }
      else
      {
        if (sfDistanceToCar > sfTargetDistance * 3.0 / 4.0)
        {
          // We need to back off the gas:
          sfThrottle = (float) 0;
          sfBrake = (float) 0.2;
        }
        else
        {
          if (sfDistanceToCar > sfTargetDistance / 2.0)
          {
            // We need to touch the brakes:
            sfThrottle = (float) 0;
            sfBrake = (float) 0.7;
          }
          else
          {
            if (sfDistanceToCar > 1.0)
            {
              // We need to slam on the brakes:
              sfThrottle = (float) 0;
              sfBrake = (float) 1.0;
            }
            else
            {
              // Too late. Collision.
              iNewCarInhibit = true;
            }
          }
        }
      }
      // Acceleration decreases with speed
      sfSpeedAttenuation = ((float) 1.0 - sfSpeed / sfMaxSpeed);
      sfAccel = (float) (sfSpeedAttenuation * sfThrottle * .01 - sfBrake * .01);
      sfSpeed += sfAccel;
      if (sfSpeed < 0) sfSpeed = 0;                                        // Can't go backwards
      if (sfSpeed > sfMaxSpeed) sfSpeed = sfMaxSpeed;                      // Speed limit
      if (sfDistanceToCar < (float) 1.0)
      {
        // Collision
        if (CarArray[iLeadIndex].sfSpeed < sfSpeed)
        {
          sfSpeed = CarArray[iLeadIndex].sfSpeed;
        }
        else
        {
          // We have a newly created car within us, inhibit new cars.
          iNewCarInhibit = true;
        }
      }
      sfDeltaDistance = sfSpeed;

      // If the regime is an even number then we're in a turn:
      if (iMyRegime % 2 == 0)
      {
        sfTempDirection = sfDirection + (sfDeltaDistance / sfRadius);

        if (iMyRegime == 2)
        {
          // East turn:
          sfTempX = (float) ((sfRadius * Math.cos(sfTempDirection - (float) (Math.PI / 2.0))) + 10);
          sfTempY = (float) ((sfRadius * Math.sin(sfTempDirection - (float) (Math.PI / 2.0))));
        }
        else
        {
          // West turn:
          sfTempX = (float) (sfRadius * Math.cos(sfTempDirection - (float) (Math.PI / 2.0)));
          sfTempY = (float) (sfRadius * Math.sin(sfTempDirection - (float) (Math.PI / 2.0)));
        }
      }
      else
      {
        // We're on a straight:
        if (iMyRegime == 1)
        {
          // Bottom straight:
          sfTempX = sfPx + sfSpeed;
          sfTempY = -8;
          sfTempDirection = 0;
        }
        else
        {
          // Top straight:
          sfTempX = sfPx - sfSpeed;
          sfTempY = 8;
          sfTempDirection = (float) (Math.PI);
        }
      }
      CarArray[iMyIndex].pose(sfTempX, sfTempY, sfTempDirection);            // Move the car to the new pose

    } // End of Car.update()

    public int getRegime(int i)
    {
      int iRegime = 0;
      if (CarArray[i].sfPx <= 0)
      {
        // We're in the west turn
        iRegime = 4;
      }
      else
      {
        if (CarArray[i].sfPx >= 10)
        {
          // We're in the east turn
          iRegime = 2;
        }
        else
        {
          // We're on a straight
          if (CarArray[i].sfPy > 0)
          {
            // We're on the top straight
            iRegime = 3;
          }
          else
          {
            // We're on the bottom straight
            iRegime = 1;
          }
        }
      }
      return iRegime;
    }

    private float getDistance(int iARegime, int iBRegime, int iBIndex)
    {
      float d = 0;
      float sfAAngle = 0;
      float sfBAngle = 0;
      float sfAngleToCar = 0;
      float sfATurnD = 0;
      float sfBTurnD = 0;
      float sfAStraightD = 0;
      float sfBStraightD = 0;
      switch (iARegime)
      {
        case 1:                                     // I'm on the bottom straight
        {
          switch (iBRegime)
          {
            case 1:                                 // He's on the bottom straight
            {
              d = CarArray[iBIndex].sfPx - sfPx;
              break;
            }
            case 2:                                 // He's in the east turn (on the right)
            {
              sfAStraightD = 10 - sfPx;
              sfAAngle =  - (float) (Math.PI / 2.0);
              sfBAngle = normalizeAngle(CarArray[iBIndex].sfDirection  - (float) (Math.PI / 2.0));
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfBTurnD = sfAngleToCar * sfRadius;
              d = sfAStraightD + sfBTurnD;
              break;
            }
            case 3:                                 // He's on the top straight
            {
              sfAStraightD = 10 - sfPx;
              sfBTurnD = ((float) Math.PI) * sfRadius;
              sfBStraightD = 10 - CarArray[iBIndex].sfPx;
              d = sfAStraightD + sfBTurnD + sfBStraightD;
              break;
            }
            case 4:                                 // He's in the west turn (on the left)
            {
              sfAStraightD = 10 - sfPx;
              sfBTurnD = ((float) Math.PI) * sfRadius;
              sfBStraightD = 10;
              sfAAngle = (float) (Math.PI / 2.0);
              sfBAngle = normalizeAngle(CarArray[iBIndex].sfDirection  - (float) (Math.PI / 2.0));
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfBTurnD += sfAngleToCar * sfRadius;
              d = sfAStraightD + sfBTurnD + sfBStraightD;
              break;
            }
          }
          break;
        }
        case 2:                                     // I'm on the east turn (on the right)
        {
          switch (iBRegime)
          {
            case 1:                                 // He's on the bottom straight
            {
              sfAAngle = normalizeAngle(sfDirection  - (float) (Math.PI / 2.0));
              sfBAngle = (float) (Math.PI / 2);
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfATurnD = sfAngleToCar * sfRadius;

              sfAStraightD = 10;
              sfBTurnD = ((float) Math.PI) * sfRadius;
              sfBStraightD = CarArray[iBIndex].sfPx;
              d = sfATurnD + sfAStraightD + sfBTurnD + sfBStraightD;
              break;
            }
            case 2:                                 // He's in the east turn
            {
              sfAAngle = normalizeAngle(sfDirection - (float) (Math.PI / 2.0));
              sfBAngle = normalizeAngle(CarArray[iBIndex].sfDirection  - (float) (Math.PI / 2.0));
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfBAngle > sfAAngle)
              {
                // He's ahead of me in this turn
                d = sfAngleToCar * sfRadius;
              }
              else
              {
                // He's behind me
                sfAngleToCar = ((float) (Math.PI * 2)) + sfAngleToCar;
                d = sfAngleToCar * sfRadius + 20;
              }
              break;
            }
            case 3:                                 // He's on the top straight
            {
              sfAAngle = normalizeAngle(sfDirection - (float) (Math.PI / 2.0));
              sfBAngle =  (float) (Math.PI / 2.0);
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfATurnD = sfAngleToCar * sfRadius;
              sfBStraightD = 10 - CarArray[iBIndex].sfPx;
              d = sfATurnD + sfBStraightD;
              break;
            }
            case 4:                                 // He's in the west turn  (on the left)
            {
              sfAAngle = normalizeAngle(sfDirection - (float) (Math.PI / 2.0));
              sfBAngle = (float) (Math.PI / 2.0);
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfATurnD = sfAngleToCar * sfRadius;
              sfBStraightD = 10;
              sfAAngle = (float) (Math.PI / 2.0);
              sfBAngle = normalizeAngle(CarArray[iBIndex].sfDirection  - (float) (Math.PI / 2.0));
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfBTurnD = sfAngleToCar * sfRadius;
              d = sfATurnD + sfBStraightD + sfBTurnD;
              break;
            }
          }
          break;
        }
        case 3:                                     // I'm on the top straight
        {
          switch (iBRegime)
          {
            case 1:                                 // He's on the bottom straight
            {
              sfAStraightD = sfPx;
              sfBTurnD = ((float) Math.PI) * sfRadius;
              sfBStraightD = CarArray[iBIndex].sfPx;
              d = sfAStraightD + sfBTurnD + sfBStraightD;
              break;
            }
            case 2:                                 // He's in the east turn
            {
              sfAStraightD = sfPx;
              sfATurnD = ((float) Math.PI) * sfRadius;
              sfBStraightD = 10;
              sfAAngle = - (float) (Math.PI / 2.0);
              sfBAngle = normalizeAngle(CarArray[iBIndex].sfDirection  - (float) (Math.PI / 2.0));
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfBTurnD = sfAngleToCar * sfRadius;
              d = sfAStraightD + sfATurnD + sfBStraightD + sfBTurnD;
              break;
            }
            case 3:                                 // He's on the top straight
            {
              d =  sfPx - CarArray[iBIndex].sfPx;
              break;
            }
            case 4:                                 // He's in the west turn
            {
              sfAStraightD = sfPx;
              sfAAngle = (float) (Math.PI / 2.0);
              sfBAngle = normalizeAngle(CarArray[iBIndex].sfDirection  - (float) (Math.PI / 2.0));
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfBTurnD = sfAngleToCar * sfRadius;
              d = sfAStraightD + sfBTurnD;
              break;
            }
          }
          break;
        }
        case 4:                                     // I'm in the west turn (on the left)
        {
          switch (iBRegime)
          {
            case 1:                                 // He's on the bottom straight
            {
              sfAAngle = normalizeAngle(sfDirection  - (float) (Math.PI / 2.0));
              sfBAngle = -(float) (Math.PI / 2);
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfATurnD = sfAngleToCar * sfRadius;
              sfAStraightD = CarArray[iBIndex].sfPx;
              d = sfATurnD + sfAStraightD;
              break;
            }
            case 2:                                 // He's in the east turn (on the right)
            {
              sfAAngle = normalizeAngle(sfDirection  - (float) (Math.PI / 2.0));
              sfBAngle = -(float) (Math.PI / 2);
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfATurnD = sfAngleToCar * sfRadius;

              sfAStraightD = 10;
              sfAAngle = -(float) (Math.PI / 2);
              sfBAngle = normalizeAngle(CarArray[iBIndex].sfDirection  - (float) (Math.PI / 2.0));
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfBTurnD = sfAngleToCar * sfRadius;
              d = sfATurnD + sfAStraightD + sfBTurnD;
              break;
            }
            case 3:                                 // He's on the top straight
            {
              sfAAngle = normalizeAngle(sfDirection - (float) (Math.PI / 2.0));
              sfBAngle = -(float) (Math.PI / 2);
              sfAngleToCar = sfBAngle - sfAAngle;
              if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
              sfATurnD = sfAngleToCar * sfRadius;

              sfAStraightD = 10;
              sfBTurnD = ((float) Math.PI) * sfRadius;
              sfBStraightD = 10 - CarArray[iBIndex].sfPx;
              d = sfATurnD + sfAStraightD + sfBTurnD + sfBStraightD;
              break;
            }
            case 4:                                 // He's in the west turn
            {
              boolean bBLeads = false;
              sfAAngle = normalizeAngle(sfDirection - (float) (Math.PI / 2.0));
              sfBAngle = normalizeAngle(CarArray[iBIndex].sfDirection - (float) (Math.PI / 2.0));
              if (sfAAngle >= 0)
              {
                if (sfBAngle >= 0)
                {
                  // Both a and b positive
                  if (sfAAngle > sfBAngle) {;}
                  else
                  {
                    // b leads a
                    bBLeads = true;
                  }
                }
                else
                {
                  if (sfAAngle > sfBAngle)
                  {
                    // b leads a
                    bBLeads = true;
                  }
                  else {;}
                }
              }
              else
              {
                if (sfBAngle >= 0)
                {
                  if (sfAAngle > sfBAngle)
                  {
                    // b leads a
                    bBLeads = true;
                  }
                  else {;}
                }
                else
                {
                  // Both a and b negative
                  if (sfAAngle > sfBAngle) {;}
                  else
                  {
                    // b leads a
                    bBLeads = true;
                  }
                }
              }
              if (bBLeads)
              {
                sfAngleToCar = sfBAngle - sfAAngle;
                if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
                d = sfAngleToCar * sfRadius;
              }
              else
              {
                sfAngleToCar = sfAAngle - sfBAngle;
                if (sfAngleToCar < 0) sfAngleToCar += (float) (Math.PI * 2.0);
                sfAngleToCar = ((float) (Math.PI * 2)) - sfAngleToCar;
                d = sfAngleToCar * sfRadius + 20;
              }
              break;
            }
          }
          break;
        }
      }
      return d;
    }

    public void paint(Graphics g)
    {
      Polygon pgon;
      int i;
      for (i = 0; i <= 3; i++)
      {
        iX[i] = 200 + Math.round(sfPaintScaleFactor * sfX[i]); // Get the polygon for painting
        iY[i] = 200 - Math.round(sfPaintScaleFactor * sfY[i]);
      }
      pgon = new Polygon(iX, iY, 4);
      g.setColor(cCar);
      g.fillPolygon(pgon);
      g.setColor(Color.black);
      g.drawPolygon(pgon);
    }

    private float normalizeAngle(float a)          // Normalize angle to +/- pi radians
    {
      float r = a;
      if (a > Math.PI) r -= (float) (Math.PI * 2.0);
      if (a <= -Math.PI) r += (float) (Math.PI * 2.0);
      return r;
    }
  }  // End of Car class

} // End of Applet class

class Timer implements Runnable
{
  // Calls the onTimer() function in the calling applet every iInterval milliseconds.
  // Copyright 1998 and 1999 by Rick Wagner, all rights reserved. No use without attribution.

  private Thread TimerThread;
  private int iInterval;
  private Traffic app;                           // The calling applet

  public Timer(Traffic a, int i)                 // Interval constructor
  {
    iInterval = i;
    app = a;
  }

  public void start()
  {
    TimerThread = new Thread(this);
    TimerThread.start();
  }

  public void stop()
  {
    if (TimerThread != null) TimerThread.stop();
    TimerThread = null;
  }

  public void run()
  {
    long lStartTime = System.currentTimeMillis();
    long lTemp = 0;
    int iSleepTime = iInterval / 10;
    if (iSleepTime == 0) iSleepTime = 1;
    while (true)
    {
      try
      {
        TimerThread.sleep(iSleepTime);               // Wake up every 1/10 interval and see
      }                                              // if it's time yet.
      catch(InterruptedException e1)
      {
      }
      lTemp = System.currentTimeMillis();
      if (lTemp - lStartTime >= iInterval)
      {
        app.onTimer();                               // Trigger the desired events.
        lStartTime = lTemp;
      }
    }
  }
} // End of Timer class
