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
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 }
Generated on Fri Jul 15 2022 22:39:35 by 1.7.2