Driver's Ed Game

Driver's Ed Game

A project by Burak Aydemir and Muhammad Doukmak for ECE 4180

Description

This game is a mini arcade game that uses many different input/output devices in a unique and fun way. The premise of the game is that you are a student driver during an driving exam. You must drive on a 3-lane highway while taking instructions from the driving instructor. There are several things you must be aware of while you're driving, or you'll fail the test:

  • Be sure to avoid collision with all obstacles/cars on the highway
  • Obey the posted speed limit at all times!
  • Stop when the instructor tells you to, and remain stopped until you are cleared to proceed.

Game Mechanics

During the game, you must be constantly changing lanes, adjusting your speed, and stopping when needed or you'll make your instructor impatient with you. The instructor's patience will be displayed at the bottom of the screen, and every mistake you make will decrease the instructor's patience. The slower your reaction times, the more impatient the instructor will become. If the instructor's patience reaches 0, you will lose the game. Additionally, any collision with an obstacle/vehicle will end the game.

The instructor will indicate when the user needs to stop via two indicators: An audible command ("Stop") and the Red LED turning on. After a few seconds, the instructor will then indicate when to go ("Go") with the green LED turning on.

Game Controls

There are two main input devices in this game:

  1. 5-way digital tactile switch
  2. Analog IR proximity sensor

You use the 5-way tactile switch to navigate the menus before the game. During the game, you use the 5-way switch to change lanes and apply the brakes. The Analog IR proximity sensor is used to control speed.

Input DeviceMenus (Before Game)During Game
5-Way Tactile SwitchUp/Down/Center to NavigateUp/Down = Change Lanes... Center = Stop/Go
IR Proximity SensorN/ACloser = Faster, Farther = Slower

Component List

Hookup Guide

mbedDevice
GNDBreadboard Ground Rail
VOUTBreadboard 3.3V Rail
VUBreadboard 5V Rail
p5microSD DI
p6microSD DO
p7microSD SCK
p8microSD CS
p125-Way Switch D
p145-Way Switch C
p155-Way Switch U
p18Audio Amp IN+
p20IR Sensor Out
p21Green LED (+ 100Ohm resistor)
p22Red LED (+100Ohm resistor)
p27uLCD TX
p28uLCD RX
p30uLCD RES
microSD
GNDGround Rail
VCC3.3V Supply Rail
5Way Tactile Switch
-Ground Rail
+3.3V Supply Rail
Class DAudio Amp Breakout
IN-Ground Rail
PWR+3.3V Supply Rail
PWR-Ground Rail
VOL10K Trimpot
OUT-Speaker-
OUT+Speaker+
IR Sensor
Vin5V Supply Rail
GNDGround Rail
uLCD
+5V5V Supply Rail
GNDGround Rail

Connection Image

http://i.imgur.com/npCpDwo.jpg

Program

Import programDrivers Ed

This game is a mini arcade game that uses many different input/output devices in a unique and fun way. The premise of the game is that you are a student driver during a driving exam. You must drive on a 3-lane highway while taking instructions from the driving instructor and obeying speed limits.

include the mbed library with this snippet

#include "mbed.h"
#include "uLCD_4DGL.h"
#include "wave_player.h"
#include "SDFileSystem.h"
#include "rtos.h"
#define MAXPATIENCE 22
Mutex move_mutex;
Mutex brake_mutex;
AnalogIn distance(p20);
PwmOut red(p22);
PwmOut green(p21);
struct Obstacle {
    int x;
    int y;
    int color;
} OBSTACLE;
// Game Variables
int carY = 32;
int carX = 2;
int carH = 10;
int carW = 15;
float speed = 0.0;
int move = 0;
int brake = 0;
int isBraked = 0;
int counter = 0;
int stopCounter = 0;
int speedCounter = 0;
int oi = 0;
int obsWidth = 10;
struct Obstacle obstacles[30];
int didLose = 0;
bool shouldStop = false;
int randTimes[100];
int randCount = 0;
int penaltyTime = 0;
int speedLimit = 55;
DigitalOut led1(LED1);

