Dodging asteroids game.

Dependencies:   4DGL-uLCD-SE PinDetect SDFileSystem mbed wave_player

main.cpp

Committer:
dylanslack
Date:
2016-03-14
Revision:
0:3f73e98442ec

File content as of revision 0:3f73e98442ec:

#include "LSM9DS1.h"
#include "uLCD_4DGL.h"
#include "wave_player.h"
#include "SDFileSystem.h"
#include <math.h>
#include <vector>
#include <time.h>

uLCD_4DGL uLCD(p28, p27, p30);
SDFileSystem sd(p5, p6, p7, p8, "sd");
Serial pc(USBTX, USBRX);
LSM9DS1 IMU(p9, p10, 0xD6, 0x3C);
AnalogOut DACout(p18);
wave_player waver(&DACout);

FILE *explosion = fopen("/sd/explosion.wav", "r");

int score = 0;

/**
 * The SpaceCraft class has a single position variable and
 * the methods required to move, draw, and clear a SpaceCraft
 * instance.
 */
class SpaceCraft {
    public:
        SpaceCraft();
        void move(float);
        void draw();
        void undraw();
        int position;
};

/**
 * Asteroids are similar to SpaceCrafts but are generated with
 * a random size, position, and velocity after being invoked.
 * The `isAlive` variable becomes false when the asteroid should
 * be reset, where it is given another random position, size, and 
 * velocity.
 */
class Asteroid {
    public:
        Asteroid();
        void move(int);
        void reset();
        void fall();
        void draw();
        void undraw();
        bool isAlive;
        int x, y, size, velocity;
};

SpaceCraft::SpaceCraft() {
    position = 64;
}

Asteroid::Asteroid() {    
    x = (rand() % 100) + 10;
    y = 0;
    size = (int)((rand() % 4) + 6);
    velocity = (int)((rand() % 3) + 1);
    isAlive = true;
}

// moves the space craft by clearing it, advancing it, then redrawing it
void SpaceCraft::move(float x) {
    undraw();
    position = (int)(64.0 - (x * 64.0));
    draw();
}

// moves the asteroid by clearing it, advancing it, then redrawing it
void Asteroid::move(int posY) {
    if (y == posY) return;
    
    undraw();
    y = posY;
    
    if (y >= 127) {
        isAlive = false;
        score++;
    } else {
        draw();   
    }
}

// moves the asteroid downward according to its velocity
void Asteroid::fall() {
    move(y + velocity);
}

// resets the astroid by putting it back at the top with a random x coordinate
void Asteroid::reset() {
    y = 0;
    x = (rand() % 100) + 10;
    velocity = (int)((rand() % 3) + 1);
    isAlive = true;
}

// draws the asteroid
void Asteroid::draw() {
    int largeCraterRadius = (int)(floor((float)size / 3.0));
    int largeCraterX = x - largeCraterRadius;
    int largeCraterY = y - largeCraterRadius;
    
    int smallCraterRadius = (int)(floor((float)size / 4.0));
    int smallCraterX = x + largeCraterRadius;
    int smallCraterY = y + largeCraterRadius;
    
    uLCD.filled_circle(x, y, size, 0x909090);
    uLCD.circle(largeCraterX, largeCraterY, largeCraterRadius, 0x404040);
    uLCD.circle(smallCraterX, smallCraterY, smallCraterRadius, 0x404040);
}

// draws the space craft
void SpaceCraft::draw() {
    int width = 12;
    int left = position - (width / 2);
    int right = position + (width / 2);
    int middle = left + (width / 2);
    
    // draw the space craft (blue triangle)
    uLCD.triangle(left, 120, middle, 106, right, 120, BLUE);
    
    // draw the flame (red triangle)
    uLCD.triangle(left + 4, 121, middle, 125, right - 4, 121, RED);
}

// clears the asteroid
void Asteroid::undraw() {
    int largeCraterRadius = (int)(floor((float)size / 3.0));
    int largeCraterX = x - largeCraterRadius;
    int largeCraterY = y - largeCraterRadius;
    
    int smallCraterRadius = (int)(floor((float)size / 4.0));
    int smallCraterX = x + largeCraterRadius;
    int smallCraterY = y + largeCraterRadius;
    
    uLCD.filled_circle(x, y, size, BLACK);
    uLCD.circle(largeCraterX, largeCraterY, largeCraterRadius, BLACK);
    uLCD.circle(smallCraterX, smallCraterY, smallCraterRadius, BLACK);
}

