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:
- 5-way digital tactile switch
- 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 Device | Menus (Before Game) | During Game |
---|---|---|
5-Way Tactile Switch | Up/Down/Center to Navigate | Up/Down = Change Lanes... Center = Stop/Go |
IR Proximity Sensor | N/A | Closer = Faster, Farther = Slower |
Component List
- 1x Red LED
- 1x Green LED
- 1x IR Distance Sensor w/ Cable
- 1x Class D Audio Amp Breakout
- 1x Small 8-Ohm Speaker
- 1x 5-Way Tactile Switch
- 1x uLCD Display
- 1x Trimpot 10K with Knob
- 1x microSD Breakout
Hookup Guide
mbed | Device |
---|---|
GND | Breadboard Ground Rail |
VOUT | Breadboard 3.3V Rail |
VU | Breadboard 5V Rail |
p5 | microSD DI |
p6 | microSD DO |
p7 | microSD SCK |
p8 | microSD CS |
p12 | 5-Way Switch D |
p14 | 5-Way Switch C |
p15 | 5-Way Switch U |
p18 | Audio Amp IN+ |
p20 | IR Sensor Out |
p21 | Green LED (+ 100Ohm resistor) |
p22 | Red LED (+100Ohm resistor) |
p27 | uLCD TX |
p28 | uLCD RX |
p30 | uLCD RES |
microSD | |
---|---|
GND | Ground Rail |
VCC | 3.3V Supply Rail |
5 | Way Tactile Switch |
---|---|
- | Ground Rail |
+ | 3.3V Supply Rail |
Class D | Audio Amp Breakout |
---|---|
IN- | Ground Rail |
PWR+ | 3.3V Supply Rail |
PWR- | Ground Rail |
VOL | 10K Trimpot |
OUT- | Speaker- |
OUT+ | Speaker+ |
IR Sensor | |
---|---|
Vin | 5V Supply Rail |
GND | Ground Rail |
uLCD | |
---|---|
+5V | 5V Supply Rail |
GND | Ground Rail |
Connection Image
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(¢er_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.