uLCD_4DGL uLCD(p28, p27, p30); //p28 is an TX pin on mbed, p27 is an RX pin on mbed, p30 is a GPIO pin on mbed.
AnalogOut DACout(p18);
wave_player waver(&DACout);
SDFileSystem sd(p5, p6, p7, p8, "sd"); //SD card
InterruptIn up(p15);
InterruptIn center(p14);
InterruptIn down(p12);
FILE *stop_File;
FILE *go_File;
FILE *lose_File;


void playStop() {
    rewind(stop_File);
    bool asd = false;
    waver.play(stop_File, &asd);
}

void playGo () {
    rewind(go_File);
    bool asd = false;
    waver.play(go_File, &asd);
}


////////// enumeration for the state machine of the game //////////
enum gameStateType {splashScreen, menu, game, lose, quit};

////////// global variables ////////
gameStateType gameState;
bool isStopped = false;
bool isGameDifficult = true;
bool isSoundOn = true;
bool isInMainMenu = true;
bool isMenuChanged = false;
bool isStateChanged = false;
bool isSoundChanged = false;
bool isDifficultyChanged = false;
int menuPointer = 0;



////////// callback function definitons //////////

void up_hit_callback (void) {
    switch (gameState) {
        case splashScreen:
            isStopped = true;
            gameState = menu;
        break;
        case menu:
            menuPointer--;
            if (menuPointer < 0) {
                menuPointer += 3;   
            }
        break;
        case game:
            move_mutex.lock();
            move = 1;
            move_mutex.unlock();
        break;
    }    
}

void down_hit_callback (void) {
    switch (gameState) {
        case splashScreen:
            isStopped = true;
            gameState = menu;
        break;
        case menu:
            menuPointer++;
            menuPointer = menuPointer % 3;
        break;
        case game:
            move_mutex.lock();
            move = -1;
            move_mutex.unlock();
        break;
    }    
}

void center_hit_callback (void) {
    switch (gameState) {
        case splashScreen:
            isStopped = true;
            gameState = menu;
        break;
        case menu:
            if (isInMainMenu) {
                if (menuPointer == 0) {
                    gameState = game;
                    isStateChanged = true;    
                } else if (menuPointer == 1) {
                    isInMainMenu = false;
                    isMenuChanged = true;
                    menuPointer = 0;    
                } else if (menuPointer == 2) {
                    gameState = quit;
                    isStateChanged = true;
                }
            } else {
                if (menuPointer == 0) {
                    isSoundOn = !isSoundOn;
                    isSoundChanged = true;    
                } else if (menuPointer == 1) {
                    isGameDifficult = !isGameDifficult;
                    isDifficultyChanged = true;    
                } else if (menuPointer == 2) {
                    isInMainMenu = !isInMainMenu;
                    isMenuChanged = true;
                    menuPointer = 0;
                }
            }
        break;
        case game:  
            brake_mutex.lock();
            brake = 1;
            brake_mutex.lock();
        break;
    }    
}
// Methods
void moveCar(bool val) {
    if (val & (carY > 33)) 
    {
        uLCD.filled_rectangle(carX, carY, carX+carW, carY + carH, BLACK);
        carY-=25;
        uLCD.filled_rectangle(carX, carY, carX+carW, carY + carH, BLUE);
    } else if (!val & (carY < 81)){
        uLCD.filled_rectangle(carX, carY, carX+carW, carY + carH, BLACK);
        carY+=25;
        uLCD.filled_rectangle(carX, carY, carX+carW, carY + carH, BLUE);
    }
}
void brakeNow() {
    if (isBraked == 1) {
        isBraked = 0;
        speed = 15;  // Change this <<<<<<<<<<<<<<<<<<<
    } else {
        speed = 0;
        isBraked = 1;
    }
}
bool detectCollision(int x1, int y1, int w, int x2, int y2) {
    bool result = false;
    if (y1 == y2) {
        if ((x1+w) >= x2) {
            result = true;
        }
    }
    return result;
}
void setStop() {
    if(counter%(randTimes[randCount]/2) == 0) {
        randCount++;
        if(randCount>99) {
            randCount = 0;
        }
        green = 0.0f;
        red = 0.9f;
        shouldStop = true;
        stopCounter = 0;
        if (isSoundOn) {
            playStop();
        }
        printf("Stop #%d\n",randCount);
    }
    if (shouldStop & (stopCounter==20)) {
        shouldStop = false;
        if (isSoundOn) {
            playGo();
        }
        printf("Go");
        green = 0.5f;
        red = 0;
    }
}
void checkStop() {
    if (shouldStop & (speed > 0)) {
        penaltyTime++;
        uLCD.filled_rectangle((60+3*MAXPATIENCE)-3*penaltyTime, 113, 126, 118, BLACK);
    }
    if ((penaltyTime>MAXPATIENCE) & shouldStop) {
        didLose = 1; // didLose = 1 for Stopping
        penaltyTime = 0;
    }
}
void setSpeed() {
    if(counter%(randTimes[randCount]/4) == 0) {
        printf("Speed limit #%d\n",randCount);
        randCount++;
        speedLimit = 25 + 15*(rand()%3);
        speedCounter = 0;
    }
    if ((speedLimit < 55) & (speedCounter == 50)) {
        speedLimit = 55;
    }
}
void checkSpeed() {
    if ((speedLimit < 55) & (speed > speedLimit)) {
        penaltyTime++;
        uLCD.filled_rectangle((60+3*MAXPATIENCE)-3*penaltyTime, 113, 126, 118, BLACK);
    }
    if (penaltyTime>MAXPATIENCE) {
        didLose = 2; // didLose = 2 for speed limit
        penaltyTime = 0;
    }
}
        
