Dodging asteroids game.

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

Revision:
0:3f73e98442ec
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Mar 14 03:08:37 2016 +0000
@@ -0,0 +1,286 @@
+#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);
+    }
+}
\ No newline at end of file