ELEC2645 (2015/16)
/
Dodgemania
Wall dodging game utilising a joystick and Nokia 5110 LCD display
Diff: main.cpp
- Revision:
- 10:dd2886f3ac0a
- Parent:
- 9:ca800196baeb
diff -r ca800196baeb -r dd2886f3ac0a main.cpp --- a/main.cpp Sun Apr 24 16:17:55 2016 +0000 +++ b/main.cpp Thu May 05 14:52:59 2016 +0000 @@ -1,430 +1,33 @@ -/* Joystick -Max Hamilton -My project will be designing a game in which the player must avoid a number -of obstacles coming from different locations for as long as possible - -Week 19 Code - Initial test of mbed screen and initilisations -Week 20 Code - Add code to move a player controlled ball around the screen -Week 21 Code - Add code to generate and move an obstacle down the screen - -Week 22 Code - Significant progress over easter holiday - - Diagonal directions added to joystick - - Collisions implemented. Game over triggers if character hits a wall - - Walls/obstacles developed greatly - - Obstacle extended into full wall. Wall structs created and walls travel from all sides of the screen - - Walls start of appearing from one side of the screen. As time goes on, more will appear from differnt sides, with multiple walls on screen at once - - Cooldown added to walls to ensure a new one will not instantly appear from a side that another wall has just travelled to. - - visual warning on screen when walls are about to appear from a new direction - - Game now keeps track of score and displays it at the end of the game - - Intro screen added - -Week 23 Code - Brief invincibility added by pressing joybutton - - invincibilty limit added, indicators added to the side - - game loops succesfully - - Menu added with 4 options - - DodgeMILD, start the game easy with one wall - - DodgeMANIA, start the game hard with all four walls - - Help, get instructions on the game - - Scores, see the top 3 highscore, will be used for saved scores if SD card is implemented - -NOTES - top wall collision seems to be off - - horizontal walls still seem to go beyond the left border occasinanly - -*/ - -#include "mbed.h" -#include "N5110.h" -#include "tones.h" - -#define DIRECTION_TOLERANCE 0.05 // tolerance of joystick direction -#define PLAYERRADIUS 2 // size of player ball -#define MUTE 0 // 0 - buzzer plays, 1 - buzzer muted - -// VCC, SCE, RST, D/C, MOSI, SCLK, LED -N5110 lcd (PTD3 , PTA0 , PTC4 , PTD0 , PTD2 , PTD1 , PTC3); - -AnalogIn backlight(PTB2); // pot to control brightness -DigitalIn button(PTB3); // joystick button object -AnalogIn xPot(PTB11); // joystick x direction object -AnalogIn yPot(PTB10); // joystick y direction object -PwmOut buzzer(PTA2); // buzzer object -InterruptIn flick (PTB3); // interruptin instance of button - -Ticker pollJoystick; // timer to regularly read the joystick -Ticker menu_pollJoystick; // slower ticker to move joystick through menu -Ticker game_timer; // timer to regularly update the screen - -Serial serial(USBTX,USBRX); // Serial for debug - -// create enumerated type (0,1,2,3 etc. for direction) -enum DirectionName { - UP, - DOWN, - LEFT, - RIGHT, - UPRIGHT, // Diagonally up + right - UPLEFT, // Diagonally up + left - DOWNRIGHT, // Diagonally down + right - DOWNLEFT, // Diagonally down + left - CENTRE, - UNKNOWN -}; - -typedef struct JoyStick Joystick; // struct for Joystick -typedef struct Wall Wall; // struct for Walls -typedef const struct State STyp; -typedef struct Highscore Highscore; - -struct JoyStick { - float x; // current x value - float x0; // 'centred' x value - float y; // current y value - float y0; // 'centred' y value - int button; // button state (assume pull-down used, so 1 = pressed, 0 = unpressed) - DirectionName direction; // current direction -}; - -struct Wall { - int x; // x-coordinate of wall (realtive to centre of the gap) - int y; // y-coordinate of wall (relative to centre of the gap) - DirectionName direction; // Direction the wall travels in - int random; // randomly generated integer to determine when a wall begins to travel - int cooldown; // stops a wall respawning before a certain amount of time - volatile bool moveFlag; // flag to determine if wall is on screen - volatile bool genFlag; // flag to determine if wall has been generated -}; - -struct State { - int output; // output value for current state - int next_state[3]; // next state (depending on direction 0 - UP, 1 - DOWN) -}; - -struct Highscore { - int one; - int two; - int three; -}; - -STyp fsm[4] = { - {1,{0,1,0}}, - {2,{1,2,0}}, - {4,{2,3,1}}, - {8,{3,3,2}} -}; - -// struct variable for joystick -Joystick joystick; -// struct variable for moving walls -Wall leftwall; -Wall rightwall; -Wall downwall; -Wall upwall; - -Highscore hiscore; - -void calibrateJoystick(); // read default positions of the joystick to calibrate later readings -void updateJoystick(); // reads direction the joystick has been moved -void initDisplay(); // initialises the LCD display -void game_timer_isr(); // sets flag for timer interrupt -void initGame(); -void moveBall(); // reads joystick direction and moves position of the player -void moveWall(); // moves walls along the screen -void invincible(); // makes player briefly invincible -void checkWallCollision(); // checks for any collisions with wall -void checkBorderCollision(); // checks for any collisions with border -void updateScreen(); // refreshes the screen, redraws player and walls -void warning(); // flashes screen when a new wall is ready to appear -void initSerial(); // sets baud rate for serial -void debug(); // prints for debug purposes -void button_isr(); -void blink(); // function to make screen flash (do not put in isr unless breaking the loop) -void printHelp(); // prints game help -void printScores(); -void draw_border(); -void calculateHighscores(); -void initHiscores(); -void playNote(int freq, float time); - -//float refresh_rate = 20; // how often to update display (Hz) -//float g_dt = 1.0F/refresh_rate; // global to store time step (F makes it a float, gets rid of compiler warning) -volatile int g_timer_flag = 0; // flag for timer interrupt -volatile int game_over_flag = 0; // flag to signal game over -volatile int g_button_flag = 0; -int printFlag = 0; // flag for printing -volatile int game_start_flag = 0; // flag to start the game -int i; // x-coordinate value of player -int j; // y-coordinate value of player -int counter = 0; // number of times code has looped -volatile bool mortal = 1; -int invun_cool = 0; -int saves = 5; -int joyspeed = 5; -int score = 0; - -int direction; -int state; -int output; +#include "main.h" int main() { - initSerial(); - srand(time(NULL)); // generate seed for random number generation - calibrateJoystick(); - initDisplay(); - initHiscores(); - - wait (1.0); - - flick.rise(&button_isr); - flick.mode(PullDown); - - lcd.printString("Dodgemania",13,2); // Print game title on screen - wait (2.5); - for (int z=0; z<88; z++) { - lcd.drawCircle(z,20,4,1); - - lcd.clearPixel(z-3,16); - lcd.clearPixel(z-4,17); - lcd.clearPixel(z-4,18); - lcd.clearPixel(z-5,19); - lcd.clearPixel(z-5,20); - lcd.clearPixel(z-5,21); - lcd.clearPixel(z-4,22); - lcd.clearPixel(z-4,23); - lcd.clearPixel(z-3,24); - lcd.refresh(); - wait(0.01); - } - - lcd.clear(); - wait(0.5); - - blink(); + initSerial(); // Initialises serial port + srand(time(NULL)); // Generate seed for random number generation + calibrateJoystick(); // Zeroes current position of joystick + button.rise(&button_isr); // Attach button to ISR function + button.mode(PullDown); // Select button mode + initDisplay(); // Initialise LCD display + initHiscores(); // Initialise highscores + introScreen(); // Plays intro animation with title + game_running = 0; while(1) { - g_button_flag = 0; - - joyspeed = 10; - game_timer.detach(); - game_timer.attach(&game_timer_isr,1.0/joyspeed); - - pollJoystick.detach(); - pollJoystick.attach(&updateJoystick,1.0/joyspeed); // read joystick (JOYSPEED) times per second - - state = 0; - - - while(game_start_flag == 0) // ticker interrupt - if (g_timer_flag) { - g_timer_flag = 0; // clear flag - - if (joystick.direction == UP) { - direction = 2; - } else if (joystick.direction == DOWN) { - direction = 1; - } else { - direction = 0; - } - - output = fsm[state].output; - state = fsm[state].next_state[direction]; - - lcd.clear(); - - lcd.printString("Dodgemild",12,1); - lcd.printString("DodgeMANIA!",12,2); - lcd.printString("Help",12,3); - lcd.printString("Scores",12,4); - - if (output == 1) { - lcd.drawCircle(6,11,2,1); - } else if (output == 2) { - lcd.drawCircle(6,19,2,1); - } else if (output == 4) { - lcd.drawCircle(6,27,2,1); - } else { - lcd.drawCircle(6,35,2,1); - } - lcd.refresh(); - - if (((direction == 1)&&(output != 8))||((direction == 2)&&(output != 1))) { - playNote(NOTE_G2,0.05); - } + g_button_flag = 0; // force flag off to prevent menu items being accidently selected + setTickers(10); // calls ticker functions 10 times per second (slower speed to make menu control easier) + state = 0; // start on top menu item by default - if (g_button_flag) { - if (output == 1) { - g_button_flag = 0; - playNote(NOTE_B2,0.1); - initGame(); - game_start_flag = 1; - } else if (output == 2) { - g_button_flag = 0; - playNote(NOTE_B2,0.1); - initGame(); - counter = 1501; - game_start_flag = 1; - } else if (output == 4) { - g_button_flag = 0; - playNote(NOTE_B2,0.1); - blink(); - lcd.clear(); - printHelp(); - } else if (output == 8) { - g_button_flag = 0; - playNote(NOTE_B2,0.1); - blink(); - lcd.clear(); - printScores(); - } - } - } - blink(); - lcd.clear(); -// Draw game border - draw_border(); -// Countdown - wait(0.5); - lcd.printString("3",40,2); - playNote(NOTE_C4,0.2); - wait(0.3); - lcd.drawRect(10,10,64,28,2); - lcd.refresh(); - wait(0.5); - lcd.printString("2",40,2); - playNote(NOTE_C4,0.2); - wait(0.3); - lcd.drawRect(10,10,64,28,2); - lcd.refresh(); - wait(0.5); - lcd.printString("1",40,2); - playNote(NOTE_C4,0.2); - wait(0.3); - lcd.drawRect(10,10,64,28,2); - lcd.refresh(); - wait(0.5); - lcd.drawRect(10,10,64,28,2); - lcd.refresh(); - lcd.printString("Go!",36,2); - playNote(NOTE_G4,0.5); - lcd.drawRect(10,10,64,28,2); - lcd.refresh(); + menu(); // Brings up game menu (Help and Scores contained in menu() function, selecting a gameplay option moves on from menu) - joyspeed = 20; - game_timer.detach(); - game_timer.attach(&game_timer_isr,1.0/joyspeed); - pollJoystick.detach(); - pollJoystick.attach(&updateJoystick,1.0/joyspeed); - - while (game_over_flag == 0) { - - if ( g_timer_flag ) { // ticker interrupt - g_timer_flag = 0; // clear flag - moveWall(); - moveBall(); - invincible(); - checkBorderCollision(); - checkWallCollision(); - updateScreen(); - warning(); - debug(); - - counter++; // increment counter each cycle (approx. 20 points a second) - score++; // this is seperate from counter for the purposes of keeping score 0 while all walls are present on DodgeMANIA - - // wall cooldowns increased. N.B these are set to 0 when a wall finishes moving - leftwall.cooldown++; - rightwall.cooldown++; - downwall.cooldown++; - upwall.cooldown++; - } - sleep(); - } - - game_over_flag = 0; - - blink(); - playNote(NOTE_E4,0.1); - playNote(NOTE_C4,0.1); - playNote(NOTE_A3,0.1); - playNote(NOTE_A2,0.3); - wait(0.8); - lcd.clear(); - wait(0.2); - - calculateHighscores(); - - lcd.printString("Game Over!",14,0); - lcd.refresh(); - - wait(1.0); - - lcd.printString("HiScore:",3,3); - lcd.printString("Score:",3,2); + setTickers(20); // Calls ticker functions 20 times per second - char score_buffer[14]; - char hiscore_buffer[14]; - int length_one = sprintf(score_buffer,"%d",score); - int length_two = sprintf(hiscore_buffer,"%d",hiscore.one); - if (score <= 9999) { // 9999 is highest number that fits on screen - lcd.printString(score_buffer,54,2); - lcd.printString(hiscore_buffer,54,3); - } else { - lcd.printString ("Wow!",54,2); // if score is too large to fit in box - lcd.printString ("Wow!",54,3); - } - - lcd.refresh(); - - if (score >= hiscore.one) { - wait(1.0); - lcd.printString("HIGHSCORE!",13,5); - playNote(NOTE_E3,0.1); - wait(0.1); - playNote(NOTE_C3,0.05); - wait(0.05); - playNote(NOTE_C3,0.05); - wait(0.05); - playNote(NOTE_C3,0.05); - wait(0.15); - playNote(NOTE_G3,0.1); - wait(0.1); - playNote(NOTE_E3,0.1); - wait(0.1); - playNote(NOTE_G3,0.1); - wait(0.1); - playNote(NOTE_C4,0.5); - wait(0.1); - lcd.printString("CONGRATS!!",13,5); - playNote(NOTE_E2,0.2); - - } else { - playNote(NOTE_A2,0.05); - wait(0.05); - playNote(NOTE_A2,0.05); - wait(0.05); - playNote(NOTE_A2,0.05); - wait(0.05); - playNote(NOTE_A2,0.3); - } - lcd.refresh(); - while(1) { - if (g_button_flag) { - g_button_flag = 0; - playNote(NOTE_E2,0.1); - blink(); - lcd.clear(); - break; - } - } - game_start_flag = 0; - -//return 0; + playGame(); // Begins gameplay + resultsScreen(); // Show final player score and previous highscore } - } -void moveBall() // reads joystick direction and moves position of the player +void moveBall() // Reads joystick direction and moves position of the player { if (joystick.direction == UP) { j-=1; @@ -449,50 +52,51 @@ } } -void moveWall() // moves walls along the screen +void moveWall() // Moves walls along the screen { - leftwall.random = rand()%20; + // Random variables to determine if wall moves this loop or not + leftwall.random = rand()%20; // 1/20 chance rightwall.random = rand()%20; - downwall.random = rand()%50; + downwall.random = rand()%50; // 1/50 chance upwall.random = rand()%50; // LEFT WALL - if (leftwall.moveFlag == 1) { // if wall is moving - leftwall.x-=1; // move wall left - if (leftwall.x<8) { // if wall hits a border - leftwall.moveFlag = 0; // stop wall moving - leftwall.cooldown = 0; // reset the cooldown for the wall + if (leftwall.moveFlag == 1) { // if wall is moving + leftwall.x-=1; // move wall left + if (leftwall.x<8) { // if wall hits a border + leftwall.moveFlag = 0; // stop wall moving + leftwall.cooldown = 0; // reset the cooldown for the wall } - } else { // if wall has stopped moving - if (leftwall.genFlag == 0) { // if a new wall HASN'T been generated - leftwall.y = rand() % 27+8; // make new random y-coordinate - leftwall.x = 82; // reset x-coordinate to rightmost position - leftwall.genFlag = 1; // wall has been generated - } else { // if a new wall HAS been generated - if (leftwall.cooldown > 80) { // if a new wall hasnt started moving in 4 seconds, force it to move - leftwall.moveFlag = 1; - leftwall.genFlag = 0; - } else if ((leftwall.random == 1)&&(rightwall.cooldown > 60)) { // if wall starts moving again - leftwall.moveFlag = 1; // start wall moving - leftwall.genFlag = 0; // clear 'wall generated' flag - } else { // else if wall has not started moving again - leftwall.moveFlag = 0; // wall is stopped + } else { // if wall has stopped moving + if (leftwall.genFlag == 0) { // if a new wall HASN'T been generated + leftwall.y = rand() % 27+8; // make new random y-coordinate (note - range of random values prevents holes in the walls going beyond border) + leftwall.x = 82; // reset x-coordinate to rightmost position + leftwall.genFlag = 1; // wall has been generated + } else { // if a new wall HAS been generated + if (leftwall.cooldown > 80) { // if new wall hasnt started moving in 4 seconds, force it to move + leftwall.moveFlag = 1; // start wall moving + leftwall.genFlag = 0; // clear 'wall generated' flag + } else if ((leftwall.random == 1)&&(rightwall.cooldown > 60)) { // else 2 second window in which wall may randomly start moving before it is forced to + leftwall.moveFlag = 1; // start wall moving + leftwall.genFlag = 0; // clear 'wall generated' flag + } else { // else if wall has not started moving again + leftwall.moveFlag = 0; // wall is stopped } } } // RIGHT WALL - if (counter > 200) { + if (counter > 200) { // After 10 seconds of gameplay if (rightwall.moveFlag == 1) { - rightwall.x+=1; - if (rightwall.x>80) { + rightwall.x+=1; // move wall right + if (rightwall.x>80) { // if wall goes off right edge (accounting for border) rightwall.moveFlag = 0; rightwall.cooldown = 0; } } else { if ((rightwall.genFlag == 0)) { - rightwall.y = rand() % 27+8; - rightwall.x = 6; + rightwall.y = rand() % 27+8; // random y-coordinate (note - range of random values prevents holes in the walls going beyond border) + rightwall.x = 6; // moves wall back to left side rightwall.genFlag = 1; } else { if (rightwall.cooldown > 80) { @@ -509,23 +113,21 @@ } // DOWN WALL - if (counter > 600) { + if (counter > 600) { // After 30 seconds of gameplay if (downwall.moveFlag == 1) { - if (upwall.cooldown > 60) { - if (counter % 2 == 1) { // horizontal walls move half the speed of vertical walls - downwall.y+=1; - } + if (counter % 2 == 1) { // horizontal walls move half the speed of vertical walls + downwall.y+=1; // move wall down } - if (downwall.y>44) { + if (downwall.y>44) { // if wall goes off bottom edge (accounting for border) downwall.moveFlag = 0; } } else { if (downwall.genFlag == 0) { - downwall.x = rand() % 58+13; + downwall.x = rand() % 52+19; // random x-coordinate (note - range of random values prevents holes in the walls going beyond border) downwall.y = 1; downwall.genFlag = 1; } else { - if (downwall.cooldown > 80) { + if (downwall.cooldown > 120) { // 6s cooldown downwall.moveFlag = 1; downwall.genFlag = 0; } else if ((downwall.random == 1)&&(upwall.cooldown > 60)) { @@ -538,24 +140,22 @@ } } -// UP WALL - if (counter > 1500) { + // UP WALL + if (counter > 1200) { // After 60 seconds of gameplay if (upwall.moveFlag == 1) { - if (downwall.cooldown > 60) { - if (counter % 2 == 1) { // horizontal walls move half the speed of vertical walls - upwall.y-=1; - } + if (counter % 2 == 1) { // horizontal walls move half the speed of vertical walls + upwall.y-=1; // move wall up } - if (upwall.y<3) { + if (upwall.y<3) { // if wall goes off bottom edge (accounting for border) upwall.moveFlag = 0; } } else { if (upwall.genFlag == 0) { - upwall.x = rand() % 58+13; + upwall.x = rand() % 52+19; // random x-coordinate (note - range of random values prevents holes in the walls going beyond border) upwall.y = 46; upwall.genFlag = 1; } else { - if (upwall.cooldown > 80) { + if (upwall.cooldown > 120) { // 6s cooldown upwall.moveFlag = 1; upwall.genFlag = 0; } else if ((upwall.random == 1)&&(downwall.cooldown > 60)) { @@ -568,71 +168,75 @@ } } } -void checkBorderCollision() +void checkBorderCollision() // Checks if player has hit border { // if floor if ( j >= 47 - (PLAYERRADIUS+3)) { - j = 47 - (PLAYERRADIUS+3); + j = 47 - (PLAYERRADIUS+3); // Forces player position } // if roof if ( j <= (PLAYERRADIUS+3)) { - j = (PLAYERRADIUS+3); + j = (PLAYERRADIUS+3); // Forces player position } // if right wall if ( i >= 83 - (PLAYERRADIUS+3)) { - i = 83 - (PLAYERRADIUS+3); + i = 83 - (PLAYERRADIUS+3); // Forces player position } // if left wall if ( i <= (PLAYERRADIUS+8)) { - i = (PLAYERRADIUS+8); + i = (PLAYERRADIUS+8); // Forces player position } } -void invincible() +void invincible() // Briefly allows player to move through walls { if (g_button_flag) { g_button_flag = 0; - if ((saves > 0) && (mortal == true)) { // saves are available and not currently used - invun_cool=0; + if ((saves > 0) && (mortal == true)) { // Invincibility is available and not currently used + saves_cool=0; mortal = false; - invun_cool++; + saves_cool++; saves--; } } if (mortal == false) { - invun_cool++; - if (invun_cool > 30) { // ticker called 20 times per second, therefore 1.5s of invincibility + saves_cool++; // Countdown variable for invincibility + if (saves_cool > 30) { // Ticker called 20 times per second, therefore 1.5s of invincibility mortal = true; - invun_cool=0; + saves_cool=0; // Resets countdown variable } } } -void checkWallCollision() // checks for any collisions (i.e. player has hit a wall or side of the screen) +void checkWallCollision() // Checks for any collisions (i.e. player has hit a wall or side of the screen) { - if (mortal) { + if (mortal) { // If player has not triggered invincibility // LEFT WALL + // (- - - - - - - If wall has passed inside of player radius - - - - - - -) (- - If player has hit side of wall gap - -) if ((((i - PLAYERRADIUS) <= leftwall.x) && (leftwall.x <= (i + PLAYERRADIUS))) && (j > (leftwall.y+3) || j < (leftwall.y-3))) { - game_over_flag = 1; + game_running = 0; } // RIGHT WALL + // (- - - - - - - If wall has passed inside of player radius - - - - - - -) (- - If player has hit side of wall gap - -) if ((((i - PLAYERRADIUS) <= rightwall.x) && (rightwall.x <= (i + PLAYERRADIUS))) && (j > (rightwall.y+3) || j < (rightwall.y-3))) { - game_over_flag = 1; + game_running = 0; } // DOWN WALL - if ((((j - PLAYERRADIUS) <= downwall.y) && (downwall.y <= (j - PLAYERRADIUS))) && (i > (downwall.x+9) || i < (downwall.x-9))) { // if player x is 4 or more than wall x, it has missed the gap - game_over_flag = 1; + // (- - - - - - - If wall has passed inside of player radius - - - - - - -) (- - If player has hit side of wall gap - -) + if ((((j - PLAYERRADIUS) <= downwall.y) && (downwall.y-1 <= (j + PLAYERRADIUS))) && (i > (downwall.x+9) || i < (downwall.x-9))) { // if player x is 4 or more than wall x, it has missed the gap + game_running = 0; } // UP WALL - if (((j + PLAYERRADIUS) == upwall.y || (j - PLAYERRADIUS) == upwall.y) && (i > (upwall.x+9) || i < (upwall.x-9))) { // if player x is 4 or more than wall x, it has missed the gap - game_over_flag = 1; + // (- - - - - - - If wall has passed inside of player radius - - - - - - -) (- - If player has hit side of wall gap - -) + if ((((j - PLAYERRADIUS) <= upwall.y+1) && (upwall.y <= (j + PLAYERRADIUS))) && (i > (upwall.x+9) || i < (upwall.x-9))) { + game_running = 0; } } } @@ -643,12 +247,12 @@ if (mortal) { lcd.drawCircle(i,j,PLAYERRADIUS,1); } else { - if (counter % 2 == 0) { + if (counter % 2 == 0) { // Make player blink if invincible lcd.drawCircle(i,j,PLAYERRADIUS,1); } } - lcd.refresh(); // update display + // Draws remaining invincibilty indicators if (saves > 0) { lcd.drawCircle(2,7,1,1); } @@ -665,11 +269,10 @@ lcd.drawCircle(2,39,1,1); } - - // draw Border + // Draw border draw_border(); - // draw walls + // Draw walls // LEFT WALL lcd.drawLine(leftwall.x,leftwall.y+5,leftwall.x,44,1); lcd.drawLine(leftwall.x,leftwall.y-5,leftwall.x,3,1); @@ -677,7 +280,7 @@ lcd.drawLine(leftwall.x+1,leftwall.y-5,leftwall.x+1,3,1); // RIGHT WALL - if (counter > 200) { + if (counter > 200) { // After 10 seconds of gameplay lcd.drawLine(rightwall.x,rightwall.y+5,rightwall.x,44,1); lcd.drawLine(rightwall.x,rightwall.y-5,rightwall.x,3,1); lcd.drawLine(rightwall.x-1,rightwall.y+5,rightwall.x-1,44,1); @@ -685,26 +288,22 @@ } // DOWN WALL - if (counter > 600) { + if (counter > 600) { // After 30 seconds of gameplay lcd.drawLine(downwall.x+11,downwall.y,80,downwall.y,1); lcd.drawLine(downwall.x-11,downwall.y,8,downwall.y,1); lcd.drawLine(downwall.x+11,downwall.y-1,80,downwall.y-1,1); lcd.drawLine(downwall.x-11,downwall.y-1,8,downwall.y-1,1); + } - // UP WALL - if (counter > 1500) { + if (counter > 1200) { // After 60 seconds of gameplay lcd.drawLine(upwall.x+11,upwall.y,80,upwall.y,1); lcd.drawLine(upwall.x-11,upwall.y,8,upwall.y,1); lcd.drawLine(upwall.x+11,upwall.y+1,80,upwall.y+1,1); lcd.drawLine(upwall.x-11,upwall.y+1,8,upwall.y+1,1); } - lcd.refresh(); -} - -void warning () -{ + // Flash screen if a wall is about to appear from a new direction if (counter == 170) { lcd.inverseMode(); } else if (counter == 570) { @@ -714,14 +313,15 @@ } else { lcd.normalMode(); } + lcd.refresh(); } -void game_timer_isr() // sets flag for timer interrupt +void gameTicker_isr() // Sets flag for timer interrupt { g_timer_flag = 1; } -void button_isr() +void button_isr() // Sets flag for button interrupt { g_button_flag = 1; } @@ -729,27 +329,28 @@ void initDisplay() // initialises the LCD display { lcd.init(); + wait(0.5); // wait for LCD to initialise lcd.normalMode(); - lcd.setBrightness(1.0F-backlight); // brightness pot on PCB is soldered in the wrong direction, (1.0F-backlight) inverts the reading + lcd.setBrightness(1.0F-backlight); // brightness pot on PCB is soldered in the wrong direction, (1.0F-backlight) inverts the reading } -void calibrateJoystick() // read default positions of the joystick to calibrate later readings +void calibrateJoystick() // Read default positions of the joystick to calibrate later readings { button.mode(PullDown); - // must not move during calibration - joystick.x0 = xPot; // initial positions in the range 0.0 to 1.0 (0.5 if centred exactly) + // Must not move during calibration + joystick.x0 = xPot; // Initial positions in the range 0.0 to 1.0 (0.5 if centred exactly) joystick.y0 = yPot; } -void updateJoystick() // reads direction the joystick has been moved +void updateJoystick() // Reads direction the joystick has been moved { - // read current joystick values relative to calibrated values (in range -0.5 to 0.5, 0.0 is centred) + // Read current joystick values relative to calibrated values (in range -0.5 to 0.5, 0.0 is centred) joystick.x = xPot - joystick.x0; joystick.y = yPot - joystick.y0; - // read button state + // Read button state joystick.button = button; - // calculate direction depending on x,y values - // tolerance allows a little lee-way in case joystick not exactly in the stated direction + // Calculate direction depending on x,y values + // Tolerance allows a little lee-way in case joystick not exactly in the stated direction if ( fabs(joystick.y) < DIRECTION_TOLERANCE && fabs(joystick.x) < DIRECTION_TOLERANCE) { joystick.direction = CENTRE; } else if ( joystick.y > DIRECTION_TOLERANCE && fabs(joystick.x) < DIRECTION_TOLERANCE) { @@ -773,54 +374,40 @@ else { joystick.direction = UNKNOWN; } - - - printFlag = 1; // set flag for printing } -void debug() // prints for debug purposes -{ - if (printFlag) { // if flag set, clear flag and print joystick values to serial port - printFlag = 0; - serial.printf("saves = %d \n",saves); - } -} - -void initSerial() // sets baud rate for serial +void initSerial() // Sets baud rate for serial { serial.baud(115200); } -void initGame() +void initGame() // Initialises gameplay variables { - g_button_flag = 0; leftwall.y = rand() % 27+8; - leftwall.x = 0; + leftwall.x = 82; rightwall.y = rand() % 27+8; rightwall.x = 6; - downwall.x = rand() % 58+13; + downwall.x = rand() % 52+19; downwall.y = 0; - upwall.x = rand() % 58+13; + upwall.x = rand() % 52+19; upwall.y = 0; rightwall.cooldown = 61; counter = 0; score = 0; - saves = 5; - i = 42; j = 24; } -void blink() // command for brief flash +void flash() // Produces a brief flash on screen { lcd.inverseMode(); wait(0.2); lcd.normalMode(); } -void printHelp() +void printHelp() // Prints instructions { while (1) { lcd.printString("Try and",0,1); @@ -829,9 +416,8 @@ lcd.printString("possible!",0,4); lcd.refresh(); if (g_button_flag) { - //g_button_flag = 0; playNote(NOTE_E2,0.1); - blink(); + flash(); lcd.clear(); break; } @@ -844,9 +430,8 @@ lcd.printString("the joystick",0,4); lcd.refresh(); if (g_button_flag) { - //g_button_flag = 0; playNote(NOTE_E2,0.1); - blink(); + flash(); lcd.clear(); break; } @@ -859,9 +444,8 @@ lcd.printString("untouchable",0,4); lcd.refresh(); if (g_button_flag) { - // g_button_flag = 0; playNote(NOTE_E2,0.1); - blink(); + flash(); lcd.clear(); break; } @@ -874,9 +458,8 @@ lcd.printString("do it 5 times",0,4); lcd.refresh(); if (g_button_flag) { - // g_button_flag = 0; playNote(NOTE_E2,0.1); - blink(); + flash(); lcd.clear(); break; } @@ -889,9 +472,8 @@ lcd.printString("the screen...",0,4); lcd.refresh(); if (g_button_flag) { - // g_button_flag = 0; playNote(NOTE_E2,0.1); - blink(); + flash(); lcd.clear(); break; } @@ -906,14 +488,14 @@ if (g_button_flag) { g_button_flag = 0; playNote(NOTE_E2,0.1); - blink(); + flash(); lcd.clear(); break; } } } -void printScores() +void printScores() // Prints previous scores { while(1) { lcd.printString("Highscores",13,0); @@ -936,14 +518,14 @@ if (g_button_flag) { g_button_flag = 0; playNote(NOTE_E2,0.1); - blink(); + flash(); lcd.clear(); break; } } } -void draw_border() // draws game border +void draw_border() // Draws game border { lcd.drawLine(7,2,81,2,1); lcd.drawLine(7,2,7,45,1); @@ -955,10 +537,15 @@ lcd.drawLine(83,0,83,47,1); lcd.drawLine(5,47,83,47,1); + lcd.drawRect(6,0,1,2,1); + lcd.drawRect(6,45,1,2,1); + lcd.drawRect(81,0,1,2,1); + lcd.drawRect(81,45,1,2,1); + lcd.refresh(); } -void calculateHighscores() +void calculateHighscores() // Determines if player score is a new highscore { if (score > hiscore.one) { hiscore.three = hiscore.two; @@ -972,22 +559,307 @@ } } -void initHiscores() +void initHiscores() // Initialises highscores { hiscore.one = 0; hiscore.two = 0; hiscore.three = 0; } -void playNote(int freq, float time) +void playNote(int freq, float time) // Plays a tone of specific frequency and duration { - if(!MUTE) { // no sound if MUTE - buzzer.period(1.0/freq); // 1 KHz + if(backlight < 0.5F) { // Use potentiometer to switch sound on and off + buzzer.period(1.0/freq); buzzer.write(0.0); buzzer.write(0.5); wait(time); buzzer.write(0.0); + } else { // If muted + wait(time); // Wait included so that function still takes the same time to execute + } +} + +void countdown() // Countdown for start of game +{ + wait(0.5); + lcd.printString("3",40,2); + playNote(NOTE_C4,0.2); + wait(0.3); + lcd.drawRect(10,10,64,28,2); + lcd.refresh(); + wait(0.5); + lcd.printString("2",40,2); + playNote(NOTE_C4,0.2); + wait(0.3); + lcd.drawRect(10,10,64,28,2); + lcd.refresh(); + wait(0.5); + lcd.printString("1",40,2); + playNote(NOTE_C4,0.2); + wait(0.3); + lcd.drawRect(10,10,64,28,2); + lcd.refresh(); + wait(0.5); + lcd.drawRect(10,10,64,28,2); + lcd.refresh(); + lcd.printString("Go!",36,2); + playNote(NOTE_G4,0.5); + lcd.drawRect(10,10,64,28,2); + lcd.refresh(); +} + +void introScreen() // Introduction animation +{ + wait (1.0); + lcd.printString("Dodgemania",13,2); // Print game title on screen + wait (2.5); + for (int z=0; z<88; z++) { + lcd.drawCircle(z,20,4,1); + lcd.clearPixel(z-3,16); + lcd.clearPixel(z-4,17); + lcd.clearPixel(z-4,18); + lcd.clearPixel(z-5,19); + lcd.clearPixel(z-5,20); + lcd.clearPixel(z-5,21); + lcd.clearPixel(z-4,22); + lcd.clearPixel(z-4,23); + lcd.clearPixel(z-3,24); + lcd.refresh(); + wait(0.01); + } + lcd.clear(); + wait(0.5); + flash(); +} + +void proceed() // Moves to selected menu option +{ + if (menu_item == 1) { + playNote(NOTE_B2,0.1); + initGame(); + game_running = 1; + } else if (menu_item == 2) { + playNote(NOTE_B2,0.1); + initGame(); + counter = 1200; // Note - initGame must be called here as calling it after the menu loop will reset the counter, ergo no hard mode + game_running= 1; + } else if (menu_item == 3) { + playNote(NOTE_B2,0.1); + flash(); + lcd.clear(); + printHelp(); + } else if (menu_item == 4) { + playNote(NOTE_B2,0.1); + flash(); + lcd.clear(); + printScores(); + } +} + +void setTickers(int speed) // Dettaches tickers and reattaches them with specified speed +{ + gameTicker.detach(); + gameTicker.attach(&gameTicker_isr,1.0/speed); + pollJoystick.detach(); + pollJoystick.attach(&updateJoystick,1.0/speed); +} + +void congratulate() // Congratulates player if they got a highscore +{ + if (score >= hiscore.one) { // New highscore + wait(1.0); + lcd.printString("HIGHSCORE!",13,5); + // Highscore melody + playNote(NOTE_E3,0.1); + wait(0.1); + playNote(NOTE_C3,0.05); + wait(0.05); + playNote(NOTE_C3,0.05); + wait(0.05); + playNote(NOTE_C3,0.05); + wait(0.15); + playNote(NOTE_G3,0.1); + wait(0.1); + playNote(NOTE_E3,0.1); + wait(0.1); + playNote(NOTE_G3,0.1); + wait(0.1); + playNote(NOTE_C4,0.5); + wait(0.1); + lcd.printString("CONGRATS!!",13,5); + playNote(NOTE_E2,0.2); + + } else if ((score >= hiscore.two) && (score < hiscore.one)) { // Second place + // Endgame melody + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.3); + + } else if ((score >= hiscore.three) && (score < hiscore.two)) { // Third place + // Endgame melody + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.3); + + } else { // No highscore + // Endgame melody + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.05); + wait(0.05); + playNote(NOTE_A2,0.3); + } +} + +void menu() +{ + while(game_running == 0) { // If game is not started + if (g_timer_flag) { + g_timer_flag = 0; + + if ((joystick.direction == UP)||(joystick.direction == UPRIGHT)||(joystick.direction == UPLEFT)) { + menu_direction = 2; + } else if ((joystick.direction == DOWN)||(joystick.direction == DOWNRIGHT)||(joystick.direction == DOWNLEFT)) { + menu_direction = 1; + } else { + menu_direction = 0; + } + + menu_item = fsm[state].output; // Which menu item the cursor is on + state = fsm[state].next_state[menu_direction]; // Moves up or down the menu, or stays on the same item, depending on joystick direction + lcd.clear(); + + lcd.printString("Start Easy",12,1); + lcd.printString("Start Hard",12,2); + lcd.printString("Help",12,3); + lcd.printString("Scores",12,4); + + // Place cursor next to appropriate menu item + if (menu_item == 1) { + lcd.drawCircle(6,11,2,1); + } else if (menu_item == 2) { + lcd.drawCircle(6,19,2,1); + } else if (menu_item == 3) { + lcd.drawCircle(6,27,2,1); + } else { + lcd.drawCircle(6,35,2,1); + } + lcd.refresh(); + + if (((menu_direction == 1)&&(menu_item != 4))||((menu_direction == 2)&&(menu_item != 1))) { + playNote(NOTE_G2,0.05); // Menu scrolling sound + } + + if (g_button_flag) { // If button press + g_button_flag = 0; + if (menu_item == 1) { + playNote(NOTE_B2,0.1); + initGame(); + game_running = 1; + } else if (menu_item == 2) { + playNote(NOTE_B2,0.1); + initGame(); + counter = 1200; // Note - initGame must be called here as calling it after the menu loop will reset the counter, ergo no hard mode + game_running= 1; + } else if (menu_item == 3) { + playNote(NOTE_B2,0.1); + flash(); + lcd.clear(); + printHelp(); + } else if (menu_item == 4) { + playNote(NOTE_B2,0.1); + flash(); + lcd.clear(); + printScores(); + } + } + } + } + + // When one of the two gameplay options are selected, the loop is broken and moves onto countdown/gameplay + flash(); + lcd.clear(); + draw_border(); // Draw game border + countdown(); // Countdown to game start +} + +void playGame() +{ + while (game_running == 1) { // Gameplay loop + + if ( g_timer_flag ) { // Ticker interrupt + g_timer_flag = 0; // Clear flag + moveWall(); // Move wall obstacles across screen + moveBall(); // Move player + invincible(); // Check if invincibility has been enabled + checkBorderCollision(); // Check if player has hit a border + checkWallCollision(); // Check if player has hit a wall + updateScreen(); // Redraw screen with new object positions + + counter++; // Increment counter each cycle (approx. 20 points a second) + score++; // This is seperate from counter for the purposes of keeping score 0 when Hard Start is selected to allow 4 walls from the start + + // Increase wall cooldown variable + leftwall.cooldown++; + rightwall.cooldown++; + downwall.cooldown++; + upwall.cooldown++; + } + sleep(); // Put processor to sleep till next interrupt + } + + // Briefly freezes screen and plays game-over melody if player hits wall + flash(); + playNote(NOTE_E4,0.1); + playNote(NOTE_C4,0.1); + playNote(NOTE_A3,0.1); + playNote(NOTE_A2,0.3); + wait(0.9); + lcd.clear(); + wait(0.2); +} + +void resultsScreen() +{ + calculateHighscores(); // Determines if player has a new highscore + lcd.printString("Game Over!",14,0); + lcd.refresh(); + wait(1.0); + lcd.printString("HiScore:",3,3); + lcd.printString("Score:",3,2); + + char score_buffer[14]; + char hiscore_buffer[14]; + int length_one = sprintf(score_buffer,"%d",score); + int length_two = sprintf(hiscore_buffer,"%d",hiscore.one); + if (score <= 9999) { // 9999 is highest number that fits on screen + lcd.printString(score_buffer,54,2); + lcd.printString(hiscore_buffer,54,3); } else { - wait(time); // wait included so that function still takes the same time to execute + lcd.printString ("Wow!",54,2); // if score is too large to fit in box, print text instead (would require 8.3 minutes of gameplay...) + lcd.printString ("Wow!",54,3); + } + + lcd.refresh(); + congratulate(); // Congratulates the player if they have a new highscore + lcd.refresh(); + while(1) { // Remain on screen until button is pressed + if (g_button_flag) { + g_button_flag = 0; + playNote(NOTE_E2,0.1); + flash(); + lcd.clear(); + break; + } } } \ No newline at end of file