Dodging asteroids game.
Dependencies: 4DGL-uLCD-SE PinDetect SDFileSystem mbed wave_player
Diff: main.cpp
- Revision:
- 0:3f73e98442ec
diff -r 000000000000 -r 3f73e98442ec main.cpp --- /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