void redraw() {
    move_mutex.lock();
    brake_mutex.lock();
    if ((move != 0) & !isBraked) {
        moveCar(move==1 ? true:false);
        move = 0;
    } else if(brake == 1) {
        move = 0;
        brakeNow();
        brake = 0;
    }
    brake_mutex.unlock();
    move_mutex.unlock();
    if (!isBraked) {
        float normalized = 40.0*distance;
        if (normalized > 27) {
            speed = 50;
        } else if (normalized > 17) {
            speed = 35;
        } else {
            speed = 20;
        }
    }
    int difficulty;
    if(isGameDifficult) {
        difficulty = 2;
    } else {
        difficulty = 1;
    }
    if ((counter%(40/difficulty)) == 0 & !isBraked) {
        obstacles[oi].x = 127;
        printf("put obstacle %d on screen\n",oi);
        oi ++;
        if(oi>29) {
            oi=0;
        }
    }
    for (int i=0; i < 30; i++) {
        if (obstacles[i].x >= 0) {
            if (detectCollision(carX, carY, carW, obstacles[i].x, obstacles[i].y)) {
                didLose = 3;
                break;
            }
            uLCD.filled_rectangle(obstacles[i].x-speed/5+obsWidth, obstacles[i].y, obstacles[i].x+obsWidth, obstacles[i].y+obsWidth, BLACK);
            obstacles[i].x = obstacles[i].x - (speed/5);
            if (obstacles[i].x < 1) {
                uLCD.filled_rectangle(0, obstacles[i].y, obstacles[i].x+obsWidth, obstacles[i].y+obsWidth, BLACK);
                obstacles[i].x = -1;
            } else if (obstacles[i].x < 127-obsWidth) {
                uLCD.filled_rectangle(obstacles[i].x, obstacles[i].y, obstacles[i].x+obsWidth, obstacles[i].y+obsWidth, obstacles[i].color);
            }
        }
    }
    uLCD.locate(0,0);
    uLCD.printf("Speed:%2.0f  ", speed);
    uLCD.locate(0,15);
    uLCD.printf("Speed Limit:%d",speedLimit);
    counter++;
    speedCounter++;
    stopCounter++;
}
void setup() {
    uLCD.cls();
    counter = 0;
    oi = 0;
    uLCD.locate(0,0);
    uLCD.color(GREEN);
    uLCD.printf("Speed:%d",speed);
    uLCD.locate(0,15);
    uLCD.color(WHITE);
    uLCD.printf("Speed Limit:");
    uLCD.color(GREEN);
    uLCD.printf("%d", speedLimit);
    uLCD.locate(0,14);
    uLCD.printf("Patience:"); 
    uLCD.filled_rectangle(60, 113, 60+3*MAXPATIENCE, 118, RED);
    uLCD.line(0,25,127,25,WHITE);
    uLCD.line(0,75,25,75,0xFFFF00);
    uLCD.line(50,75,75,75,0xFFFF00);
    uLCD.line(100,75,125,75,0xFFFF00);
    uLCD.line(0,50,25,50,0xFFFF00);
    uLCD.line(50,50,75,50,0xFFFF00);
    uLCD.line(100,50,125,50,0xFFFF00);
    uLCD.line(0,100,127,100,WHITE);
    uLCD.filled_rectangle(carX, carY, carX+carW, carY + carH, BLUE);
    for (int i=0; i<100; i++) {
        randTimes[i] = 100 + 100*(rand()%3);
    }
    wait(2);
    green = 0.5f;
    speed = 0.0;
    move = 0;
    brake = 0;
    isBraked = 0;
    counter = 0;
    speedCounter = 0;
    stopCounter = 0;
    oi = 0;
    didLose = 0;
    shouldStop = false;
    randCount = 0;
    penaltyTime = 0;
    speedLimit = 55;
    for (int i=0; i<30; i++) {
        obstacles[i].x = -1;
        obstacles[i].y = 32 + (rand()%3)*25;
        obstacles[i].color = rand()%0xFFFFFF;
    }
}
void reDrawMainMenu(void) {
    uLCD.cls();
    uLCD.locate(2,0);
    uLCD.printf("Driver School!!");
    uLCD.line(0, 18, 127, 18, WHITE);
    uLCD.locate(2,4);
    uLCD.printf("Start The Game");
    uLCD.locate(2,8);
    uLCD.printf("Options");
    uLCD.locate(2,12);
    uLCD.printf("Exit The Game");
    uLCD.line(0,115,127,115,WHITE);
    uLCD.triangle(11,35, 6, 32, 6,38, RED);
}

