new mods upon mods by devhammer: - Added paddle control using tilting of the console - Finished mute function - Reduced flickering See game.cpp for full info.

Dependencies:   mbed

Fork of RETRO_Pong_Mod by G. Andrew Duthie

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers Game.cpp Source File

Game.cpp

00001 // Updated version of the official firmware for the Outrageous Circuits RETRO
00002 //
00003 // mod150106 - Modified by G. Andrew Duthie (devhammer)
00004 // Changes:
00005 // - Added sounds for all ball bounces
00006 // - Changed ball from square to circle
00007 // - Adjusted collision detection to add ball speed every 10 paddle hits
00008 // - Added scoring
00009 // - Added mute function (not fully implemented...needs to be set up on a button).
00010 //
00011 // mod150115 - Modified by Maxint
00012 // Changes:
00013 // - Upped the I2C frequency to make the Accelerometer useable in actual gameplay
00014 // - Added paddle control using tilting of the console
00015 // - Changed left-right button control to make it a bit more logical
00016 // - tied mute function to the circle button and added el cheapo debouncing
00017 // - reduced flickering by only redrawing paddle and ball when changed position
00018 
00019 #include "Game.h"
00020 
00021 const char* Game::LOSE_1 = "You lose.";
00022 const char* Game::LOSE_2 = "Press ship to restart.";
00023 const char* Game::SPLASH_1 = "Press ship to start.";
00024 const char* Game::SPLASH_2 = "Press robot to switch.";
00025 const char* Game::LIVES = "Lives: ";
00026 const char* Game::SCORE = "Score: ";
00027     
00028 Game::Game() : left(P0_14, PullUp), right(P0_11, PullUp), down(P0_12, PullUp), up(P0_13, PullUp), square(P0_16, PullUp), circle(P0_1, PullUp), led1(P0_9), led2(P0_8), pwm(P0_18), ain(P0_15), i2c(P0_5, P0_4) {
00029     srand(this->ain.read_u16());
00030     
00031     this->lastUp = false;
00032     this->lastDown = false;
00033     this->mode = true;      // true=game, false=accelerometer
00034     
00035     //this->i2c.frequency(400);       // Really? Is this a joke? 400 Hz is much too slow. Reading the XYZ now takes 210 ms
00036     this->i2c.frequency(400000);      // fast I2C is 400 KHz, not 400 Hz. Default frequency is 100 KHz, but the faster, the less delay...
00037 
00038     this->writeRegister(0x2A, 0x01); 
00039     
00040     this->colors[0] = DisplayN18::RED;
00041     this->colors[1] = DisplayN18::GREEN;
00042     this->colors[2] = DisplayN18::BLUE;
00043     
00044     this->initialize();
00045 }
00046 
00047 void Game::readRegisters(char address, char* buffer, int len) {
00048     this->i2c.write(Game::I2C_ADDR, &address, 1, true);
00049     this->i2c.read(Game::I2C_ADDR | 1, buffer, len);
00050 }
00051 
00052 int Game::writeRegister(char address, char value) {    
00053     char buffer[2] = { address, value };
00054     
00055     return this->i2c.write(Game::I2C_ADDR, buffer, 2);
00056 }
00057 
00058 double Game::convert(char* buffer) {
00059     double val = ((buffer[0] << 2) | (buffer[1] >> 6));
00060             
00061     if (val > 511.0) 
00062         val -= 1024.0;
00063     
00064     return val / 512.0;
00065 }
00066 
00067 void Game::getXYZ(double& x, double& y, double& z) {
00068     char buffer[6];
00069     
00070     this->readRegisters(0x01, buffer, 6);
00071     
00072     x = this->convert(buffer);
00073     y = this->convert(buffer + 2);
00074     z = this->convert(buffer + 4);
00075 }
00076 
00077 void Game::printDouble(double value, int x, int y) {
00078     char buffer[10];
00079     int len = sprintf(buffer, "%.1f", value);
00080     
00081     this->disp.drawString(x, y, buffer, DisplayN18::WHITE, DisplayN18::BLACK);
00082 }
00083 
00084 void Game::drawAxes() {
00085     for (int i = 0; i < 3; i++) {
00086         this->disp.drawLine(0, i * (Game::GRAPH_HEIGHT + Game::GRAPH_SPACING), 0, i * (Game::GRAPH_HEIGHT + Game::GRAPH_SPACING) + Game::GRAPH_HEIGHT, DisplayN18::WHITE);
00087         this->disp.drawLine(0, i * (Game::GRAPH_HEIGHT + Game::GRAPH_SPACING) + Game::GRAPH_HEIGHT / 2, DisplayN18::WIDTH, i * (Game::GRAPH_HEIGHT + Game::GRAPH_SPACING) + Game::GRAPH_HEIGHT / 2, DisplayN18::WHITE);
00088     }
00089 }
00090 
00091 void Game::drawPoint(int axis, double value) {
00092     if (value < -1.0)
00093         value = -1.0;
00094 
00095     if (value > 1.0)
00096         value = 1.0;
00097 
00098     value += 1.0;
00099     value /= 2.0;
00100     value = 1.0 - value;
00101     value *= Game::GRAPH_HEIGHT;
00102 
00103     this->disp.setPixel(this->graphX, axis * (Game::GRAPH_HEIGHT + Game::GRAPH_SPACING) + (int)value, this->colors[axis]);
00104 }
00105 
00106 void Game::checkGraphReset() {
00107     if (this->graphX > DisplayN18::WIDTH) {
00108         this->graphX = 0;
00109         this->disp.clear();
00110         this->drawAxes();
00111     }
00112 }
00113 
00114 void Game::initialize() {    
00115     this->initializeBall();
00116         
00117     this->paddleX = DisplayN18::WIDTH / 2 - Game::PADDLE_WIDTH / 2;
00118     this->paddleXprev=this->paddleX;
00119     this->pwmTicksLeft = 0;
00120     this->lives = 4;
00121     this->score = 0;
00122     this->muted = false;
00123     
00124     this->pwm.period(1);
00125     this->pwm.write(0.00);
00126     
00127     this->disp.clear();
00128 }
00129     
00130 void Game::initializeBall() {
00131     this->ballX = DisplayN18::WIDTH / 2 - Game::BALL_RADIUS;
00132     this->ballY = DisplayN18::HEIGHT / 4 - Game::BALL_RADIUS;
00133     this->ballXprev=this->ballX;
00134     this->ballYprev=this->ballY;
00135     
00136     this->ballSpeedX = Game::BALL_STARTING_SPEED;
00137     this->ballSpeedY = Game::BALL_STARTING_SPEED;
00138 
00139     this->ballSpeedX *= (rand() % 2 ? 1 : -1);
00140     this->ballSpeedY *= (rand() % 2 ? 1 : -1);
00141 }
00142 
00143 void Game::tick() {  
00144     this->checkButtons();
00145     
00146     if (this->mode) {
00147         //this->clearPaddle();
00148         //this->clearBall();
00149         
00150         this->updatePaddle();
00151         this->updateBall();
00152     
00153         this->checkCollision();
00154         
00155         this->redrawPaddle();        
00156         this->redrawBall();
00157         
00158         this->checkPwm();
00159         this->checkLives(); 
00160         
00161         wait_ms(25);
00162     }
00163     else {    
00164         double x, y, z;
00165         
00166         this->getXYZ(x, y, z);
00167         
00168         this->checkGraphReset();
00169         this->drawPoint(0, x);
00170         this->drawPoint(1, y);
00171         this->drawPoint(2, z);
00172         this->graphX++;
00173 
00174         wait_ms(100);   // added delay after upping the I2C frequency to a usable value
00175     } 
00176 }
00177 
00178 
00179 int Game::checkTilt()
00180 {    // check the orientation of the console to allow tilt-control
00181     double x, y, z;
00182     
00183     this->getXYZ(x, y, z);
00184 
00185     if(x<-0.07) return(-1);     // Using 0.1 requires too much an angle for nice gameplay. 0.07 is more subtle.
00186     else if(x>0.07) return(1);
00187     else return(0);
00188 }
00189 
00190 void Game::checkButtons() {
00191     if (!this->square.read()) {
00192         this->mode = !this->mode;
00193         
00194         this->disp.clear();
00195         
00196         if (!this->mode) {
00197             this->graphX = 0;
00198             
00199             this->drawAxes();
00200         }
00201         
00202         this->led1.write(!this->mode);
00203     }
00204     else if(!this->circle.read()) { 
00205         // use ship-button to mute
00206         this->muted = !this->muted;
00207         this->led2.write(this->muted);
00208         wait_ms(250);   // el-cheapo deboounce
00209     }
00210     
00211     bool xDir = this->ballSpeedX > 0;
00212     bool yDir = this->ballSpeedY > 0;
00213     bool isUp = !this->up.read();
00214     bool isDown = !this->down.read();
00215     
00216     if (isUp && isDown) goto end;
00217     if (!isUp && !isDown) goto end;
00218     
00219     if (isUp && this->lastUp) goto end;
00220     if (isDown && this->lastDown) goto end;
00221     
00222     if (!xDir) this->ballSpeedX *= -1;
00223     if (!yDir) this->ballSpeedY *= -1;
00224     
00225     if (isUp) {
00226         if (++this->ballSpeedX > 5) this->ballSpeedX = 5;
00227         if (++this->ballSpeedY > 5) this->ballSpeedY = 5;
00228     }
00229     else if (isDown) {
00230         if (--this->ballSpeedX == 0) this->ballSpeedX = 1;
00231         if (--this->ballSpeedY == 0) this->ballSpeedY = 1;
00232     }
00233     
00234     if (!xDir) this->ballSpeedX *= -1;
00235     if (!yDir) this->ballSpeedY *= -1;
00236     
00237 end:
00238     this->lastUp = isUp;
00239     this->lastDown = isDown;    
00240 }
00241 
00242 void Game::drawString(const char* str, int y) {
00243     this->disp.drawString(DisplayN18::WIDTH / 2 - (DisplayN18::CHAR_WIDTH + DisplayN18::CHAR_SPACING) * strlen(str) / 2, y, str, DisplayN18::WHITE, DisplayN18::BLACK);         
00244 }
00245 
00246 void Game::showSplashScreen() {
00247     this->drawString(Game::SPLASH_1, DisplayN18::HEIGHT / 2 - DisplayN18::CHAR_HEIGHT / 2);  
00248     this->drawString(Game::SPLASH_2, DisplayN18::HEIGHT / 2 + DisplayN18::CHAR_HEIGHT / 2); 
00249        
00250     while (this->circle.read())
00251         wait_ms(1);
00252     wait_ms(250);   // el-cheapo deboounce
00253         
00254     this->disp.clear();
00255 }
00256 
00257 void Game::clearPaddle() {
00258     this->disp.fillRect(this->paddleX, DisplayN18::HEIGHT - Game::PADDLE_HEIGHT, Game::PADDLE_WIDTH, Game::PADDLE_HEIGHT, DisplayN18::BLACK);    
00259 }
00260 
00261 void Game::drawPaddle() {
00262     this->disp.fillRect(this->paddleX, DisplayN18::HEIGHT - Game::PADDLE_HEIGHT, Game::PADDLE_WIDTH, Game::PADDLE_HEIGHT, DisplayN18::BLUE);    
00263 }
00264 
00265 void Game::redrawPaddle()
00266 {   // draw the paddle, but only clear when changed to reduce flickering
00267     if(this->paddleXprev!=this->paddleX)
00268     {
00269         this->disp.fillRect(this->paddleXprev, DisplayN18::HEIGHT - Game::PADDLE_HEIGHT, Game::PADDLE_WIDTH, Game::PADDLE_HEIGHT, DisplayN18::BLACK);    
00270     }
00271     this->disp.fillRect(this->paddleX, DisplayN18::HEIGHT - Game::PADDLE_HEIGHT, Game::PADDLE_WIDTH, Game::PADDLE_HEIGHT, DisplayN18::BLUE);    
00272 }
00273 
00274 void Game::updatePaddle() {
00275     // see if the paddle position needs changing
00276     this->paddleXprev=this->paddleX;
00277     if(!this->left.read())  // note: button.read() is LOW (0) when button pressed
00278         this->paddleX -= Game::PADDLE_SPEED;
00279     else if(!this->right.read())
00280         this->paddleX += Game::PADDLE_SPEED;
00281     else
00282     {
00283         int nTilt=this->checkTilt();        // don't call too often as this I2C is slow and will delay the game
00284         if(nTilt>0)
00285             this->paddleX += Game::PADDLE_SPEED;
00286         else if(nTilt<0)
00287             this->paddleX -= Game::PADDLE_SPEED;
00288     }
00289 }
00290 
00291 void Game::clearBall() {   
00292     //this->disp.fillRect(this->ballX - Game::BALL_RADIUS, ballY - Game::BALL_RADIUS, Game::BALL_RADIUS * 2, Game::BALL_RADIUS * 2, DisplayN18::BLACK);
00293     //this->disp.fillCircle(this->ballX - Game::BALL_RADIUS, this->ballY - Game::BALL_RADIUS, Game::BALL_RADIUS, DisplayN18::BLACK);
00294     this->disp.fillCircle(this->ballX, this->ballY, Game::BALL_RADIUS, DisplayN18::BLACK);
00295     this->disp.setPixel(this->ballX, this->ballY, DisplayN18::BLACK);
00296 }
00297 
00298 void Game::clearBallPrev()
00299 {   // clear the ball from previous position
00300     this->disp.fillCircle(this->ballXprev, this->ballYprev, Game::BALL_RADIUS, DisplayN18::BLACK);
00301     this->disp.setPixel(this->ballXprev, this->ballYprev, DisplayN18::BLACK);
00302 }
00303 
00304 void Game::drawBall() {
00305     //this->disp.fillRect(this->ballX - Game::BALL_RADIUS, ballY - Game::BALL_RADIUS, Game::BALL_RADIUS * 2, Game::BALL_RADIUS * 2, DisplayN18::RED);
00306     //this->disp.fillCircle(this->ballX - Game::BALL_RADIUS, ballY - Game::BALL_RADIUS, Game::BALL_RADIUS, DisplayN18::RED);
00307     this->disp.fillCircle(this->ballX, ballY, Game::BALL_RADIUS, DisplayN18::RED);
00308     this->disp.setPixel(this->ballX, this->ballY, DisplayN18::GREEN);
00309 }
00310 
00311 void Game::redrawBall()
00312 {   // redraw the ball, but only clear when changed to reduce flickering
00313     if(this->ballX!=this->ballXprev || this->ballY!=this->ballYprev)
00314     {
00315         this->disp.fillCircle(this->ballXprev, this->ballYprev, Game::BALL_RADIUS, DisplayN18::BLACK);
00316         this->disp.setPixel(this->ballXprev, this->ballYprev, DisplayN18::BLACK);
00317     }
00318     this->disp.fillCircle(this->ballX, ballY, Game::BALL_RADIUS, DisplayN18::RED);
00319     this->disp.setPixel(this->ballX, this->ballY, DisplayN18::GREEN);
00320 }
00321 
00322 void Game::updateBall() {
00323     this->ballXprev=this->ballX;
00324     this->ballYprev=this->ballY;
00325     this->ballX += this->ballSpeedX;
00326     this->ballY += this->ballSpeedY;
00327 }
00328 
00329 void Game::checkCollision() {    
00330     if (this->paddleX < 0)
00331         this->paddleX = 0;
00332         
00333     if (this->paddleX + Game::PADDLE_WIDTH > DisplayN18::WIDTH)
00334         this->paddleX = DisplayN18::WIDTH - Game::PADDLE_WIDTH;
00335         
00336     //if ((this->ballX - Game::BALL_RADIUS < 0 && this->ballSpeedX < 0) || (this->ballX + Game::BALL_RADIUS >= DisplayN18::WIDTH && this->ballSpeedX > 0)) {
00337     if ((this->ballX - Game::BALL_RADIUS < 0 && this->ballSpeedX < 0) || (this->ballX + Game::BALL_RADIUS * 2 >= DisplayN18::WIDTH && this->ballSpeedX > 0)) {
00338         this->ballSpeedX *= -1;
00339         if(!this->muted) {
00340             this->pwm.period_ms(2);
00341             this->pwmTicksLeft = Game::BOUNCE_SOUND_TICKS;
00342         }
00343     }
00344         
00345     if (this->ballY - Game::BALL_RADIUS < (0 + DisplayN18::CHAR_HEIGHT) && this->ballSpeedY < 0){
00346         this->ballSpeedY *= -1;
00347         if(!this->muted) {
00348             this->pwm.period_ms(2);
00349             this->pwmTicksLeft = Game::BOUNCE_SOUND_TICKS;
00350         }
00351     }
00352         
00353     if (this->ballY + Game::BALL_RADIUS >= DisplayN18::HEIGHT - Game::PADDLE_HEIGHT && this->ballSpeedY > 0) {
00354         if (this->ballY + Game::BALL_RADIUS >= DisplayN18::HEIGHT) {
00355             this->clearBallPrev();
00356             this->initializeBall();
00357             
00358             this->lives--;
00359 
00360             if(this->lives > 0) {
00361                 if(!this->muted) {
00362                     this->pwm.period(1.0/220);
00363                     this->pwm.write(0.5);
00364                     wait_ms(150);
00365                     this->pwm.write(0.0);
00366                 }
00367             }
00368 
00369         }
00370         else if (this->ballX > this->paddleX && this->ballX < this->paddleX + Game::PADDLE_WIDTH) {
00371             this->ballSpeedY *= -1;
00372             
00373             if(!this->muted){
00374                 this->pwm.period_ms(1);
00375                 this->pwmTicksLeft = Game::BOUNCE_SOUND_TICKS;
00376             }
00377             this->score = this->score + 10;
00378             if(this->score % 100 == 0) {
00379                 if(this->ballSpeedX < 0){
00380                     this->ballSpeedX -= 1;
00381                 }
00382                 else {
00383                     this->ballSpeedX += 1;
00384                 }
00385                 this->ballSpeedY -= 1;
00386             }
00387         }
00388     }
00389     char buf[10];
00390     int a = this->score;
00391     sprintf(buf, "%d", a);
00392     this->disp.drawString(DisplayN18::WIDTH - (DisplayN18::CHAR_WIDTH * 12), 0, Game::SCORE, DisplayN18::WHITE, DisplayN18::BLACK);     
00393     this->disp.drawString(DisplayN18::WIDTH - (DisplayN18::CHAR_WIDTH * 4), 0, buf, DisplayN18::WHITE, DisplayN18::BLACK);   
00394 }
00395 
00396 void Game::checkPwm() {
00397     if (this->pwmTicksLeft == 0) {
00398          this->pwm.write(0.0);
00399     }
00400     else {
00401         this->pwmTicksLeft--;
00402         this->pwm.write(0.5); 
00403     }
00404 }
00405 
00406 void Game::checkLives() {
00407     if (this->lives == 0) {
00408         this->disp.clear();
00409                 
00410         this->drawString(Game::LOSE_1, DisplayN18::HEIGHT / 2 - DisplayN18::CHAR_HEIGHT); 
00411         this->drawString(Game::LOSE_2, DisplayN18::HEIGHT / 2);  
00412         
00413         if(!this->muted) {
00414             this->pwm.period(1.0/220);
00415             this->pwm.write(0.5);
00416             wait_ms(150);
00417             this->pwm.write(0.0);
00418     
00419             this->pwm.period(1.0/196);
00420             this->pwm.write(0.5);
00421             wait_ms(150);
00422             this->pwm.write(0.0);
00423     
00424             this->pwm.period(1.0/164.81);
00425             this->pwm.write(0.5);
00426             wait_ms(150);
00427             this->pwm.write(0.0);
00428         }
00429         
00430         while (this->circle.read())
00431             wait_ms(1);
00432             
00433         this->initialize();
00434     }
00435     else {
00436         this->disp.drawString(0, 0, Game::LIVES, DisplayN18::WHITE, DisplayN18::BLACK);
00437         this->disp.drawCharacter(DisplayN18::CHAR_WIDTH * 8, 0, static_cast<char>(this->lives + '0'), DisplayN18::WHITE, DisplayN18::BLACK);   
00438     }
00439 }