Retro game that let's the player steer a ball through a hole filled maze. Has multiple levels of increasing difficulty.

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 = "-*- Ball and holes -*-";
00006 const char* Game::SPLASH_2 = "Press ship to start.";
00007 const char* Game::SPLASH_3 = "Tilt console to steer ball.";
00008 
00009 
00010 #define WHITE Color565::White
00011 #define BLACK Color565::Black
00012 #define BLUE Color565::Blue
00013 #define RED Color565::Red
00014 #define YELLOW Color565::Yellow
00015 
00016 #define CHAR_WIDTH 8
00017 #define CHAR_HEIGHT 8
00018 #define HEIGHT this->disp.getHeight()
00019 #define WIDTH this->disp.getWidth()
00020 
00021 
00022 
00023 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), 
00024     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), vFriction(-0.005, -0.005), ball(&disp)
00025 {
00026     this->disp.setOrientation(LCD_ST7735::Rotate270, false);
00027     this->disp.setForegroundColor(WHITE);
00028     this->disp.setBackgroundColor(BLACK);
00029     this->disp.clearScreen();
00030 
00031     srand(this->ain.read_u16());
00032     
00033     this->lastUp = false;
00034     this->lastDown = false;
00035     this->mode = true;          // mode: true=game, false=graph
00036     
00037     this->nGameTickDelay=25;    // game tickdelay can be adjusted using up/down
00038 
00039     nTopWall=8; // room for display of game stats such as the score
00040     for(int i=0; i<NUM_WALLS; i++)
00041         aWalls[i]=Wall(&(this->disp));
00042     for(int i=0; i<NUM_HOLES; i++)
00043         aHoles[i]=Hole(&(this->disp));
00044 
00045     this->snd.reset();
00046 }
00047 
00048 void Game::printDouble(double value, int x, int y)
00049 {
00050     char buffer[10];
00051     int len = sprintf(buffer, "%.1f ", value);
00052     
00053     this->disp.drawString(font_oem, x, y, buffer);
00054 }
00055 
00056 void Game::initialize()
00057 {
00058     nBalls = 4;
00059     nScore = 0;
00060     
00061     tWait.start();      // start the timer
00062 
00063     for(int i=0; i<NUM_WALLS; i++)
00064         aWalls[i]=Wall(&(disp));
00065     
00066     initLevel();
00067 
00068     newBall();     // start first ball
00069     snd.play("T240 L16 O5 D E F");
00070 }
00071 
00072 void Game::initLevel()
00073 {
00074     char szLevel0[]="01:1 0262 2787-2227 8187 ";
00075     char szLevel1[]="01:1 0292 0343 63:3 2484 0545 65:5 1797-5257 1617 2526 3637 7677 8586 9697 ";
00076     char szLevel2[]="01:1 0232 82:2 1343 6393 0484 1696-4143 6163 5355 9396 3435 7475 1617 4547 5758 6567 ";
00077     char szLevel3[]="01:1 0232 82:2 3444 1545 6696-4147 6166 2223 8284 1315 5358 9397 ";
00078     
00079     disp.clearScreen();
00080     snd.reset();
00081 
00082     // reset current walls and holes
00083     for(int i=0; i<NUM_WALLS; i++)
00084         aWalls[i].fActive=false;
00085     for(int i=0; i<NUM_HOLES; i++)
00086         aHoles[i].fActive=false;
00087 
00088     // add walls/holes depending on level
00089     switch(nScore)
00090     {
00091         case 0:
00092             addWalls(szLevel0);
00093             addHoles("4411 6412 ");
00094             break;
00095         case 1:
00096             addWalls(szLevel1);
00097             addHoles("6411 0312 9412 4612 5612 ");
00098             break;
00099         case 2:
00100             addWalls(szLevel1);
00101             addHoles("5711 0312 9412 4612 5612 ");
00102             break;
00103         case 3:
00104             addWalls(szLevel1);
00105             addHoles("0211 0312 9412 4612 5612 ");
00106             break;
00107 
00108         case 4:
00109             addWalls(szLevel2);
00110             addHoles("4411 6112 5112 1512 8512 2712 7712 8612 ");
00111             break;
00112         case 5:
00113             addWalls(szLevel2);
00114             addHoles("5611 6112 5112 1512 8512 2712 7712 8612 ");
00115             break;
00116         case 6:
00117             addWalls(szLevel2);
00118             addHoles("9111 6112 5112 1512 8512 2712 7712 8612 ");
00119             break;
00120 
00121         case 7:
00122             addWalls(szLevel3);
00123             addHoles("3411 4112 5112 6112 2212 3312 6312 1412 7412 8512 0612 2612 6612 8612 5712 7712 ");
00124             break;
00125         case 8:
00126             addWalls(szLevel3);
00127             addHoles("5211 4112 5112 6112 2212 3312 6312 1412 7412 8512 0612 2612 6612 8612 5712 7712 ");
00128             break;
00129         case 9:
00130             addWalls(szLevel3);
00131             addHoles("9711 4112 5112 6112 2212 3312 6312 1412 7412 8512 0612 2612 6612 8612 5712 7712 ");
00132             break;
00133         case 10:
00134             addWalls(szLevel3);
00135             addHoles("9111 4112 5112 6112 2212 3312 6312 1412 7412 8512 0612 2612 6612 8612 5712 7712 ");
00136             break;
00137         case 11:
00138         default:
00139             addWalls(szLevel3);
00140             addHoles("9111 4112 5112 6112 2212 3312 6312 1412 7452 8512 0612 2652 6612 8612 5712 7712 ");
00141             break;
00142     }
00143 
00144 
00145     drawWalls();
00146     drawHoles();
00147     checkNumBalls();
00148 }
00149 
00150 
00151 
00152 Point Game::getGridPos(char *sPos)
00153 {   // get item pos based on a grid definition of 16x16 pixel squares, layed out in a 10x8 grid
00154     // sPos is a 2 character string containing the coordinates in decimal notation: xy
00155     int x=(sPos[0]-'0')*16+8;
00156     int y=(sPos[1]-'0')*16+8;
00157     return(Point(x,y));
00158 }
00159 
00160 void Game::addWall(char *sWall)
00161 {   // add a wall based on a grid definition of 16x16 pixel squares, layed out in a 10x8 grid
00162     // sWall is a 4 character string containing the edge coordinates in decimal notation: xyXY
00163     // grid axis range from x: '0'-'9', ':'=10  - y:  '0'-'8'
00164     for(int i=0; i<NUM_WALLS; i++)
00165     {
00166         if(!aWalls[i].fActive)
00167         {
00168             int x1=sWall[0]-'0';
00169             int y1=sWall[1]-'0';
00170             int x2=sWall[2]-'0';
00171             int y2=sWall[3]-'0';
00172             aWalls[i].setRect(Rectangle(x1*16,y1*16,x2*16+1,y2*16+1));
00173             aWalls[i].fActive=true;
00174             break;
00175         }
00176     }
00177 }
00178 
00179 void Game::addWalls(char *sWalls)
00180 {
00181     char sWall[]="0000";
00182     for(int i=0; i<strlen(sWalls); i+=5)
00183     {
00184         strncpy(sWall, sWalls+i, 4);
00185         addWall(sWall);
00186     }
00187 }
00188 
00189 
00190 void Game::drawWalls()
00191 {
00192     for(int i=0; i<NUM_WALLS; i++)
00193         if(aWalls[i].fActive)
00194         {
00195             aWalls[i].draw();
00196         }
00197 }
00198 
00199 void Game::addHole(char *sHole)
00200 {   // add a wall based on a grid definition of 16x16 pixel squares, layed out in a 10x8 grid
00201     // sWall is a 4 character string containing the edge coordinates in decimal notation: xyXY
00202     // grid axis range from x: '0'-'9', ':'=10  - y:  '0'-'8'
00203     for(int i=0; i<NUM_WALLS; i++)
00204     {
00205         if(!aHoles[i].fActive)
00206         {
00207             int x=(sHole[0]-'0')*16+8;
00208             int y=(sHole[1]-'0')*16+8;
00209             int r=sHole[2]-'0';
00210             int c=sHole[3]-'0';
00211             int rnd1=0, rnd2=0;
00212             if(r==2) rnd1=rand() % r - r/2;
00213             if(r==2) rnd2=rand() % r - r/2;
00214             aHoles[i].setCirc(Circle(x+rnd1,y+rnd2, Game::HOLE_RADIUS));
00215             if(c==1) aHoles[i].setColor(Color565::Green);
00216             if(c==2) aHoles[i].setColor(Color565::Gray);
00217             if(c==3) aHoles[i].setColor(Color565::Red);
00218             aHoles[i].fActive=true;
00219             break;
00220         }
00221     }
00222 }
00223 
00224 void Game::addHoles(char *sHoles)
00225 {
00226     char sHole[]="0000";
00227     for(int i=0; i<strlen(sHoles); i+=5)
00228     {
00229         strncpy(sHole, sHoles+i, 4);
00230         addHole(sHole);
00231     }
00232 }
00233 
00234 
00235 void Game::drawHoles()
00236 {
00237     for(int i=0; i<NUM_HOLES; i++)
00238         if(aHoles[i].fActive)
00239         {
00240             aHoles[i].draw();
00241         }
00242 }
00243 
00244 void Game::newBall()
00245 {   // add a ball to the game
00246     Point ptBall=getGridPos("01");
00247     ball.initialize(ptBall.getX(), ptBall.getY(), Game::BALL_RADIUS, Color565::White);
00248 }
00249 
00250 void Game::updateBall()
00251 {
00252     ball.update();                    // update the ball position 
00253 
00254     // increase speed based on gravity
00255     checkTilt();
00256     if(ball.vSpeed.getSize()<2.0)
00257         ball.vSpeed.add(this->vGravity);    // add some gravity
00258         
00259     // decrease speed based on friction
00260     Vector vDecel=ball.vSpeed.getNormalized();
00261     vDecel.multiply(vFriction);
00262     ball.vSpeed.add(vDecel);
00263 }
00264 
00265 void Game::redrawBall()
00266 {
00267     ball.redraw();                    // update the ball position 
00268 }
00269 
00270 
00271 void Game::tick()
00272 {  
00273     checkButtons();
00274     
00275     if(mode)
00276     {
00277 /*
00278         if(this->tWait.read_ms()>100)
00279         {
00280             this->tWait.reset();
00281         }
00282 */
00283 
00284         updateBall();                    // update the ball positions
00285     
00286         checkBallCollision();
00287 
00288         redrawBall();
00289         
00290         if(this->tWait.read_ms()>100)
00291         {   // redraw walls and holes every tenth second
00292             drawWalls();
00293             drawHoles();
00294 
00295             checkNumBalls();
00296 
00297             this->tWait.reset();
00298         }
00299         
00300 //        this->snd.checkPwm();
00301         //this->checkScore(); 
00302         //this->checkBall(); 
00303         
00304         wait_ms(nGameTickDelay);  // can be adjusted using up/down
00305     }
00306     else
00307     {
00308         accel.updateGraph();
00309         wait_ms(100);
00310     } 
00311 }
00312 
00313 void Game::checkTilt()
00314 {    // move the gravity direction and weight by tilting the board
00315     double x, y, z;
00316     this->accel.getXYZ(x, y, z);
00317 
00318     vGravity=Vector(x,y);
00319     
00320     // don't let the tilt generate too much gravity
00321     while(vGravity.getSize()>0.5)
00322         vGravity.multiply(Vector(0.9, 0.9));
00323 }
00324 
00325 void Game::checkButtons()
00326 {
00327     if(!this->square.read())       // note: button.read() is false (LOW/0) when pressed
00328     {
00329         wait_ms(250);   // el-cheapo deboounce
00330         this->mode = !this->mode;
00331         
00332         this->disp.clearScreen();
00333         
00334         if (!this->mode)
00335         {
00336             this->accel.resetGraph();
00337         }
00338         
00339         this->led1.write(this->mode);
00340         this->led2.write(!this->mode);
00341     }
00342     else if(!this->circle.read() && this->mode)       // note: button.read() is false (LOW/0) when pressed
00343     {
00344         bool fMute=this->snd.getMute();
00345         fMute=!fMute;
00346         this->snd.setMute(fMute);
00347         this->led2.write(fMute);
00348         wait_ms(250);   // el-cheapo deboounce
00349     }
00350     else
00351     {  
00352         bool isUp = !this->up.read();
00353         bool isDown = !this->down.read();
00354 
00355         if(!this->left.read())
00356         {   // cheat: restart level
00357             snd.play("T240 L128 O6 CEF");
00358             ball.fActive=false;
00359             initLevel();
00360             wait_ms(500);
00361             newBall();     // start a new ball
00362             return;
00363         }
00364         else if(!this->right.read())
00365         {   // cheat: next level
00366             snd.play("T240 L128 O5 FEC");
00367             nScore++;
00368             ball.fActive=false;
00369             initLevel();
00370             wait_ms(500);
00371             newBall();     // start a new ball
00372             return;
00373         }
00374 
00375         
00376         if (isUp && isDown) goto end;
00377         if (!isUp && !isDown) goto end;
00378         
00379         if (isUp && this->lastUp) goto end;
00380         if (isDown && this->lastDown) goto end;
00381         
00382         if (isUp)
00383         {
00384             if(this->nGameTickDelay<1000) this->nGameTickDelay=(float)this->nGameTickDelay*1.20;
00385             this->printf(100, 0, "Speed: %d  ", this->nGameTickDelay);   
00386         }
00387         else if (isDown)
00388         {
00389             if(this->nGameTickDelay>5) this->nGameTickDelay=(float)this->nGameTickDelay/1.20;
00390             this->printf(100, 0, "Speed: %d  ", this->nGameTickDelay);   
00391         }
00392     
00393 end:
00394         this->lastUp = isUp;
00395         this->lastDown = isDown;
00396     }
00397 }
00398 
00399 void Game::drawString(const char* str, int y)
00400 {
00401     uint8_t width;
00402     uint8_t height;
00403     
00404     this->disp.measureString(font_oem, str, width, height);
00405     this->disp.drawString(font_oem, WIDTH / 2 - width / 2, y, str);
00406     
00407 }
00408 
00409 void Game::showSplashScreen()
00410 {
00411     this->drawString(Game::SPLASH_1, HEIGHT / 2 - CHAR_HEIGHT / 2);  
00412     this->drawString(Game::SPLASH_2, HEIGHT / 2 + CHAR_HEIGHT / 2); 
00413     this->drawString(Game::SPLASH_3, HEIGHT / 2 + CHAR_HEIGHT / 2 + 2*CHAR_HEIGHT); 
00414 
00415     while (this->circle.read())
00416         wait_ms(1);
00417     wait_ms(250);   // el-cheapo deboounce
00418 
00419     this->initialize();     // start a new game
00420 }
00421 
00422 void Game::checkBallCollision()
00423 {
00424     Rectangle rTop=Rectangle(0, -10, WIDTH, this->nTopWall);       // top wall
00425     Rectangle rBottom=Rectangle(0, HEIGHT, WIDTH, HEIGHT+10);      // bottom wall
00426     Rectangle rLeft=Rectangle(-10, 0, 0, HEIGHT);                  // left wall
00427     Rectangle rRight=Rectangle(WIDTH, 0, WIDTH+10, HEIGHT);        // right wall
00428 
00429     if(ball.collides(rTop) && ball.vSpeed.isUp())                 // top wall
00430         ball.vSpeed.y=0;
00431     if(ball.collides(rRight) && ball.vSpeed.isRight())            // right wall
00432         ball.vSpeed.x=0;
00433     if(ball.collides(rLeft) && ball.vSpeed.isLeft())              // left wall
00434         ball.vSpeed.x=0;
00435     if(ball.collides(rBottom) && ball.vSpeed.isDown())            // bottom wall
00436         ball.vSpeed.y=0;
00437 
00438     for(int i=0; i<NUM_WALLS; i++)      // check maze walls
00439     {
00440         if(aWalls[i].fActive)
00441         {
00442             Rectangle rWall=aWalls[i].getRect();
00443             if(ball.collides(rWall))
00444             {
00445                 //printf(30, 0, "b: %.2f", ball.vSpeed.getSize());
00446                 if(rWall.isHorizontal())
00447                 {
00448                     if(fabs(ball.vSpeed.y)>0.8)
00449                         snd.play("T240 L128 O4 A");
00450                     ball.Bounce(Vector(1,-0.1));
00451                 }
00452                 else
00453                 {
00454                     if(fabs(ball.vSpeed.x)>0.8)
00455                         snd.play("T240 L128 O4 A");
00456                     ball.Bounce(Vector(-0.1,1));
00457                 }
00458                 // TODO: the ball can stick to the end of wall.
00459                 // To fix, the above code should compare the position of the ball relative to the wall, not just the speed and wall-orientation
00460                 // Although this is a bug, its causes some interesting game-play, so leave it in for now
00461                 // Magnetic walls are fun!
00462                 
00463                 aWalls[i].draw();
00464             }
00465             //printf(0, 100, "W: %d,%d - %d,%d", rWall.getX1(), rWall.getY1(), rWall.getX2(), rWall.getY2());
00466         }
00467     }
00468 
00469     for(int i=0; i<NUM_HOLES; i++)      // check holes
00470     {
00471         if(aHoles[i].fActive)
00472         {
00473             Circle cHole=aHoles[i].getCirc();
00474             if(ball.collides(cHole))
00475             {
00476                 if(aHoles[i].hasGoneIn(ball.getBoundingCircle()))
00477                 {
00478                     ball.fActive=false;
00479                     if(i==0)
00480                     {   // TODO: for now first hole array is assumed to be the target hole
00481                         nScore++;
00482                         snd.play("T240 L16 O5 CDEFG");
00483                     }
00484                     else
00485                     {
00486                         nBalls--;
00487                         snd.beepLow();
00488                     }
00489 
00490                     initLevel();
00491                     wait_ms(500);
00492 
00493                     newBall();     // start a new ball
00494                 }                                    
00495             }
00496         }
00497     }
00498 }
00499 
00500 
00501 void Game::printf(int x, int y, const char *szFormat, ...)
00502 {
00503     char szBuffer[256];
00504     va_list args;
00505 
00506     va_start(args, szFormat);
00507     vsprintf(szBuffer, szFormat, args);
00508     va_end(args);
00509     this->disp.drawString(font_oem, x, y, szBuffer);
00510 }
00511 
00512 void Game::checkNumBalls()
00513 {
00514     if (this->nBalls == 0)
00515     {   // game over
00516         char buf[256];
00517         this->disp.clearScreen();
00518 
00519         this->drawString(Game::LOSE_1, HEIGHT / 2 - CHAR_HEIGHT); 
00520         this->drawString(Game::LOSE_2, HEIGHT / 2);  
00521         sprintf(buf,"Your score: %d  ", this->nScore);
00522         this->drawString(buf, HEIGHT / 2 + CHAR_HEIGHT / 2 + CHAR_HEIGHT ); 
00523         
00524         this->snd.play("T120 O3 L4 R4 F C F2 C");
00525         while (this->circle.read())
00526             wait_ms(1);
00527         wait_ms(250);   // el-cheapo deboounce
00528         this->initialize();
00529     }
00530     else
00531     {
00532         printf(0, 0, "Balls: %d  ", this->nBalls);   
00533         printf(100, 0, "Score: %d ", this->nScore);
00534     }
00535 }