// clears the space craft
void SpaceCraft::undraw() {
    int width = 12;
    int left = position - (width / 2);
    int right = position + (width / 2);
    int middle = left + (width / 2);
    
    // clear the space craft (blue triangle)
    uLCD.triangle(left, 120, middle, 106, right, 120, BLACK);
    
    // clear the flame (red triangle)
    uLCD.triangle(left + 4, 121, middle, 125, right - 4, 121, BLACK);
}

/**
 * Returns true if there has been a collision between an asteroid and a ship.
 * This a very basic collision: it determines if any of the ship's vertices
 * interset with the asteroid's bounding circle. This fails for instances in
 * which the asteroid intesects with the edge of a ship but not one of the
 * vertices.
 *
 * This calculates vertex collision by comparing the circle's radius and the distance
 * between a vertex and the center of an asteroid.
 */
bool checkCollision(SpaceCraft ship, Asteroid asteroid) {
    // asteroid coordinates
    int asteroidCenterX = asteroid.x;
    int asteroidCenterY = asteroid.y;
    int asteroidRadius = asteroid.size;
    
    // ship coordinates
    int shipLeftX = ship.position - 6;
    int shipLeftY = 120;
    int shipRightX = ship.position + 6;
    int shipRightY = 120;
    int shipMiddleX = ship.position;
    int shipMiddleY = 106;
       
    // left vertex detection
    if (sqrt(pow(asteroidCenterX - shipLeftX, 2.0) + pow(asteroidCenterY - shipLeftY, 2.0)) <= asteroidRadius)
        return true;
    
    // right vertex detection
    if (sqrt(pow(asteroidCenterX - shipRightX, 2.0) + pow(asteroidCenterY - shipRightY, 2.0)) <= asteroidRadius)
        return true;
    
    // middle vertex detection
    if (sqrt(pow(asteroidCenterX - shipMiddleX, 2.0) + pow(asteroidCenterY - shipMiddleY, 2.0)) <= asteroidRadius)
        return true;
    
    return false;
}

SpaceCraft ship;
vector<Asteroid> asteroids;

int main() {
    float tilt;
    
    // don't forget to seed the rand function
    srand(time(NULL));
    
    uLCD.reset();
    uLCD.cls();
    uLCD.background_color(BLACK);
    uLCD.printf("\nDodge asteroids!\n\r");
    uLCD.printf("Loading...");
    
    wait(1.0);
    uLCD.cls();
    wait(1.0);

    // jack up the baudrate
    uLCD.baudrate(3000000);
    
    // initialize some asteroids
    Asteroid a, b, c, d;
    asteroids.push_back(a);
    asteroids.push_back(b);
    asteroids.push_back(c);
    asteroids.push_back(d);
    
    IMU.begin();
    
    if (!IMU.begin()) {
        pc.printf("Failed to communicate with IMU.\n");
        return 0;
    }
    
    IMU.calibrate(1);
    IMU.calibrateMag(0);
    
    if (explosion == NULL) {
        pc.printf("explosion.wav was not opened!\n");
        return 0;
    }
        
    while(1) {
        while(!IMU.accelAvailable());
        IMU.readAccel();

        /**
         * `tilt` is the 'y' acceleration value, ranging between [-1.0, 1.0].
         *
         * This variable will control the spacecraft's horizontal position.
         */
        tilt = IMU.calcAccel(IMU.ay);
        
        for(size_t i = 0; i < asteroids.size(); i++) {
            asteroids[i].fall();

            // reset the asteroid if it goes below the screen
            if (!asteroids[i].isAlive) {
                asteroids[i].reset();
            }
            
            // play the explosion sound and end the game if there is a collision
            if (checkCollision(ship, asteroids[i])) {
                uLCD.background_color(RED);
                uLCD.textbackground_color(RED);
                waver.play(explosion);
                fseek(explosion, 0, SEEK_SET);
                uLCD.cls();
                uLCD.color(WHITE);
                uLCD.text_width(3);
                uLCD.text_height(3);
                uLCD.printf("\nGame\nOVER\n\n");
                uLCD.text_width(1);
                uLCD.text_height(1);
                uLCD.printf("Score: %d", score);
                return 0;
            }
        }
        
        // update the score
        uLCD.locate(0, 0);
        uLCD.printf("Score: %d", score);
        
        // move the ship based on the IMU's accelerometer reading
        ship.move(tilt);
        wait(0.01);
    }
}