Balls & Paddle game for RETRO Pong inspired game featuring multi-directional tilt-sensitive paddle, multiple balls, shrinking ceiling and a bit of gravity.

Dependencies:   LCD_ST7735 MusicEngine RETRO_BallsAndThings mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers Game.cpp Source File

Game.cpp

00001 #include "Game.h"
00002 
00003 const char* Game::LOSE_1 = "Game over";
00004 const char* Game::LOSE_2 = "Press ship to restart";
00005 const char* Game::SPLASH_1 = "-*- Balls and paddle -*-";
00006 const char* Game::SPLASH_2 = "Press ship to start";
00007 const char* Game::SPLASH_3 = "Left/Right/tilt to play.";
00008 
00009 #define WHITE Color565::White
00010 #define BLACK Color565::Black
00011 #define BLUE Color565::Blue
00012 #define RED Color565::Red
00013 #define YELLOW Color565::Yellow
00014 
00015 #define CHAR_WIDTH 8
00016 #define CHAR_HEIGHT 8
00017 #define HEIGHT this->disp.getHeight()
00018 #define WIDTH this->disp.getWidth()
00019 
00020 //
00021 // Initialisation
00022 //
00023 
00024 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), 
00025     ain(P0_15), disp(P0_19, P0_20, P0_7, P0_21, P0_22, P1_15, P0_2, LCD_ST7735::RGB), accel(this->I2C_ADDR, &disp), vGravity(0, 0.1), paddle(&disp)
00026 {
00027     this->disp.setOrientation(LCD_ST7735::Rotate270, false);
00028     this->disp.setForegroundColor(WHITE);
00029     this->disp.setBackgroundColor(BLACK);
00030     this->disp.clearScreen();
00031 
00032     srand(this->ain.read_u16());
00033     
00034     this->lastUp = false;
00035     this->lastDown = false;
00036     this->mode = true;          // mode: true=game, false=graph
00037 
00038     this->nGameTickDelay=25;    // game tickdelay can be adjusted using up/down
00039 
00040     for(int i=0; i<NUM_BALLS; i++)
00041         this->aBalls[i]=Ball(&(this->disp));
00042 
00043     this->snd.reset();
00044 }
00045 
00046 void Game::initialize()
00047 {
00048     this->disp.clearScreen();
00049 
00050     this->snd.reset();
00051     this->nBalls = 4;
00052     this->nScore = 0;
00053     this->nTopWall = 8;
00054     this->fDrawTopWall=true;
00055     
00056     this->initializePaddle();
00057     this->setNoBalls();     // reset all balls
00058     this->newBall();     // start first ball
00059     this->snd.play("T240 L16 O5 D E F");
00060 }
00061 
00062 
00063 //
00064 // Generic methods
00065 //
00066 
00067 void Game::printDouble(double value, int x, int y)
00068 {
00069     char buffer[10];
00070     int len = sprintf(buffer, "%.1f ", value);
00071     
00072     this->disp.drawString(font_oem, x, y, buffer);
00073 }
00074 
00075 void Game::drawString(const char* str, int y)
00076 {
00077     uint8_t width;
00078     uint8_t height;
00079     
00080     this->disp.measureString(font_oem, str, width, height);
00081     this->disp.drawString(font_oem, WIDTH / 2 - width / 2, y, str);
00082     
00083 }
00084 
00085 void Game::printf(int x, int y, const char *szFormat, ...)
00086 {   // formats: %s, %d, %0.2f
00087     char szBuffer[256];
00088     va_list args;
00089 
00090     va_start(args, szFormat);
00091     vsprintf(szBuffer, szFormat, args);
00092     va_end(args);
00093     this->disp.drawString(font_oem, x, y, szBuffer);
00094 }
00095 
00096 int Game::checkTiltLeftRight()
00097 {    // check current X-tilting for left-right input (left=-1, right=1)
00098     double x, y, z;
00099     this->accel.getXYZ(x, y, z);
00100     if(x<-0.1) return(-1);
00101     else if(x>0.1) return(1);
00102     else return(0);
00103 }
00104 
00105 
00106 //
00107 // Paddle
00108 //
00109 
00110 void Game::initializePaddle()
00111 {
00112     this->paddle.initialize(WIDTH / 2 - Game::PADDLE_WIDTH/2, HEIGHT - Game::PADDLE_HEIGHT, Game::PADDLE_WIDTH, Game::PADDLE_HEIGHT);
00113     this->fDrawPaddle=true;
00114 }
00115 
00116 
00117 void Game::updatePaddle()
00118 {
00119     if (!this->left.read())  // note: read is LOW (0) when button pressed
00120         this->paddle.move(Vector(-1 * Game::PADDLE_SPEED, 0));
00121     else if (!this->right.read())
00122         this->paddle.move(Vector(Game::PADDLE_SPEED, 0));
00123     else
00124     {    // move the paddle by tilting the board left or right
00125         int i=this->checkTiltLeftRight();
00126         if(i>0)
00127             this->paddle.move(Vector(Game::PADDLE_SPEED, 0));
00128         else if(i<0)
00129             this->paddle.move(Vector(-1 * Game::PADDLE_SPEED, 0));
00130         else if(this->paddle.hasChanged())
00131             paddle.move(Vector(0, 0));  // move to same place to restrict redraws
00132     }
00133 }
00134 
00135 void Game::redrawPaddle()
00136 {   // redraw the paddle when moved, or when forced by this->fDrawPaddle (set at bounce)
00137     this->paddle.redraw(this->fDrawPaddle);
00138     this->fDrawPaddle=false;
00139 }
00140 
00141 void Game::checkPaddle()
00142 {
00143     Rectangle rScreen=Rectangle(0,0, WIDTH, HEIGHT);            // screen boundary
00144 
00145     this->paddle.checkBoundary(rScreen);
00146 }
00147 
00148 
00149 //
00150 // Balls
00151 //
00152 
00153 void Game::setNoBalls()
00154 {   // make sure no balls are active
00155     for(int i=0; i<NUM_BALLS; i++)
00156         this->aBalls[i].fActive=false;
00157 }
00158 
00159 void Game::newBall()
00160 {   // add a new ball to the game
00161     for(int i=0; i<NUM_BALLS; i++)
00162     {
00163         if(this->aBalls[i].fActive)
00164             continue;
00165         else
00166         {
00167             this->aBalls[i].initialize(WIDTH / 2 - Game::BALL_RADIUS, this->nTopWall + (HEIGHT-this->nTopWall) / 4 - Game::BALL_RADIUS, Game::BALL_RADIUS, Color565::fromRGB(i==0?0xFF:0x33, i==1?0xFF:0x33, i==2?0xFF:0x33));
00168             float ftRandX=((rand() % 20) - 10)/5.0;     // left/right at random speed
00169             float ftRandY=((rand() % 10) - 10)/5.0;     // up at random speed
00170             this->aBalls[i].vSpeed.set(ftRandX, ftRandY);
00171             this->aBalls[i].fActive=true;
00172             break;
00173         }
00174     }
00175 }
00176 
00177 void Game::updateBalls()
00178 {
00179     for(int i=0; i<NUM_BALLS; i++)
00180     {
00181         if(!this->aBalls[i].fActive)
00182             continue;
00183 
00184         this->aBalls[i].update();                    // update the ball position 
00185 
00186         // add downward gravity
00187         if(this->aBalls[i].vSpeed.getSize()<10.0)
00188             this->aBalls[i].vSpeed.add(this->vGravity);    // add some gravity
00189 
00190     }
00191 }
00192 
00193 void Game::redrawBalls()
00194 {
00195     for(int i=0; i<NUM_BALLS; i++)
00196     {
00197         if(!this->aBalls[i].fActive)
00198             continue;
00199         this->aBalls[i].redraw();                    // update the ball position 
00200     }
00201 }
00202 
00203 int Game::countBalls()
00204 {
00205     int nResult=0;
00206     for(int i=0; i<NUM_BALLS; i++)
00207     {
00208         if(this->aBalls[i].fActive)
00209             nResult++;
00210     }
00211     return(nResult);
00212 }
00213 
00214 void Game::checkNumBalls()
00215 {
00216     if (this->nBalls == 0)
00217     {   // game over
00218         char buf[256];
00219         this->disp.clearScreen();
00220 
00221         this->drawString(Game::LOSE_1, HEIGHT / 2 - CHAR_HEIGHT); 
00222         this->drawString(Game::LOSE_2, HEIGHT / 2);  
00223         sprintf(buf,"Your score: %d  ", this->nScore);
00224         this->drawString(buf, HEIGHT / 2 + CHAR_HEIGHT / 2 + CHAR_HEIGHT ); 
00225         
00226         this->snd.play("T120 O3 L4 R4 F C F2 C");
00227         while (this->circle.read())
00228             wait_ms(1);
00229         wait_ms(250);   // el-cheapo deboounce
00230         this->initialize();
00231     }
00232     else
00233     {
00234         this->printf(0, 0, "Balls: %d  ", this->nBalls);   
00235     }
00236 }
00237 
00238 
00239 
00240 void Game::checkBallsCollision()
00241 {
00242     Rectangle rTop=Rectangle(0, -10, WIDTH, this->nTopWall);       // top wall
00243     Rectangle rBottom=Rectangle(0, HEIGHT, WIDTH, HEIGHT+10);      // bottom gap
00244     Rectangle rLeft=Rectangle(-10, 0, 0, HEIGHT);                  // left wall
00245     Rectangle rRight=Rectangle(WIDTH, 0, WIDTH+10, HEIGHT);        // right wall
00246     Rectangle rPaddle=Rectangle(paddle.pos.getX(), paddle.pos.getY(), paddle.pos.getX() + Game::PADDLE_WIDTH, HEIGHT+10);        // paddle
00247     Rectangle rPaddleLeft=Rectangle(paddle.pos.getX(), paddle.pos.getY(), paddle.pos.getX() + Game::PADDLE_WIDTH/3, HEIGHT+10);      // paddle left part
00248     Rectangle rPaddleMiddle=Rectangle(paddle.pos.getX() + Game::PADDLE_WIDTH/3, paddle.pos.getY(), paddle.pos.getX() + Game::PADDLE_WIDTH/3 + Game::PADDLE_WIDTH/3, HEIGHT+10);      // paddle middle part
00249     Rectangle rPaddleRight=Rectangle(paddle.pos.getX()+ Game::PADDLE_WIDTH/3 + Game::PADDLE_WIDTH/3, paddle.pos.getY(), paddle.pos.getX() + Game::PADDLE_WIDTH, HEIGHT+10);      // paddle right part
00250 
00251     Line lPaddleLeft=Line(paddle.pos.getX(), paddle.pos.getY(), paddle.pos.getX() + Game::PADDLE_WIDTH/3, HEIGHT+10);      // paddle left part
00252     Line lPaddleRight=Line(paddle.pos.getX()+ Game::PADDLE_WIDTH/3 + Game::PADDLE_WIDTH/3, paddle.pos.getY(), paddle.pos.getX() + Game::PADDLE_WIDTH, HEIGHT+10);      // paddle right part
00253 
00254     //printf(0, 20, "Paddle: %d-%d  %d-%d  ", rPaddleLeft.getX1(), rPaddleLeft.getX2(), rPaddleRight.getX1(), rPaddleRight.getX2());
00255 
00256     Ball* pBall;
00257     for(int i=0; i<NUM_BALLS; i++)
00258     {
00259         if(!this->aBalls[i].fActive)
00260             continue;
00261 
00262         pBall=&(this->aBalls[i]);
00263 
00264         if(pBall->collides(rTop) && pBall->vSpeed.isUp())      // top wall
00265         {
00266             pBall->Bounce(Vector(1,-1));        // bounce vertical
00267             this->snd.beepShort();
00268             this->fDrawTopWall=true;
00269         }
00270         if(pBall->collides(rRight) && pBall->vSpeed.isRight())      // right wall
00271         {
00272             pBall->Bounce(Vector(-1,1));        // bounce horizontal
00273             this->snd.beepShort();
00274         }
00275         if(pBall->collides(rLeft) && pBall->vSpeed.isLeft())      // left wall
00276         {
00277             pBall->Bounce(Vector(-1,1));        // bounce horizontal
00278             this->snd.beepShort();
00279         }
00280         if(pBall->collides(rPaddle) && pBall->vSpeed.isDown())      // paddle
00281         {
00282             if(pBall->collides(lPaddleLeft) || pBall->collides(lPaddleRight) || pBall->collides(rPaddleMiddle))
00283             {
00284                 if(pBall->collides(lPaddleLeft))   pBall->vSpeed.add(Vector(-1.3,0));      // left side of paddle has bias to the left
00285                 if(pBall->collides(lPaddleRight))  pBall->vSpeed.add(Vector(1.3,0));       // right side of paddle has bias to the right
00286                 pBall->Bounce(Vector(1,-1));
00287               
00288                 {
00289                     // increase the speed of the ball when hitting the paddle to increase difficulty
00290                     float ftSpeedMax=3.0;
00291                     if(this->nScore>50)
00292                         ftSpeedMax=5.0;
00293                     if(this->nScore>100)
00294                         ftSpeedMax=10.0;
00295                     if(this->nScore>150)
00296                         ftSpeedMax=999.0;
00297                     if(pBall->vSpeed.getSize()<ftSpeedMax)
00298                         pBall->vSpeed.multiply(Vector(1,1.02));        // bounce up from paddle at higher speed
00299                 }
00300                 //printf(10, 10, "Bounce: %0.2f, %0.2f  ", pBall->vSpeed.x, pBall->vSpeed.y);
00301         
00302                 // force drawing the paddle after redrawing the bounced ball
00303                 this->fDrawPaddle=true;
00304     
00305                 // make sound and update the score
00306                 this->snd.beepLong();
00307                 this->nScore++;
00308                 this->printf(100, 0, "Score: %d ", this->nScore);   
00309     
00310                 // add a new ball every 10 points
00311                 if(this->nScore>0 && this->nScore%10==0)
00312                 {
00313                     this->newBall();
00314                     this->nBalls++;
00315                     this->snd.play("T240 L16 O5 D E F");
00316                 }
00317     
00318                 // lower the ceiling every 25 points
00319                 if(this->nScore>0 && this->nScore%25==0)
00320                 {
00321                     this->nTopWall+=3;
00322                     this->fDrawTopWall=true;
00323                     this->snd.play("T240 L16 O5 CDEFG");
00324                 }
00325             }
00326         }
00327         if(pBall->collides(rBottom) && pBall->vSpeed.isDown())      // bottom gap
00328         {
00329             pBall->clearPrev();   // clear the ball from its previous position
00330             pBall->clear();   // clear the ball from its current position
00331             pBall->vSpeed.set(0,0);
00332             pBall->fActive=false;
00333             this->nBalls--;
00334             if(countBalls()==0)
00335             {
00336                 this->newBall();     // start a new ball
00337                 this->snd.beepLow();
00338             }
00339             this->fDrawPaddle=true;
00340         }
00341     }
00342 }
00343 
00344 
00345 //
00346 // Other gamestuff
00347 //
00348 
00349 void Game::tick()
00350 {  
00351     this->checkButtons();
00352     
00353     if (this->mode) {
00354 
00355         this->updateBalls();                    // update the ball positions
00356         this->updatePaddle();
00357     
00358         this->checkPaddle();
00359         this->checkBallsCollision();
00360 
00361         this->redrawBalls();
00362         this->redrawPaddle();
00363         this->redrawTopWall();
00364         
00365         //this->checkScore(); 
00366         this->checkNumBalls(); 
00367         
00368         wait_ms(this->nGameTickDelay);  // can be adjusted using up/down
00369     }
00370     else {
00371         this->accel.updateGraph();
00372         wait_ms(100);
00373     } 
00374 }
00375 
00376 
00377 void Game::checkButtons()
00378 {
00379     if(!this->square.read())       // note: button.read() is false (LOW/0) when pressed
00380     {
00381         wait_ms(250);   // el-cheapo deboounce
00382         this->mode = !this->mode;
00383         
00384         this->disp.clearScreen();
00385         
00386         if (!this->mode)
00387         {
00388             this->accel.resetGraph();
00389         }
00390         
00391         this->led1.write(this->mode);
00392         this->led2.write(!this->mode);
00393     }
00394     else if(!this->circle.read() && this->mode)       // note: button.read() is false (LOW/0) when pressed
00395     {
00396         bool fMute=this->snd.getMute();
00397         fMute=!fMute;
00398         this->snd.setMute(fMute);
00399         this->led2.write(fMute);
00400         wait_ms(250);   // el-cheapo deboounce
00401     }
00402     else
00403     {  
00404         bool isUp = !this->up.read();
00405         bool isDown = !this->down.read();
00406         
00407         if (isUp && isDown) goto end;
00408         if (!isUp && !isDown) goto end;
00409         
00410         if (isUp && this->lastUp) goto end;
00411         if (isDown && this->lastDown) goto end;
00412         
00413         if (isUp)
00414         {
00415             if(this->nGameTickDelay<1000) this->nGameTickDelay=(float)this->nGameTickDelay*1.20;
00416             this->printf(100, 0, "Speed: %d  ", this->nGameTickDelay);   
00417         }
00418         else if (isDown)
00419         {
00420             if(this->nGameTickDelay>5) this->nGameTickDelay=(float)this->nGameTickDelay/1.20;
00421             this->printf(100, 0, "Speed: %d  ", this->nGameTickDelay);   
00422         }
00423     
00424 end:
00425         this->lastUp = isUp;
00426         this->lastDown = isDown;
00427     }
00428 }
00429 
00430 
00431 void Game::showSplashScreen()
00432 {
00433     this->drawString(Game::SPLASH_1, HEIGHT / 2 - CHAR_HEIGHT / 2);  
00434     this->drawString(Game::SPLASH_2, HEIGHT / 2 + CHAR_HEIGHT / 2); 
00435     this->drawString(Game::SPLASH_3, HEIGHT / 2 + CHAR_HEIGHT / 2 + 2*CHAR_HEIGHT); 
00436            
00437     while (this->circle.read())
00438         wait_ms(1);
00439     wait_ms(250);   // el-cheapo deboounce
00440 
00441     this->initialize();     // start a new game
00442 }
00443 
00444 
00445 void Game::redrawTopWall()
00446 {
00447     if(this->fDrawTopWall)
00448     {
00449         int nTop=max(this->nTopWall-2, 8);
00450         this->disp.fillRect(0, 8, WIDTH, nTop, Color565::Black);
00451         this->disp.fillRect(0, nTop, WIDTH, this->nTopWall, Color565::Purple);
00452         this->fDrawTopWall=false;
00453     }
00454 }