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
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 }
Generated on Wed Jul 27 2022 14:50:36 by 1.7.2