void reDrawSubMenu(void) {
    uLCD.cls();    
    uLCD.locate(2,0);
    uLCD.printf("Driver School!!");
    uLCD.line(0, 18, 127, 18, WHITE);
    uLCD.locate(2,4);
    if (isSoundOn) {
        uLCD.printf("Sound: On");
    } else {
        uLCD.printf("Sound: Off");    
    }
    uLCD.locate(2,8);
    uLCD.printf("Difficulty: High");
    uLCD.locate(2,12);
    uLCD.printf("Return to Main                Menu");
    uLCD.line(0,115,127,115,WHITE);
    uLCD.triangle(11,35, 6, 32, 6,38, RED);
}




int main() {
    ////////// initialization //////////
    // initializing the game state.
    gameState = splashScreen;
    uLCD.baudrate(BAUD_3000000);
    // attaching ISR's.
    up.fall(&up_hit_callback);
    down.fall(&down_hit_callback);
    center.fall(&center_hit_callback);
    go_File = fopen("/sd/mydir/Go.wav", "r");
    stop_File = fopen("/sd/mydir/Stop.wav", "r");
    lose_File = fopen("/sd/mydir/Loser.wav", "r");
    // Game Loop
    while (true) { 
        switch (gameState) {
            case splashScreen: {
                uLCD.media_init();
                uLCD.set_sector_address(0x00, 0x00);
                uLCD.display_image(0,0);
                FILE *wave_file;
                wave_file = fopen("/sd/mydir/INeedSpeed.wav", "r");
                if (wave_file == NULL) {
                    printf("cannot open the file\n\r");  
                }
                while (!isStopped) {
                    waver.play(wave_file, &isStopped);
                }
                fclose(wave_file);
                break;
            }
            case menu: {
                reDrawMainMenu();
                int oldMenuPointer = menuPointer;
                while (!isStateChanged) {
                    if (isMenuChanged) {
                        if (!isInMainMenu) {
                            reDrawSubMenu();
                        } else {
                            reDrawMainMenu();    
                        }
                        isMenuChanged = false;
                    } else {
                        if (oldMenuPointer != menuPointer) {
                            if (oldMenuPointer == 0) {
                                uLCD.triangle(11,35, 6, 32, 6,38, BLACK);
                            } else if (oldMenuPointer == 1) {
                                uLCD.triangle(11,68, 6, 65, 6,71, BLACK);
                            } else if (oldMenuPointer == 2) {
                                uLCD.triangle(11,100, 6, 97, 6, 103, BLACK);
                            }
                            if (menuPointer == 0) {
                                uLCD.triangle(11,35, 6, 32, 6,38, RED);
                            } else if (menuPointer == 1) {
                                uLCD.triangle(11,68, 6, 65, 6,71, RED);
                            } else if (menuPointer == 2) {
                                uLCD.triangle(11,100, 6, 97, 6, 103, RED);
                            }
                            oldMenuPointer = menuPointer;
                        }
                        if (isSoundChanged) {
                            if (isSoundOn) {
                                uLCD.locate(9,4);
                                uLCD.printf("On ");    
                            } else {
                                uLCD.locate(9,4);
                                uLCD.printf("Off");    
                            }
                            isSoundChanged = false;
                        }
                        if (isDifficultyChanged) {
                            if (isGameDifficult) {
                                uLCD.locate(14,8);
                                uLCD.printf("High");
                            } else {
                                uLCD.locate(14,8);
                                uLCD.printf("Low ");    
                            }
                            isDifficultyChanged = false;
                        }
                    }
                }
            }
                break;
            case game: {
                setup();
                if (isSoundOn) {
                    playGo();
                }
                while (gameState == game) {
                    redraw();
                    setStop();
                    checkStop();
                    setSpeed();
                    checkSpeed();
                    if (didLose) {
                        gameState = lose;
                    }
                }
                break;
            }
            case lose: {
                red = 0;
                green = 0;
                uLCD.cls();
                uLCD.locate(0,0);
                uLCD.color(RED);
                switch(didLose) {
                    case 1:
                        uLCD.printf("You lose!\n You didn't stop\n in time!");
                        break;
                    case 2: 
                        uLCD.printf("You lose!\n You must obey\n speed limits!");
                        break;
                    case 3:
                        uLCD.printf("You lose!\n You shouldn't \n crash into things");
                        break;
                }
                didLose = 0;
                if (isSoundOn) {
                    rewind(lose_File);
                    bool asd = false;
                    waver.play(lose_File, &asd);
                } else {
                    wait(5);    
                }
                isStateChanged = false;
                gameState = menu;
                uLCD.color(GREEN);
            }
            break;
            case quit: {
                uLCD.media_init();
                uLCD.set_sector_address(0x00, 0x41);
                uLCD.display_image(0,0);
                if (isSoundOn) {
                    FILE *wave_file;
                    wave_file = fopen("/sd/mydir/GoodBye.wav", "r");
                    if (wave_file == NULL) {
                        printf("cannot open the file\n\r");  
                    }
                    bool asd = false;
                    waver.play(wave_file, &asd);
                    fclose(wave_file);
                }
                fclose(go_File);
                fclose(stop_File);
                fclose(lose_File);
                wait(10);
                uLCD.cls();
                exit(0);
                break;
            }
        }
    }
    
}

Video Demo


Please log in to post comments.