Summary of Simulator Classes

Read time: 11 minutes (2769 words)

To help you design your simulator, here are the example header files I used in building the demonstration code you have seen in class.

Highway Class

The highway provides the logic that makes the demo run. Vehicles were designed like the pool balls that inspired this demo code. After discussing the design in class last week, this design is not that great. But it is a start.

include/Highway.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#pragma once

#include "Vehicle.h"
#include "Random.h"

class Highway {
    public:
        int lanes;      // number of lanes to display
        int lWidth;     // width of a lane in pixels
        int length;     // length of highway section
        int speed;      // speed limit in pixels/sec
        int safe;       // safe passing distance
        double sScale;   // used to somtrol simulation speed
        double xScale;   // distance scale
        int xRoad;
        int yRoad;
        int rampSpeed;  // controls on ramp injection speed

        Highway();      // default contsructor
        void drawLanes( void );
        void setPosition( int x, int y );
        void setScaling( double speed, double length );

        // traffic setup
        static const int MAX_CARS = 20;
        Vehicle cars[MAX_CARS];

        Random sim_rand;

        // vehicle management
        void trafficSetup( int safe );
        void initVehicle( int c );
        void setRampSpeed( int speed );
        bool checkSafe( int c, int lane );
        void changeLanes( int c, int speed );
        void checkCollisions( void);
        void drawVehicle( void );
        void move( void );
        void inject( int time );
        void retire( int car );
};

If you look at the routines provided here, you see methods that are doing the checking to see if a car needs to modify its behavior. For example, the check_safe method determines if a car could change lanes when it is told to move. Figuring this out involves looking to see if any other car is in the way of a possible lane change. In this simple simulation, the lane changes happen instantly.

The checkCollisions method is right out of the pool ball simulation, and is used to alter the speed of the offending vehicle.

Vehicles

Vehicles in the demo are limited gadgets. Basically, they store a bit of data used to decide how they move.

include/Vehicle.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#pragma once

class Vehicle {
    public:
        int size;       // how big should this vehicle be
        int xPos;       // screen position in pixels
        int yPos;       
        int xSpeed;     // current speed
        int ySpeed;

        int lane;
        int preferred_lane;
        bool active;
        int color;

        Vehicle();
        void setPosition( int x, int y );
        void setSpeed( int xv, int yv );

        void move( void );
};

Here, the move method is the most important. The vehicle will only move away from the current lane if it is “safe”. You need to think about this to decide what should happen. (It depends on which way you need to go.)

The logic needed here is up to the car, not the highway. The idea is for the highway to provide enough information to the car for it to decide the next move.

Demo Main Code

Here is the original demo main logic used to run the simulation. This went together pretty quickly so I could show it in class. It needs refactoring a bit, which we will see next.

src/main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "Graphics.h"
#include "Highway.h"
#include "Vehicle.h"
#include <iostream>
using namespace std;

// highway setup --------------------------------
Highway I35;
int xRoad = 50; 
int yRoad = 50;
const int SAFE_LEVEL = 10;
int rampSpeed = 0;

const double xScale = 900/5280.0;
double sScale = 0.2;       // controls simulation speed

int cycles = 0;

void showTime( void ) {
    string msg = "Simulation time: 000000";
    sprintf(&msg[17], "%6d", cycles);
    showString(50, 450, msg);
}

void drawScene(void) {
    clearWindow();          // clear the last scene
    showTime();
    I35.drawLanes();
    I35.drawVehicle();
    glutSwapBuffers();      // double buffering control
}


void animate() {
    // move your objects here
    I35.move();
    glutPostRedisplay();
    pause_ms(16);
    cycles++;
}

// keyboard handler to terminate the program when "q" key is pressed
void handleKey(unsigned char key, int x, int y) {
    switch(key) {
        case 'q':
            exit(0);
            break;
    }
}

int main(int argc, char **argv) {
    if( argc > 1)
        sScale = atof(argv[1]);
    // set up the highway
    I35.setPosition(xRoad,yRoad);
    I35.setScaling( xScale, sScale );

    I35.trafficSetup( SAFE_LEVEL );

    graphicsSetup(argc, argv);      // initialize the graphics system
    glutDisplayFunc(drawScene);     // tell GLUT what function draws the scene
    glutIdleFunc(animate);          // Move objects when animating
    glutKeyboardFunc(handleKey);    // set up the "q" key to quit
    glutMainLoop();                 // GLUT will control the action
    glutSwapBuffers();              // double buffering control
}

This is pretty ugly, since it combines application logic and graphics engine logic. A better design would isolate those two aspects of the design.

Main Refactored

Pulling the graphics logic out of main, and placing it in its own file is what is needed here. Here is the new main code.

src/main.cpp
1
2
3
4
5
6
7
8
#include "SimGraphics.h"
#include <iostream>


int main(int argc, char *argv[]) {
    std::cout << "i35sim (v2)" << std::endl;
    sim_main(argc, argv);
}   

That is a lot easier. It also sets us up ot run simulations that do not use graphics at all. We would bypass the call to sim_main and use only console output to see what happens.

Simulator Graphics

The heart of the visual demo is the graphics library. I put all of the startup code in a separate file to clean up main. Here is the header file for the new graphics module:

include/SimGraphics.h
1
2
3
4
5
// Copyright 2018 Roie R. Black
#pragma once


void sim_main(int argc, char *argv[]);

Here is a start on the Graphics Code. In this code I have added two key commands to make running the simulator more interesting.

The b command stops the action, allowing you to study the current situation. While the simulation is paused, you can press the s key to single step it one cycle. I wanted to get this running before I add crash logic to the demo. That will happen soon (I hope!)

lib/SimGraphics.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Copyright 2018 Roie R. Black
#include "Graphics.h"
#include "SimGraphics.h"

int cycles = 0;     // simulation loop counter
bool step_cmd = false;  // single step simulation
bool break_cmd = false;    // stop simulation toggle

void animate() {
    // move your objects here
    if (!break_cmd) {
        //I35.move();
        cycles++;
        glutPostRedisplay();
        pause_ms(16);
    } else {
        if (step_cmd) {
            // I35.move();
            cycles++;
            glutPostRedisplay();
            step_cmd = !step_cmd;
        }
    }
}

void handleKey(unsigned char key, int x, int y) {
    switch(key) {
        case 'q':
            exit(0);
            break;
        case '+':
            //I35.setRampSpeed(rampSpeed++);
            break;
        case '-':
            //I35.setRampSpeed(rampSpeed--);
            break;
        case 'b':
            break_cmd = ! break_cmd;
            break;
        case 's':
            step_cmd = true;
            break;
    }
}

void showTime( void ) {
    string msg = "Simulation time: 000000";
    sprintf(&msg[17], "%6d", cycles);
    showString(50, 450, msg);
}

void drawScene(void) {
    clearWindow();          // clear the last scene
    // draw your highway here
    showTime();

    // leave this call as the last one here
    glutSwapBuffers();      // double buffering control
}

void sim_main(int argc, char *argv[]) {
    graphicsSetup(argc, argv);      // initialize the graphics system
    glutDisplayFunc(drawScene);     // set function that draws the scene
    glutIdleFunc(animate);          // Move objects when animating
    glutKeyboardFunc(handleKey);    // set up the "q" key to quit
    glutMainLoop();                 // GLUT will control the action
}

Feel free to use this code, either directly (of course, you need to provide the implementation logic), or as a model for your own code. If you get stuck, see me for guidance. I may let you peek at my code to move you forward.