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.
Fork of RETRO_Pong_Mod by
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 }
Generated on Wed Jul 13 2022 21:32:49 by 1.7.2