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

Ball and Holes

In this game I attempted to create somewhat natural movement of the ball by implementing gravity and friction which combined over time determine the speed of the ball. Playing with the settings (aka. the magic numbers) that are spread out all over game.cpp, gives different effects, such as an icy, rough or liquid-like surface.

It took some time to figure out how to post my very first youtube video. Sorry for the shaky recording. Trying to record the video with my phone while playing the game in one hand was quite challenging, but here it is;

The left and right buttons are used to cheat: restart the current or go to the next level. Up and down control the game-tick. During game-play the robot-button shows the accelerator graph and the ship-button mutes the sound.

BTW. If your ball happens to get stuck, tilting the console in the opposite direction will set it free. For sake of argument: these magnetic wall-ends are in the words of Bob Ross "a happy accident". Since there is no specific code for it, others might call it a bug. As it results in more interesting game-play, I didn't attempt to fix it, but left a comment for those who dare to look at the mess I call code.

Committer:
maxint
Date:
Sun Feb 01 16:21:24 2015 +0000
Revision:
1:c1ee4c699517
Parent:
0:87ab172a74b4
Child:
2:d4de5a5866fe
moved graph to accelerometer class

Who changed what in which revision?

UserRevisionLine numberNew contents of line
maxint 0:87ab172a74b4 1 #include "Game.h"
maxint 0:87ab172a74b4 2
maxint 0:87ab172a74b4 3 const char* Game::LOSE_1 = "You lose.";
maxint 0:87ab172a74b4 4 const char* Game::LOSE_2 = "Press ship to restart.";
maxint 0:87ab172a74b4 5 const char* Game::SPLASH_1 = "Press ship to start.";
maxint 0:87ab172a74b4 6 const char* Game::SPLASH_2 = "Press robot to switch.";
maxint 0:87ab172a74b4 7 const char* Game::SPLASH_3 = "mod:Tilt by Maxint.";
maxint 0:87ab172a74b4 8
maxint 0:87ab172a74b4 9
maxint 0:87ab172a74b4 10 #define WHITE Color565::White
maxint 0:87ab172a74b4 11 #define BLACK Color565::Black
maxint 0:87ab172a74b4 12 #define BLUE Color565::Blue
maxint 0:87ab172a74b4 13 #define RED Color565::Red
maxint 0:87ab172a74b4 14 #define YELLOW Color565::Yellow
maxint 0:87ab172a74b4 15
maxint 0:87ab172a74b4 16 #define CHAR_WIDTH 8
maxint 0:87ab172a74b4 17 #define CHAR_HEIGHT 8
maxint 0:87ab172a74b4 18 #define HEIGHT this->disp.getHeight()
maxint 0:87ab172a74b4 19 #define WIDTH this->disp.getWidth()
maxint 0:87ab172a74b4 20
maxint 0:87ab172a74b4 21
maxint 0:87ab172a74b4 22
maxint 0:87ab172a74b4 23 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),
maxint 1:c1ee4c699517 24 pwm(P0_18), ain(P0_15), i2c(P0_5, P0_4), 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), ball(&disp), paddle(&disp)
maxint 0:87ab172a74b4 25 {
maxint 1:c1ee4c699517 26 this->disp.setOrientation(LCD_ST7735::Rotate270, false);
maxint 1:c1ee4c699517 27 this->disp.setForegroundColor(WHITE);
maxint 1:c1ee4c699517 28 this->disp.setBackgroundColor(BLACK);
maxint 1:c1ee4c699517 29 this->disp.clearScreen();
maxint 1:c1ee4c699517 30
maxint 0:87ab172a74b4 31 srand(this->ain.read_u16());
maxint 0:87ab172a74b4 32
maxint 0:87ab172a74b4 33 this->lastUp = false;
maxint 0:87ab172a74b4 34 this->lastDown = false;
maxint 0:87ab172a74b4 35 this->mode = true;
maxint 0:87ab172a74b4 36
maxint 0:87ab172a74b4 37 this->initialize();
maxint 0:87ab172a74b4 38 }
maxint 0:87ab172a74b4 39
maxint 0:87ab172a74b4 40 void Game::printDouble(double value, int x, int y) {
maxint 0:87ab172a74b4 41 char buffer[10];
maxint 0:87ab172a74b4 42 int len = sprintf(buffer, "%.1f ", value);
maxint 0:87ab172a74b4 43
maxint 1:c1ee4c699517 44 this->disp.drawString(font_oem, x, y, buffer);
maxint 0:87ab172a74b4 45 }
maxint 0:87ab172a74b4 46
maxint 1:c1ee4c699517 47 void Game::initialize()
maxint 1:c1ee4c699517 48 {
maxint 0:87ab172a74b4 49 this->disp.clearScreen();
maxint 0:87ab172a74b4 50
maxint 1:c1ee4c699517 51 this->initializeBall(); // start first ball
maxint 0:87ab172a74b4 52 this->initializePaddle();
maxint 0:87ab172a74b4 53
maxint 1:c1ee4c699517 54 // this->paddleX = WIDTH / 2 - Game::PADDLE_WIDTH / 2;
maxint 0:87ab172a74b4 55 this->pwmTicksLeft = 0;
maxint 1:c1ee4c699517 56 this->nLives = 4;
maxint 1:c1ee4c699517 57 this->nScore = 0;
maxint 0:87ab172a74b4 58
maxint 0:87ab172a74b4 59 this->pwm.period_ms(1);
maxint 0:87ab172a74b4 60 this->pwm.write(0.00);
maxint 0:87ab172a74b4 61
maxint 0:87ab172a74b4 62 this->tWait.start(); // start the timer
maxint 0:87ab172a74b4 63
maxint 0:87ab172a74b4 64 }
maxint 0:87ab172a74b4 65
maxint 0:87ab172a74b4 66 void Game::initializeBall()
maxint 0:87ab172a74b4 67 {
maxint 1:c1ee4c699517 68 this->ball.initialize(WIDTH / 2 - Game::BALL_RADIUS, HEIGHT / 4 - Game::BALL_RADIUS, Game::BALL_RADIUS, Color565::fromRGB(0xFF, 0x33, 0x33));
maxint 0:87ab172a74b4 69 this->ball.setSpeed(rand() % 2 ? 1 : -1, rand() % 2 ? 1 : -1);
maxint 0:87ab172a74b4 70 }
maxint 0:87ab172a74b4 71
maxint 0:87ab172a74b4 72 void Game::initializePaddle()
maxint 0:87ab172a74b4 73 {
maxint 1:c1ee4c699517 74 this->paddle.initialize(WIDTH / 2 - Game::PADDLE_WIDTH/2, HEIGHT - Game::PADDLE_HEIGHT, Game::PADDLE_WIDTH, Game::PADDLE_HEIGHT);
maxint 0:87ab172a74b4 75 }
maxint 0:87ab172a74b4 76
maxint 0:87ab172a74b4 77 void Game::tick() {
maxint 0:87ab172a74b4 78 this->checkButtons();
maxint 0:87ab172a74b4 79
maxint 0:87ab172a74b4 80 if (this->mode) {
maxint 0:87ab172a74b4 81 /*
maxint 0:87ab172a74b4 82 if(this->tWait.read_ms()>100)
maxint 0:87ab172a74b4 83 {
maxint 0:87ab172a74b4 84 this->updatePaddle();
maxint 0:87ab172a74b4 85 this->tWait.reset();
maxint 0:87ab172a74b4 86 }
maxint 0:87ab172a74b4 87 */
maxint 0:87ab172a74b4 88 this->updatePaddle();
maxint 0:87ab172a74b4 89 this->ball.vSpeed.add(Vector(0, 0.1)); // add some gravity
maxint 0:87ab172a74b4 90 this->ball.update(); // update the ball position
maxint 0:87ab172a74b4 91
maxint 0:87ab172a74b4 92 this->checkCollision();
maxint 0:87ab172a74b4 93 this->paddle.redraw();
maxint 0:87ab172a74b4 94 this->ball.redraw();
maxint 0:87ab172a74b4 95
maxint 0:87ab172a74b4 96 this->checkPwm();
maxint 1:c1ee4c699517 97 //this->checkScore();
maxint 0:87ab172a74b4 98 this->checkLives();
maxint 0:87ab172a74b4 99
maxint 0:87ab172a74b4 100 wait_ms(25);
maxint 0:87ab172a74b4 101 }
maxint 1:c1ee4c699517 102 else {
maxint 1:c1ee4c699517 103 this->accel.updateGraph();
maxint 0:87ab172a74b4 104 wait_ms(100);
maxint 0:87ab172a74b4 105 }
maxint 0:87ab172a74b4 106 }
maxint 0:87ab172a74b4 107
maxint 0:87ab172a74b4 108 int Game::checkTilt()
maxint 0:87ab172a74b4 109 { // move the paddle by tilting the board left or righr
maxint 0:87ab172a74b4 110 double x, y, z;
maxint 0:87ab172a74b4 111 //int nStart=this->tWait.read_ms();
maxint 0:87ab172a74b4 112 this->accel.getXYZ(x, y, z);
maxint 0:87ab172a74b4 113
maxint 0:87ab172a74b4 114 //printDouble((double)this->tWait.read_ms()-nStart, 10, 10);
maxint 0:87ab172a74b4 115 /*
maxint 0:87ab172a74b4 116 printDouble(x, 0, 0);
maxint 0:87ab172a74b4 117 char buf[256];
maxint 0:87ab172a74b4 118 sprintf(buf,"tilt:%0.1f", x);
maxint 0:87ab172a74b4 119 this->drawString(buf, DisplayN18::HEIGHT / 2 - DisplayN18::CHAR_HEIGHT / 2 + 4*DisplayN18::CHAR_HEIGHT );
maxint 0:87ab172a74b4 120 */
maxint 0:87ab172a74b4 121
maxint 0:87ab172a74b4 122 if(x<-0.1) return(-1);
maxint 0:87ab172a74b4 123 else if(x>0.1) return(1);
maxint 0:87ab172a74b4 124 else return(0);
maxint 0:87ab172a74b4 125 }
maxint 0:87ab172a74b4 126
maxint 0:87ab172a74b4 127 void Game::checkButtons()
maxint 0:87ab172a74b4 128 {
maxint 0:87ab172a74b4 129 if (!this->square.read())
maxint 0:87ab172a74b4 130 {
maxint 0:87ab172a74b4 131 this->mode = !this->mode;
maxint 0:87ab172a74b4 132
maxint 0:87ab172a74b4 133 //this->disp.clear();
maxint 0:87ab172a74b4 134 this->disp.clearScreen();
maxint 0:87ab172a74b4 135
maxint 0:87ab172a74b4 136 if (!this->mode)
maxint 0:87ab172a74b4 137 {
maxint 1:c1ee4c699517 138 this->accel.resetGraph();
maxint 0:87ab172a74b4 139 }
maxint 0:87ab172a74b4 140
maxint 0:87ab172a74b4 141 this->led1.write(this->mode);
maxint 0:87ab172a74b4 142 this->led2.write(!this->mode);
maxint 0:87ab172a74b4 143 }
maxint 0:87ab172a74b4 144 else
maxint 0:87ab172a74b4 145 {
maxint 0:87ab172a74b4 146 bool isUp = !this->up.read();
maxint 0:87ab172a74b4 147 bool isDown = !this->down.read();
maxint 0:87ab172a74b4 148
maxint 0:87ab172a74b4 149 if (isUp && isDown) goto end;
maxint 0:87ab172a74b4 150 if (!isUp && !isDown) goto end;
maxint 0:87ab172a74b4 151
maxint 0:87ab172a74b4 152 if (isUp && this->lastUp) goto end;
maxint 0:87ab172a74b4 153 if (isDown && this->lastDown) goto end;
maxint 0:87ab172a74b4 154
maxint 0:87ab172a74b4 155 if (isUp)
maxint 0:87ab172a74b4 156 {
maxint 0:87ab172a74b4 157 this->ball.changeSpeed(true);
maxint 0:87ab172a74b4 158 }
maxint 0:87ab172a74b4 159 else if (isDown)
maxint 0:87ab172a74b4 160 {
maxint 0:87ab172a74b4 161 this->ball.changeSpeed(false);
maxint 0:87ab172a74b4 162 }
maxint 0:87ab172a74b4 163
maxint 0:87ab172a74b4 164 end:
maxint 0:87ab172a74b4 165 this->lastUp = isUp;
maxint 0:87ab172a74b4 166 this->lastDown = isDown;
maxint 0:87ab172a74b4 167 }
maxint 0:87ab172a74b4 168 }
maxint 0:87ab172a74b4 169
maxint 1:c1ee4c699517 170 void Game::drawString(const char* str, int y)
maxint 1:c1ee4c699517 171 {
maxint 1:c1ee4c699517 172 uint8_t width;
maxint 1:c1ee4c699517 173 uint8_t height;
maxint 1:c1ee4c699517 174
maxint 1:c1ee4c699517 175 this->disp.measureString(font_oem, str, width, height);
maxint 1:c1ee4c699517 176 this->disp.drawString(font_oem, WIDTH / 2 - width / 2, y, str);
maxint 1:c1ee4c699517 177
maxint 0:87ab172a74b4 178 }
maxint 0:87ab172a74b4 179
maxint 0:87ab172a74b4 180 void Game::showSplashScreen() {
maxint 0:87ab172a74b4 181 this->drawString(Game::SPLASH_1, HEIGHT / 2 - CHAR_HEIGHT / 2);
maxint 0:87ab172a74b4 182 this->drawString(Game::SPLASH_2, HEIGHT / 2 + CHAR_HEIGHT / 2);
maxint 0:87ab172a74b4 183 this->drawString(Game::SPLASH_3, HEIGHT / 2 + CHAR_HEIGHT / 2 + 2*(8));
maxint 0:87ab172a74b4 184
maxint 0:87ab172a74b4 185 while (this->circle.read())
maxint 0:87ab172a74b4 186 {
maxint 0:87ab172a74b4 187 int i=this->checkTilt();
maxint 0:87ab172a74b4 188 char buf[256];
maxint 1:c1ee4c699517 189 sprintf(buf," tilt:%d ", i);
maxint 0:87ab172a74b4 190 this->drawString(buf, HEIGHT / 2 - CHAR_HEIGHT / 2 + (4*CHAR_HEIGHT) );
maxint 0:87ab172a74b4 191
maxint 0:87ab172a74b4 192 wait_ms(1);
maxint 0:87ab172a74b4 193 }
maxint 0:87ab172a74b4 194
maxint 0:87ab172a74b4 195 this->disp.clearScreen();
maxint 0:87ab172a74b4 196 }
maxint 0:87ab172a74b4 197
maxint 0:87ab172a74b4 198
maxint 0:87ab172a74b4 199 void Game::updatePaddle() {
maxint 0:87ab172a74b4 200 if (!this->left.read()) // note: read is LOW (0) when button pressed
maxint 0:87ab172a74b4 201 this->paddle.move(Vector(-1 * Game::PADDLE_SPEED, 0));
maxint 0:87ab172a74b4 202 else if (!this->right.read())
maxint 0:87ab172a74b4 203 this->paddle.move(Vector(Game::PADDLE_SPEED, 0));
maxint 0:87ab172a74b4 204 else
maxint 0:87ab172a74b4 205 {
maxint 0:87ab172a74b4 206 int i=this->checkTilt(); // don't call too often as this I2C is slow and will delay the game
maxint 0:87ab172a74b4 207 if(i>0)
maxint 0:87ab172a74b4 208 this->paddle.move(Vector(Game::PADDLE_SPEED, 0));
maxint 0:87ab172a74b4 209 else if(i<0)
maxint 0:87ab172a74b4 210 this->paddle.move(Vector(-1 * Game::PADDLE_SPEED, 0));
maxint 0:87ab172a74b4 211 }
maxint 0:87ab172a74b4 212 }
maxint 0:87ab172a74b4 213
maxint 0:87ab172a74b4 214 void Game::checkCollision()
maxint 0:87ab172a74b4 215 {
maxint 0:87ab172a74b4 216 Rectangle rTop=Rectangle(0, -10, WIDTH, 0); // Rectangle(0, 0, WIDTH, 1); // top wall
maxint 0:87ab172a74b4 217 Rectangle rBottom=Rectangle(0, HEIGHT, WIDTH, HEIGHT+10); // Rectangle(0, HEIGHT, WIDTH, HEIGHT); // bottom gap
maxint 0:87ab172a74b4 218 Rectangle rLeft=Rectangle(-10, 0, 0, HEIGHT); // Rectangle(0, 0, 0, HEIGHT); // left wall
maxint 0:87ab172a74b4 219 Rectangle rRight=Rectangle(WIDTH, 0, WIDTH+10, HEIGHT); // Rectangle(WIDTH, 0, WIDTH, HEIGHT); // right wall
maxint 0:87ab172a74b4 220 Rectangle rPaddle=Rectangle(paddle.pos.getX(), paddle.pos.getY(), paddle.pos.getX() + Game::PADDLE_WIDTH, HEIGHT+10); // Rectangle(this->paddleX, HEIGHT - Game::PADDLE_HEIGHT, this->paddleX + Game::PADDLE_WIDTH, HEIGHT); // paddle
maxint 0:87ab172a74b4 221 Rectangle rPaddleLeft=Rectangle(paddle.pos.getX(), paddle.pos.getY(), paddle.pos.getX() + Game::PADDLE_WIDTH/3, HEIGHT+10); // paddle left part
maxint 0:87ab172a74b4 222 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
maxint 1:c1ee4c699517 223 Rectangle rScreen=Rectangle(0,0, WIDTH, HEIGHT); // screen boundary
maxint 0:87ab172a74b4 224
maxint 1:c1ee4c699517 225 /*
maxint 0:87ab172a74b4 226 if (this->paddle.pos.getX() < 0)
maxint 0:87ab172a74b4 227 this->paddle.pos.setX(0);
maxint 0:87ab172a74b4 228 if (this->paddle.pos.getX() + Game::PADDLE_WIDTH > WIDTH)
maxint 0:87ab172a74b4 229 this->paddle.pos.setX(WIDTH - Game::PADDLE_WIDTH);
maxint 1:c1ee4c699517 230 */
maxint 1:c1ee4c699517 231 this->paddle.checkBoundary(rScreen);
maxint 1:c1ee4c699517 232
maxint 0:87ab172a74b4 233 if(ball.collides(rTop) && this->ball.vSpeed.isUp()) // top wall
maxint 0:87ab172a74b4 234 {
maxint 0:87ab172a74b4 235 this->ball.Bounce(Vector(1,-1)); // bounce vertical
maxint 0:87ab172a74b4 236 this->pwmTicksLeft = Game::BOUNCE1_SOUND_TICKS;
maxint 0:87ab172a74b4 237 }
maxint 0:87ab172a74b4 238 if(ball.collides(rRight) && this->ball.vSpeed.isRight()) // right wall
maxint 0:87ab172a74b4 239 {
maxint 0:87ab172a74b4 240 this->ball.Bounce(Vector(-1,1)); // bounce horizontal
maxint 0:87ab172a74b4 241 this->pwmTicksLeft = Game::BOUNCE1_SOUND_TICKS;
maxint 0:87ab172a74b4 242 }
maxint 0:87ab172a74b4 243 if(ball.collides(rLeft) && this->ball.vSpeed.isLeft()) // left wall
maxint 0:87ab172a74b4 244 {
maxint 0:87ab172a74b4 245 this->ball.Bounce(Vector(-1,1)); // bounce horizontal
maxint 0:87ab172a74b4 246 this->pwmTicksLeft = Game::BOUNCE1_SOUND_TICKS;
maxint 0:87ab172a74b4 247 }
maxint 0:87ab172a74b4 248 if(ball.collides(rPaddle) && this->ball.vSpeed.isDown()) // paddle
maxint 0:87ab172a74b4 249 {
maxint 1:c1ee4c699517 250 if(ball.collides(rPaddleLeft)) ball.vSpeed.add(Vector(-1,0)); // left side of paddle has bias to the left
maxint 1:c1ee4c699517 251 if(ball.collides(rPaddleRight)) ball.vSpeed.add(Vector(1,0)); // right side of paddle has bias to the right
maxint 1:c1ee4c699517 252
maxint 0:87ab172a74b4 253 //this->ball.Bounce(Vector(1,-1)); // bounce vertical at same speed
maxint 1:c1ee4c699517 254 ball.Bounce(Vector(1,-1.1)); // bounce from paddle at higher speed
maxint 0:87ab172a74b4 255
maxint 1:c1ee4c699517 256 this->pwmTicksLeft = Game::BOUNCE2_SOUND_TICKS;
maxint 1:c1ee4c699517 257 this->nScore++;
maxint 1:c1ee4c699517 258 this->printf(100, 0, "Score: %d ", this->nScore);
maxint 0:87ab172a74b4 259 }
maxint 0:87ab172a74b4 260 if(ball.collides(rBottom) && this->ball.vSpeed.isDown()) // bottom gap
maxint 0:87ab172a74b4 261 {
maxint 1:c1ee4c699517 262 ball.clearPrev(); // clear the ball from its previous position
maxint 1:c1ee4c699517 263 this->nLives--;
maxint 1:c1ee4c699517 264 this->initializeBall(); // start a new ball
maxint 0:87ab172a74b4 265 }
maxint 0:87ab172a74b4 266 }
maxint 0:87ab172a74b4 267
maxint 0:87ab172a74b4 268 void Game::checkPwm() {
maxint 0:87ab172a74b4 269 if (this->pwmTicksLeft == 0) {
maxint 0:87ab172a74b4 270 this->pwm.write(0.0);
maxint 0:87ab172a74b4 271 }
maxint 0:87ab172a74b4 272 else {
maxint 0:87ab172a74b4 273 this->pwmTicksLeft--;
maxint 0:87ab172a74b4 274 this->pwm.write(0.5);
maxint 0:87ab172a74b4 275 }
maxint 0:87ab172a74b4 276 }
maxint 0:87ab172a74b4 277
maxint 1:c1ee4c699517 278 void Game::printf(int x, int y, const char *szFormat, ...)
maxint 0:87ab172a74b4 279 {
maxint 0:87ab172a74b4 280 char szBuffer[256];
maxint 0:87ab172a74b4 281 va_list args;
maxint 0:87ab172a74b4 282
maxint 0:87ab172a74b4 283 va_start(args, szFormat);
maxint 0:87ab172a74b4 284 vsprintf(szBuffer, szFormat, args);
maxint 0:87ab172a74b4 285 va_end(args);
maxint 1:c1ee4c699517 286 this->disp.drawString(font_oem, x, y, szBuffer);
maxint 0:87ab172a74b4 287 }
maxint 0:87ab172a74b4 288
maxint 0:87ab172a74b4 289
maxint 0:87ab172a74b4 290 void Game::checkLives() {
maxint 1:c1ee4c699517 291 if (this->nLives == 0) {
maxint 0:87ab172a74b4 292 this->disp.clearScreen();
maxint 0:87ab172a74b4 293
maxint 0:87ab172a74b4 294 this->drawString(Game::LOSE_1, HEIGHT / 2 - CHAR_HEIGHT);
maxint 0:87ab172a74b4 295 this->drawString(Game::LOSE_2, HEIGHT / 2);
maxint 0:87ab172a74b4 296
maxint 0:87ab172a74b4 297 while (this->circle.read())
maxint 0:87ab172a74b4 298 wait_ms(1);
maxint 0:87ab172a74b4 299
maxint 0:87ab172a74b4 300 this->initialize();
maxint 0:87ab172a74b4 301 }
maxint 0:87ab172a74b4 302 else {
maxint 1:c1ee4c699517 303 this->printf(0, 0, "%d", this->nLives);
maxint 0:87ab172a74b4 304 }
maxint 0:87ab172a74b4 305 }