Final Submission. I have read and agreed with Statement of Academic Integrity.

Dependencies:   mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /*
00002 ELEC2645 Embedded Systems Project
00003 School of Electronic & Electrical Engineering
00004 University of Leeds
00005 Name: Nicholas Wu
00006 Username: el18nw
00007 Student ID Number: 201275179
00008 Date: 22/5/2020
00009 */
00010 
00011 #include "mbed.h"
00012 #include "Gamepad.h"
00013 #include "N5110.h"
00014 #include "Bitmap.h"
00015 #include "Spaceship.h"
00016 
00017 // IN-GAME DEBUGGING FLAGS
00018 bool DEBUG_menu = 0;
00019 bool sound_on = 1;
00020 // During development a mixture of serial port and in-game debugging methods
00021 // I found in-game debugging to be more interesting and accessible
00022 // Thus I had left these two debugging options here as examples
00023 
00024 // OBJECTS
00025 Gamepad pad;
00026 N5110 lcd;
00027 Bitmap AGALAG(title, 10, 48); // name, y and x width
00028 Bitmap SHIP0(player_ship0, 9, 12);
00029 Bitmap SHIP1(player_ship1, 9, 12);
00030 Spaceship player;
00031 Spaceship enemy[2];
00032 // Declaring enemies as an array makes adding more enemies simple
00033 // for this platform 2 allows for a playable framerate
00034 
00035 // FUNCTIONS & RESPECTIVE VARIABLES
00036 void dramatic_print(string, int, int, int);
00037     int temptime = clock();
00038     char g_buffer[14] = "0";
00039 void title_sequence();
00040     float g_x = 0, g_y = 0;
00041 void create_stars();
00042     bool g_star_map[224][224];
00043 bool check_stars(int, int);
00044 void print_background(float, float, float);
00045 void menu();
00046 void help();
00047 void ship_select();
00048     int player_ship_type = 0;
00049     bool difficulty = 0;
00050     bool god_mode = 0;
00051 void print_menu(int, int);
00052 void level_system();
00053     int points = 0;
00054 void engine_setup(bool);
00055 void engine(bool, float);
00056 void render_objects(int);
00057 void draw_enemy(int, int, int);
00058 void effects(bool, int, float);
00059 void sound(bool);
00060 bool explode(int, int);
00061 void print_tracker(int i);
00062 
00063 int main() {
00064     lcd.init();
00065     lcd.inverseMode();
00066     lcd.setContrast(0.48);
00067     pad.init();
00068     lcd.backLightOn();
00069     create_stars();
00070     title_sequence();
00071     while (1) menu();
00072 }
00073 
00074 void menu() {
00075     int menu = 0, selection = 0;
00076     
00077     while (1) {
00078         lcd.clear();
00079         print_background(1, 0, 0);
00080         print_menu(menu, selection);
00081         AGALAG.render(lcd, 18, 5);
00082         
00083         if (pad.B_held()) selection++;
00084         if (pad.X_held()) selection--;
00085         while (pad.B_held() || pad.X_held());
00086         if (selection < 0) selection = 3;
00087         else if (selection > 3) selection = 0;
00088         if (DEBUG_menu) {
00089             sprintf(g_buffer, " %1i,%1i", menu, selection);
00090             lcd.printString(g_buffer, 0, 5);
00091         }
00092         lcd.refresh();
00093         
00094         if (pad.A_held() || pad.Y_held()) {
00095             if (menu == 3) {
00096                 if (selection == 0) sound_on = !sound_on;
00097                 if (selection == 1) points += 100;;
00098                 if (selection == 2) DEBUG_menu = !DEBUG_menu;
00099                 if (selection == 3) menu = 0;
00100             }else if (menu==1) {
00101                 if (selection == 0) ship_select();
00102                 if (selection == 1) god_mode =! god_mode;
00103                 if (selection == 2) difficulty =! difficulty;
00104                 if (selection == 3) menu = 0;
00105             }else if (menu == 0) {
00106                 if (selection == 0) level_system();
00107                 if (selection == 1) menu = 1;
00108                 if (selection == 2) help();
00109                 if (selection == 3) menu = 3;
00110             }
00111             //this while statement captures the frame so no more than
00112             //one increment could happen with each button press
00113             while (pad.A_held() || pad.Y_held());
00114         }
00115     }
00116 }
00117 
00118 void print_menu(int menu, int selection) {
00119     //selected menu item is denoted as such: >example<
00120     if (menu == 0) {
00121         if (selection == 0) lcd.printString(">PLAY!<", 21, 2);
00122         else lcd.printString("PLAY!", 27, 2);
00123         if (selection == 1) lcd.printString(">OPTIONS<", 15, 3);
00124         else lcd.printString("OPTIONS", 21, 3);
00125         if (selection == 2) lcd.printString(">CONTROLS<", 12, 4);
00126         else lcd.printString("CONTROLS", 18, 4);
00127         if (selection == 3) lcd.printString(">DEBUG<", 21, 5);
00128         else lcd.printString("DEBUG", 27, 5);
00129     }
00130     if (menu == 1) {
00131         if (selection == 0) lcd.printString(">SHIP=0<", 18, 2);
00132         else lcd.printString("SHIP=0", 24, 2);
00133         lcd.printChar(player_ship_type+49, 54, 2);
00134         if (selection == 1) lcd.printString(">GOD MODE=0<", 6, 3);
00135         else lcd.printString("GOD MODE=0", 12, 3);
00136         lcd.printChar(48+god_mode, 66, 3);
00137         if (selection == 2) lcd.printString(">EASY MODE<", 9, 4);
00138         else lcd.printString("EASY MODE", 15, 4);
00139         if (difficulty) lcd.printString("HARD", 15, 4);
00140         if (selection == 3) lcd.printString(">BACK<", 24, 5);
00141         else lcd.printString("BACK", 30, 5);
00142     }
00143     if (menu==3) {
00144         if (selection == 0) lcd.printString(">SOUND=0<", 15, 2);
00145         else lcd.printString("SOUND=0", 21, 2);
00146         if (sound_on) lcd.printString("1", 57, 2);
00147         if (selection == 1) lcd.printString(">POINTS=   0<", 3, 3);
00148         else lcd.printString("POINTS=   0", 9, 3);
00149             sprintf(g_buffer, "%4i", points);
00150             lcd.printString(g_buffer, 52, 3);
00151         if (selection == 2) lcd.printString(">MENU DB=0<", 9, 4);
00152         else lcd.printString("MENU DB=0", 15, 4);
00153         if (DEBUG_menu) lcd.printString("1", 63, 4);
00154         if (selection == 3) lcd.printString(">BACK<", 24, 5);
00155         else lcd.printString("BACK", 30, 5);
00156     }
00157 }
00158 
00159 void ship_select() {
00160     player_ship_type++;
00161     player_ship_type %= 2;
00162     //selects between one of two
00163     //ship types available
00164 }
00165 
00166 void level_system() {
00167     while (!pad.start_held()) {
00168         create_stars();
00169         engine_setup(int(points/400)); 
00170         //passing the result of this division saves calculation during gameplay
00171         if (enemy[0].HP <= 0 && enemy[1].HP <= 0 && player.HP <= 0) {
00172             lcd.printString("<ACCEPTABLE>", 6, 4);
00173         }
00174         else if (player.HP <= 0) lcd.printString("<GAME OVER>", 9, 4);
00175         else if (enemy[0].HP <= 0 && enemy[1].HP <= 0) {
00176             lcd.printString("<NICE>", 24, 4);
00177             points += 100;
00178             if (difficulty) points += 100;
00179             if (points>400) points += 100;
00180         } else lcd.printString("<TOO SLOW>", 12, 4);
00181         //above checks victory conditions, extra points for difficulty
00182         sprintf(g_buffer, "SCORE:%4i", points);
00183         if (enemy[0].HP > 0 || enemy[1].HP > 0) points = 0;
00184         lcd.printString(g_buffer, 12, 5);
00185         lcd.refresh();
00186         wait_ms(200);
00187         if (points <= 200) lcd.printString("<EXIT: START>", 6, 0);
00188         lcd.printString("<PLAY: ABXY>", 9, 1);
00189         lcd.refresh();
00190         while (!pad.A_held() && !pad.B_held() && !pad.Y_held()
00191             && !pad.X_held() && !pad.start_held());
00192     }
00193     pad.leds(0);
00194 }
00195 
00196 void engine_setup (bool phase_two) {
00197     float HP_divider;
00198     //init_ship sets parameters - position, direction,
00199     //turn rate, acceleration, speed and hitpoints
00200     if (player_ship_type == 0) {
00201         player.init_ship(1, 0, 0, 0.05, 0.05, 0.7, 12);
00202         HP_divider = 0.5; //HP divider is used to display health on the LEDs
00203     } else {
00204         player.init_ship(1, 0, 0, 0.15, 0.15, 0.5, 20);
00205         HP_divider = 0.3;
00206     } //ship type 2: slower speed and fire rate,
00207       //faster turn rate, higher damage and more HP
00208     enemy[0].init_ship(40, -20+rand()%40, -1+rand()%2,
00209         0.06+0.00005*points, 0.08+0.0001*points, 0.7+0.001*points, 10);
00210         //additional maths in the enemy initialisation
00211         //to increase difficulty over time
00212     if (difficulty) for (int i = 0; i <= 1; i++) {
00213         enemy[i].init_ship(50+rand()%30, 10-20*i, 2-4*i,
00214         0.06+0.00005*points, 0.1+0.0001*points, 1+0.001*points, 10);
00215     } else if (phase_two) for (int i = 0; i <= 1; i++) {
00216         enemy[i].init_ship(50+rand()%30, 10-20*i, -1+2*i,
00217         0.05+0.00005*points, 0.05+0.0001*points, 0.4+0.001*points, 10);
00218     }
00219     engine(phase_two, HP_divider);
00220 }
00221 
00222 void engine(bool phase_two, float HP_divider) {
00223     int end_delay = 40, time_limit = clock() + 6000;
00224     // end_delay lets the animation continue for about two seconds after death
00225     // time_limit keeps the game fun by forcing players to engage with the game
00226     
00227     while (end_delay > 0 && clock() < time_limit) {
00228         Vector2D coord=pad.get_mapped_coord();
00229         lcd.clear();
00230         
00231         for (int i = 0; i <= (difficulty || phase_two); i++) {
00232             if (enemy[i].HP > 0) {
00233                 enemy[i].AI_controls(player.pos_x, player.pos_y, player.orientation);
00234             }
00235             if (enemy[i].check_hitbox(int(player.pos_x), int(player.pos_y), 4)) {
00236                 player.HP--;
00237             }
00238             if (player.check_hitbox(int(enemy[i].pos_x), int(enemy[i].pos_y), 5)) {
00239                 if (player_ship_type == 0)enemy[i].HP--;
00240                 else enemy[i].HP -= 4; //cannon on ship1 does more damage
00241             }
00242             enemy[i].update();
00243         }//change the for loop and object declaration to add more enemies
00244         
00245         if (player_ship_type == 0) SHIP0.render(lcd, 60, 20);
00246         else SHIP1.render(lcd, 60, 20);
00247         
00248         render_objects(phase_two || difficulty);
00249         effects(phase_two, time_limit, HP_divider);
00250         // effects control every other aspect of the gamepad output
00251         
00252         if (player.HP > 0) {
00253             player.controls(player_ship_type, pad.Y_held(),
00254             pad.A_held(), pad.X_held(), pad.B_held(), coord.x, coord.y);
00255         }
00256         if ((enemy[0].HP<=0 && enemy[1].HP <= 0) || player.HP <= 0) end_delay--;
00257         if (god_mode) player.HP = 24;
00258         
00259         lcd.refresh();
00260         // I opted not to limit the frame rate of the game because
00261         // it will never render fast enough to be less fun
00262         // as if it was running at full speed
00263     }
00264 }
00265 
00266 void render_objects(int extra_enemies) {
00267     int temp1, temp2;
00268     float sine = sin(player.orientation), cosine = cos(player.orientation);
00269     
00270     for (int i = -16; i < 69; i++) {
00271         for (int ii = -24; ii < 32; ii++) {
00272             temp1 = int(player.pos_x + (i * cosine - ii * sine));
00273             temp2 = int(player.pos_y + (ii * cosine + i * sine));
00274             
00275             if (temp1 == int(enemy[0].pos_x) 
00276                 && temp2 == int(enemy[0].pos_y)) {
00277                 draw_enemy(i, ii, 0);
00278                 #ifdef DEBUG_render
00279                     printf("Found enemy0: %3.1f,%3.1f\n", 
00280                     enemy[0].pos_x, enemy[0].pos_y);
00281                 #endif
00282                 
00283             } else if (extra_enemies && temp1 == int(enemy[1].pos_x) 
00284                 && temp2 == int(enemy[1].pos_y)) {
00285                 draw_enemy(i, ii, 1);
00286                 #ifdef DEBUG_render
00287                     printf("Found enemy1: %3.1f,%3.1f\n", 
00288                     enemy[1].pos_x, enemy[1].pos_y);
00289                 #endif
00290             }
00291             else if (check_stars(temp1, temp2) 
00292                 || player.check_bullets(temp1, temp2)
00293                 || enemy[0].check_bullets(temp1, temp2) 
00294                 || enemy[1].check_bullets(temp1, temp2)){
00295                 lcd.setPixel(68-i, 24-ii, 1);
00296             }
00297         }// above finds the location of particles to render
00298     }
00299 }
00300 
00301 void draw_enemy(int i, int j, int num) {
00302     float angle = player.orientation - enemy[num].orientation - 1.5708f;
00303     //this calculation finds what direction
00304     //to draw the enemy with respect to the player
00305     
00306     if (enemy[num].HP > 0) {
00307         lcd.drawRect(63 - i, 32 - j, 12, 4, FILL_TRANSPARENT); //health bar
00308         lcd.drawRect(64 - i, 33 - j, enemy[num].HP, 2, FILL_BLACK);
00309         
00310         if (j < 20) {//limits the game to drawing within bound of the screen
00311             lcd.drawCircle(68 - i, 24 - j, 5, FILL_BLACK);
00312             lcd.drawCircle(68 - i + 3 * sin(angle),
00313                 24 - j + 3 * cos(angle), 2, FILL_WHITE);
00314         }
00315     } else if (explode (68 - i, 24 - j) && j < 20) {
00316         lcd.drawCircle(68 - i, 24 - j, 5, FILL_BLACK);
00317         lcd.drawCircle(68 - i + 3 * sin(angle),
00318             24 - j + 3 * cos(angle), 2, FILL_WHITE);
00319     }
00320 }
00321 
00322 void effects(bool phase_two, int time_limit, float HP_divider) {
00323     if (enemy[0].HP > 0) print_tracker(0);
00324     if ((difficulty || phase_two) && (enemy[1].HP > 0)) print_tracker(1);
00325     if (pad.X_held() && clock() % 18 > 8) {
00326         for (int i = 0; i < 3; i++)lcd.setPixel(69 + i, 24 , 1);
00327         if (clock() % 9 > 3) {
00328             lcd.setPixel(72, 24, 1);
00329             lcd.setPixel(73, 24, 1);
00330         }
00331     }
00332     pad.leds(0); //LEDs display player health proportionally
00333     if (player.HP > 0) {
00334         for (int health = 0; health <= (HP_divider*player.HP); health++) {
00335             pad.led(health, 1);
00336         }
00337     }
00338     else explode(68, 24);
00339     if (sound_on) sound(difficulty || phase_two);
00340     sprintf(g_buffer, "%2i", (time_limit - clock()) / 100);
00341     lcd.printString(g_buffer, 12, 5);
00342 }
00343 
00344 void print_tracker(int num) {
00345     float diff_x = enemy[num].pos_x-player.pos_x;
00346     if (abs(diff_x) < 0.0001f) diff_x = 0.0001f;
00347     
00348     float angle_to_enemy = atan((enemy[num].pos_y - player.pos_y) / diff_x);
00349     if ((diff_x) > 0) angle_to_enemy += 3.1416f;
00350     
00351     #ifdef DEBUG_tracker
00352             sprintf(g_buffer, "%1.3f", player.orientation);
00353             lcd.printString(g_buffer, 60, 0);
00354             sprintf(g_buffer, "%1.3f", angle_to_enemy - player.orientation);
00355             lcd.printString(g_buffer, 60, 1);
00356     #endif
00357     
00358     float sine = sin(angle_to_enemy - player.orientation);
00359     float cosine = cos(angle_to_enemy - player.orientation);
00360     
00361     lcd.setPixel(67 + 8 * cosine, 24 + 6 * sine, 1);
00362     lcd.setPixel(67 + 9 * cosine, 24 + 7 * sine, 1);
00363     lcd.setPixel(67 + 10 * cosine, 24 + 8 * sine, 1);
00364     lcd.setPixel(67 + 11 * cosine, 24 + 9 * sine, 1);
00365     lcd.setPixel(67 + 12 * cosine, 24 + 10 * sine, 1);
00366     lcd.setPixel(67 + 13 * cosine, 24 + 11 * sine, 1);
00367     lcd.drawCircle(67 + 9 * cosine, 24 + 7 * sine, 1, FILL_BLACK);
00368     lcd.drawCircle(67 + 10 * cosine, 24 + 8 * sine, 1, FILL_BLACK);
00369 }
00370 
00371 void sound(bool extra_enemy) {
00372     //buzzers can only produce one tone at a time
00373     //this function prioritises some sounds over others
00374     if (player.HP <= 0) {
00375         player.explosion_FX--;
00376     } 
00377     if (enemy[0].HP <= 0) {
00378         enemy[0].explosion_FX--;
00379     }
00380     if (extra_enemy && enemy[1].HP <= 0) {
00381         enemy[1].explosion_FX--;
00382     } 
00383     //"explosion" is initialised as 8, and is decremented each frame
00384     //below if statement makes the explosion produce noise over 7 frames
00385     if ((player.explosion_FX < 8 && player.explosion_FX > 0)
00386         || (enemy[0].explosion_FX < 8 && enemy[0].explosion_FX > 0)
00387         || (extra_enemy && enemy[1].explosion_FX < 8 && enemy[1].explosion_FX > 0)) {
00388         pad.tone(1000, 0.1);
00389     }
00390     else if (player.gun_FX > 0) pad.tone(1600 - player.gun_FX * 200, 0.1);
00391     //the player's weapon takes priority for sound, after explosions
00392     else if (enemy[0].gun_FX > 0) pad.tone(5000, 0.1);
00393     else if (enemy[1].gun_FX > 0) pad.tone(5000, 0.1);
00394     
00395     enemy[0].gun_FX--;
00396     enemy[1].gun_FX--;
00397     player.gun_FX--;
00398     //decrementing the FX variable makes sure the sound is not delayed
00399     //if the function is busy producing a different sound
00400 }
00401 
00402 bool explode(int i, int j) {
00403     //a clock() with a modulo helps make the explosion more exciting
00404     //by forcing it to switch on and off over time
00405     if (clock() % 9 > 4) {
00406         for (int inc = 0; inc < 6; inc++) {
00407             float b = rand() % 63 / 10;
00408             int a = rand() % 6;
00409             int tempx = i - a * sin(b), tempy = j - a * cos(b);
00410             
00411             if (tempy > 3 && i < 81) lcd.drawCircle(tempx, tempy, 2, FILL_BLACK);
00412         }//6 randomly placed white packets to simulate explosion
00413         return 0;
00414     }//boolean returns are used for switching a rendering function in draw_enemy();
00415     return 1;
00416 }
00417 
00418 void print_background(float screen_x, float screen_y, float screen_orientation) {
00419     int temp1, temp2;
00420     float sine = sin(screen_orientation), cosine = cos(screen_orientation);
00421     
00422     for (int i = -16; i < 69; i++ ) {
00423         for (int ii = -24; ii < 25; ii++) {
00424             temp1 = int(screen_x + (i * cosine - ii * sine));
00425             temp2 = int(screen_y + (ii * cosine + i * sine)) + 1;
00426             
00427             if (check_stars(temp1, temp2)) lcd.setPixel(68 - i, 24 - ii, 1);
00428         }
00429     }
00430 }
00431 
00432 bool check_stars(int temp1, int temp2) {
00433     while (temp1 < 0) temp1 += 224;
00434     temp1 = temp1 % 224; //this function tesselates the star map
00435     while (temp2 < 0) temp2 += 224;
00436     temp2 = temp2 % 224;
00437     
00438     if (g_star_map[temp1][temp2])return 1;
00439     else return 0;
00440 }
00441 
00442 void create_stars() {//the backhround is a boolean array
00443     for (int i = 0; i < 224; i++) {
00444         for (int ii = 0; ii < 224; ii++) g_star_map[i][ii] = 0;
00445     }//this clears the background for drawing a new one
00446     for (int i = 0; i < 224; i++) {
00447         for (int ii = 0; ii < 224; ii++) {
00448             if (rand() % 1000 > 985) g_star_map[i][ii] = 1;
00449         }//1.5% of the sky is stars!
00450     }
00451 }
00452 
00453 void title_sequence() {
00454     temptime = clock();
00455     
00456     while ((clock() - temptime) < 40) {
00457         lcd.clear();
00458         print_background(1, 0, 0);
00459         lcd.refresh();
00460     }
00461     sprintf(g_buffer, "Space Combat");
00462     
00463     while (!pad.A_held() && !pad.B_held() && !pad.Y_held()
00464         && !pad.X_held() && !pad.start_held()) {
00465         AGALAG.render(lcd, 18, 12);
00466         if ((clock() - temptime) < 240) dramatic_print(g_buffer, 7, 3, 20);
00467         lcd.refresh();
00468     }
00469     temptime = clock();
00470     
00471     while (clock() - temptime < 140 && !pad.start_held()) {
00472         lcd.clear();
00473         print_background(1, 0, 0);
00474         AGALAG.render(lcd, 18, 11 - (clock() - temptime) / 20);
00475         lcd.refresh();
00476     }
00477 }
00478 
00479 void dramatic_print(string str, int offset, int line_num, int delay_time) {
00480         int i = ((clock() - temptime) / delay_time) % 14;
00481         
00482         for (int ii = 0; ii <= i; ii++) {
00483             lcd.printChar(str[ii], 6 * ii + offset, line_num);
00484         }
00485 }
00486 
00487 void help() {
00488     while (pad.A_held() || pad.Y_held());
00489     pad.leds(1.0);
00490     
00491     while (!pad.A_held() && !pad.B_held() && !pad.Y_held()
00492         && !pad.X_held() && !pad.start_held()) {
00493         Vector2D coord = pad.get_mapped_coord();
00494         lcd.clear();
00495         print_background(1, 0, 0);
00496         lcd.printString("<HP=LED>", 19, 0);
00497         lcd.printString("<SHOOT>", 0, 1);
00498         lcd.printString("<MOVE>", 45, 1);
00499         lcd.printString(">BACK<", 24, 5);
00500         lcd.drawCircle(63, 36, 3, FILL_TRANSPARENT);
00501         lcd.drawCircle(63, 20, 3, FILL_TRANSPARENT);
00502         lcd.drawCircle(53, 28, 3, FILL_TRANSPARENT);
00503         lcd.drawCircle(73, 28, 3, FILL_TRANSPARENT);
00504         lcd.drawCircle(20 + int(10 * coord.x), 
00505             28 - int(10 * coord.y), 3, FILL_BLACK);
00506             
00507         if (clock() % 4 > 1) lcd.drawCircle(20, 28, 12, FILL_TRANSPARENT);
00508         lcd.refresh();
00509     }
00510     pad.leds(0);
00511 }