Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: LCD_ST7735 MusicEngine RETRO_BallsAndThings mbed
Game.cpp
- Committer:
- maxint
- Date:
- 2015-03-01
- Revision:
- 10:1d75861242f7
- Parent:
- 9:7bd5def2115d
- Child:
- 11:ef3cbc443f27
File content as of revision 10:1d75861242f7:
#include "Game.h"
const char* Game::LOSE_1 = "Game over.";
const char* Game::LOSE_2 = "Press ship to restart.";
const char* Game::SPLASH_1 = "-*- Ball and holes -*-";
const char* Game::SPLASH_2 = "Press ship to start.";
const char* Game::SPLASH_3 = "Tilt console to steer ball.";
#define WHITE Color565::White
#define BLACK Color565::Black
#define BLUE Color565::Blue
#define RED Color565::Red
#define YELLOW Color565::Yellow
#define CHAR_WIDTH 8
#define CHAR_HEIGHT 8
#define HEIGHT this->disp.getHeight()
#define WIDTH this->disp.getWidth()
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),
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)
{
this->disp.setOrientation(LCD_ST7735::Rotate270, false);
this->disp.setForegroundColor(WHITE);
this->disp.setBackgroundColor(BLACK);
this->disp.clearScreen();
srand(this->ain.read_u16());
this->lastUp = false;
this->lastDown = false;
this->mode = true; // mode: true=game, false=graph
this->nGameTickDelay=25; // game tickdelay can be adjusted using up/down
nTopWall=8;
for(int i=0; i<NUM_WALLS; i++)
aWalls[i]=Wall(&(this->disp));
for(int i=0; i<NUM_HOLES; i++)
aHoles[i]=Hole(&(this->disp));
this->snd.reset();
}
void Game::printDouble(double value, int x, int y)
{
char buffer[10];
int len = sprintf(buffer, "%.1f ", value);
this->disp.drawString(font_oem, x, y, buffer);
}
void Game::initialize()
{
// disp.clearScreen();
// snd.reset();
nBalls = 4;
nScore = 0;
tWait.start(); // start the timer
//char sWalls[]="W010010A0";
for(int i=0; i<NUM_WALLS; i++)
{
aWalls[i]=Wall(&(disp));
}
initLevel();
// setNoBall(); // reset all balls
newBall(); // start first ball
snd.play("T240 L16 O5 D E F");
}
void Game::initLevel()
{
char szLevel0[]="01:1 0262 2787-2227 8187 ";
char szLevel1[]="01:1 0292 0343 63:3 2484 0545 65:5 1797-5257 1617 2526 3637 7677 8586 9697 ";
char szLevel2[]="01:1 0232 82:2 1343 6393 0484 1696-4143 6163 5355 9396 3435 7475 1617 4547 5758 6567 ";
char szLevel3[]="01:1 0232 82:2 3444 1545 6696-4147 6166 2223 8284 1315 5358 9397 ";
disp.clearScreen();
snd.reset();
// reset current walls and holes
for(int i=0; i<NUM_WALLS; i++)
aWalls[i].fActive=false;
for(int i=0; i<NUM_HOLES; i++)
aHoles[i].fActive=false;
// add walls/holes depending on level
switch(nScore)
{
case 0:
addWalls(szLevel0);
addHoles("4411 6412 ");
break;
case 1:
addWalls(szLevel1);
addHoles("9111 0312 9412 4612 5612 ");
break;
case 2:
addWalls(szLevel1);
addHoles("5711 0312 9412 4612 5612 ");
break;
case 3:
addWalls(szLevel1);
addHoles("0211 0312 9412 4612 5612 ");
break;
case 4:
addWalls(szLevel2);
addHoles("4411 6112 5112 1512 8512 2712 7712 8612 ");
break;
case 5:
addWalls(szLevel2);
addHoles("5611 6112 5112 1512 8512 2712 7712 8612 ");
break;
case 6:
addWalls(szLevel2);
addHoles("9111 6112 5112 1512 8512 2712 7712 8612 ");
break;
case 7:
addWalls(szLevel3);
addHoles("3411 4112 5112 6112 2212 3312 6312 1412 7412 8512 0612 2612 6612 8612 5712 7712 ");
break;
case 8:
addWalls(szLevel3);
addHoles("5211 4112 5112 6112 2212 3312 6312 1412 7412 8512 0612 2612 6612 8612 5712 7712 ");
break;
case 9:
addWalls(szLevel3);
addHoles("9711 4112 5112 6112 2212 3312 6312 1412 7412 8512 0612 2612 6612 8612 5712 7712 ");
break;
case 10:
addWalls(szLevel3);
addHoles("9111 4112 5112 6112 2212 3312 6312 1412 7412 8512 0612 2612 6612 8612 5712 7712 ");
break;
case 11:
default:
addWalls(szLevel3);
addHoles("9111 4112 5112 6112 2212 3312 6312 1412 7452 8512 0612 2652 6612 8612 5712 7712 ");
break;
}
drawWalls();
drawHoles();
checkNumBalls();
}
Point Game::getGridPos(char *sPos)
{ // get item pos based on a grid definition of 16x16 pixel squares, layed out in a 10x8 grid
// sPos is a 2 character string containing the coordinates in decimal notation: xy
int x=(sPos[0]-'0')*16+8;
int y=(sPos[1]-'0')*16+8;
return(Point(x,y));
}
void Game::addWall(char *sWall)
{ // add a wall based on a grid definition of 16x16 pixel squares, layed out in a 10x8 grid
// sWall is a 4 character string containing the edge coordinates in decimal notation: xyXY
// grid axis range from x: '0'-'9', ':'=10 - y: '0'-'8'
for(int i=0; i<NUM_WALLS; i++)
{
if(!aWalls[i].fActive)
{
int x1=sWall[0]-'0';
int y1=sWall[1]-'0';
int x2=sWall[2]-'0';
int y2=sWall[3]-'0';
aWalls[i].setRect(Rectangle(x1*16,y1*16,x2*16+1,y2*16+1));
aWalls[i].fActive=true;
//printf(0, 0, "W: %d,%d - %d,%d", x1, y1, x2, y2);
break;
//aWalls[i].draw();
//snd.beepShort();
}
}
}
void Game::addWalls(char *sWalls)
{
char sWall[]="0000";
for(int i=0; i<strlen(sWalls); i+=5)
{
strncpy(sWall, sWalls+i, 4);
addWall(sWall);
}
}
void Game::drawWalls()
{
for(int i=0; i<NUM_WALLS; i++)
if(aWalls[i].fActive)
{
aWalls[i].draw();
//snd.beepShort();
}
}
void Game::addHole(char *sHole)
{ // add a wall based on a grid definition of 16x16 pixel squares, layed out in a 10x8 grid
// sWall is a 4 character string containing the edge coordinates in decimal notation: xyXY
// grid axis range from x: '0'-'9', ':'=10 - y: '0'-'8'
for(int i=0; i<NUM_WALLS; i++)
{
if(!aHoles[i].fActive)
{
int x=(sHole[0]-'0')*16+8;
int y=(sHole[1]-'0')*16+8;
int r=sHole[2]-'0';
int c=sHole[3]-'0';
int rnd1=0, rnd2=0;
if(r==2) rnd1=rand() % r - r/2;
if(r==2) rnd2=rand() % r - r/2;
aHoles[i].setCirc(Circle(x+rnd1,y+rnd2, Game::HOLE_RADIUS));
if(c==1) aHoles[i].setColor(Color565::Green);
if(c==2) aHoles[i].setColor(Color565::Gray);
if(c==3) aHoles[i].setColor(Color565::Red);
aHoles[i].fActive=true;
//printf(0, 0, "H: %d,%d - %d,%d", x1, y1, r, c);
break;
//aWalls[i].draw();
//snd.beepShort();
}
}
}
void Game::addHoles(char *sHoles)
{
char sHole[]="0000";
for(int i=0; i<strlen(sHoles); i+=5)
{
strncpy(sHole, sHoles+i, 4);
addHole(sHole);
}
}
void Game::drawHoles()
{
for(int i=0; i<NUM_HOLES; i++)
if(aHoles[i].fActive)
{
aHoles[i].draw();
//snd.beepShort();
}
}
void Game::setNoBall()
{ // make sure no balls are active
/* for(int i=0; i<NUM_BALLS; i++)
this->aBalls[i].fActive=false;
*/
}
void Game::newBall()
{ // add a ball to the game
Point ptBall=getGridPos("01");
ball.initialize(ptBall.getX(), ptBall.getY(), Game::BALL_RADIUS, Color565::White);
//float ftRandX=rand() % 2 ? 1 : -1;
//float ftRandY=rand() % 2 ? 1 : -1;
//this->aBalls[i].setSpeed(ftRandX, ftRandY);
// float ftRandX=((rand() % 20) - 10)/5.0; // left/right at random speed
// float ftRandY=((rand() % 10) - 10)/5.0; // up at random speed
// ball.vSpeed.set(ftRandX, ftRandY);
//this->aBalls[i].fActive=true;
}
void Game::updateBall()
{
ball.update(); // update the ball position
// increase speed based on gravity
checkTilt();
if(ball.vSpeed.getSize()<2.0)
ball.vSpeed.add(this->vGravity); // add some gravity
// decrease speed based on friction
Vector vDecel=ball.vSpeed.getNormalized();
vDecel.multiply(vFriction);
ball.vSpeed.add(vDecel);
}
void Game::redrawBall()
{
ball.redraw(); // update the ball position
}
int Game::countBall()
{
int nResult=0;
/*
for(int i=0; i<NUM_BALLS; i++)
{
if(this->aBalls[i].fActive)
nResult++;
}
*/
return(nResult);
}
void Game::tick()
{
checkButtons();
if(mode)
{
/*
if(this->tWait.read_ms()>100)
{
this->tWait.reset();
}
*/
updateBall(); // update the ball positions
checkBallCollision();
redrawBall();
if(this->tWait.read_ms()>100)
{ // redraw walls and holes every tenth second
drawWalls();
drawHoles();
checkNumBalls();
this->tWait.reset();
}
// this->snd.checkPwm();
//this->checkScore();
//this->checkBall();
wait_ms(nGameTickDelay); // can be adjusted using up/down
}
else
{
accel.updateGraph();
wait_ms(100);
}
}
void Game::checkTilt()
{ // move the gravity direction and weight by tilting the board
double x, y, z;
//int nStart=this->tWait.read_ms();
this->accel.getXYZ(x, y, z);
vGravity=Vector(x,y);
while(vGravity.getSize()>0.5)
vGravity.multiply(Vector(0.9, 0.9));
// vGravity=vGravity.getNormalized();
//printDouble((double)this->tWait.read_ms()-nStart, 10, 10);
/*
printDouble(x, 0, 0);
char buf[256];
sprintf(buf,"tilt:%0.1f", x);
this->drawString(buf, DisplayN18::HEIGHT / 2 - DisplayN18::CHAR_HEIGHT / 2 + 4*DisplayN18::CHAR_HEIGHT );
*/
//return(Vector(0,0));
}
void Game::checkButtons()
{
if(!this->square.read()) // note: button.read() is false (LOW/0) when pressed
{
wait_ms(250); // el-cheapo deboounce
this->mode = !this->mode;
//this->disp.clear();
this->disp.clearScreen();
if (!this->mode)
{
this->accel.resetGraph();
}
this->led1.write(this->mode);
this->led2.write(!this->mode);
}
else if(!this->circle.read() && this->mode) // note: button.read() is false (LOW/0) when pressed
{
bool fMute=this->snd.getMute();
fMute=!fMute;
this->snd.setMute(fMute);
this->led2.write(fMute);
wait_ms(250); // el-cheapo deboounce
}
else
{
bool isUp = !this->up.read();
bool isDown = !this->down.read();
if(!this->left.read())
{ // cheat: restart level
snd.play("T240 L128 O6 CEF");
ball.fActive=false;
initLevel();
wait_ms(500);
newBall(); // start a new ball
return;
}
else if(!this->right.read())
{ // cheat: next level
snd.play("T240 L128 O5 FEC");
nScore++;
ball.fActive=false;
initLevel();
wait_ms(500);
newBall(); // start a new ball
return;
}
if (isUp && isDown) goto end;
if (!isUp && !isDown) goto end;
if (isUp && this->lastUp) goto end;
if (isDown && this->lastDown) goto end;
if (isUp)
{
if(this->nGameTickDelay<1000) this->nGameTickDelay=(float)this->nGameTickDelay*1.20;
this->printf(100, 0, "Speed: %d ", this->nGameTickDelay);
}
else if (isDown)
{
if(this->nGameTickDelay>5) this->nGameTickDelay=(float)this->nGameTickDelay/1.20;
this->printf(100, 0, "Speed: %d ", this->nGameTickDelay);
}
end:
this->lastUp = isUp;
this->lastDown = isDown;
}
}
void Game::drawString(const char* str, int y)
{
uint8_t width;
uint8_t height;
this->disp.measureString(font_oem, str, width, height);
this->disp.drawString(font_oem, WIDTH / 2 - width / 2, y, str);
}
void Game::showSplashScreen()
{
this->drawString(Game::SPLASH_1, HEIGHT / 2 - CHAR_HEIGHT / 2);
this->drawString(Game::SPLASH_2, HEIGHT / 2 + CHAR_HEIGHT / 2);
this->drawString(Game::SPLASH_3, HEIGHT / 2 + CHAR_HEIGHT / 2 + 2*CHAR_HEIGHT);
while (this->circle.read())
{
//checkTilt();
//printf(00, 120, "tilt: %.2f, %.2f ", vGravity.x, vGravity.y);
wait_ms(1);
}
wait_ms(250); // el-cheapo deboounce
//this->disp.clearScreen();
this->initialize(); // start a new game
}
void Game::checkBallCollision()
{
Rectangle rTop=Rectangle(0, -10, WIDTH, this->nTopWall); // Rectangle(0, 0, WIDTH, 1); // top wall
Rectangle rBottom=Rectangle(0, HEIGHT, WIDTH, HEIGHT+10); // Rectangle(0, HEIGHT, WIDTH, HEIGHT); // bottom gap
Rectangle rLeft=Rectangle(-10, 0, 0, HEIGHT); // Rectangle(0, 0, 0, HEIGHT); // left wall
Rectangle rRight=Rectangle(WIDTH, 0, WIDTH+10, HEIGHT); // Rectangle(WIDTH, 0, WIDTH, HEIGHT); // right wall
if(ball.collides(rTop) && ball.vSpeed.isUp()) // top wall
{
ball.vSpeed.y=0;
//this->snd.beepShort();
}
if(ball.collides(rRight) && ball.vSpeed.isRight()) // right wall
{
ball.vSpeed.x=0;
//this->snd.beepShort();
}
if(ball.collides(rLeft) && ball.vSpeed.isLeft()) // left wall
{
ball.vSpeed.x=0;
//this->snd.beepShort();
}
if(ball.collides(rBottom) && ball.vSpeed.isDown()) // bottom wall
{
ball.vSpeed.y=0;
//this->snd.beepShort();
}
for(int i=0; i<NUM_WALLS; i++) // check maze walls
{
if(aWalls[i].fActive)
{
Rectangle rWall=aWalls[i].getRect();
if(ball.collides(rWall))
{
//printf(30, 0, "b: %.2f", ball.vSpeed.getSize());
if(rWall.isHorizontal())
{
if(fabs(ball.vSpeed.y)>0.8)
snd.play("T240 L128 O4 A");
ball.Bounce(Vector(1,-0.1));
}
else
{
if(fabs(ball.vSpeed.x)>0.8)
snd.play("T240 L128 O4 A");
ball.Bounce(Vector(-0.1,1));
}
//ball.vSpeed.multiply(Vector(0,0));
aWalls[i].draw();
}
//printf(0, 100, "W: %d,%d - %d,%d", rWall.getX1(), rWall.getY1(), rWall.getX2(), rWall.getY2());
}
}
for(int i=0; i<NUM_HOLES; i++) // check holes
{
if(aHoles[i].fActive)
{
Circle cHole=aHoles[i].getCirc();
if(ball.collides(cHole))
{
//snd.play("T240 L128 O5 C");
if(aHoles[i].hasGoneIn(ball.getBoundingCircle()))
{
//snd.play("T240 L128 O5 C");
ball.fActive=false;
if(i==0)
{ // TODO: for now first hole is target hole
nScore++;
snd.play("T240 L16 O5 CDEFG");
}
else
{
nBalls--;
snd.beepLow();
}
initLevel();
wait_ms(500);
newBall(); // start a new ball
}
}
}
}
}
void Game::printf(int x, int y, const char *szFormat, ...)
{
char szBuffer[256];
va_list args;
va_start(args, szFormat);
vsprintf(szBuffer, szFormat, args);
va_end(args);
this->disp.drawString(font_oem, x, y, szBuffer);
}
void Game::checkNumBalls()
{
if (this->nBalls == 0)
{ // game over
char buf[256];
this->disp.clearScreen();
this->drawString(Game::LOSE_1, HEIGHT / 2 - CHAR_HEIGHT);
this->drawString(Game::LOSE_2, HEIGHT / 2);
sprintf(buf,"Your score: %d ", this->nScore);
this->drawString(buf, HEIGHT / 2 + CHAR_HEIGHT / 2 + CHAR_HEIGHT );
this->snd.play("T120 O3 L4 R4 F C F2 C");
while (this->circle.read())
wait_ms(1);
wait_ms(250); // el-cheapo deboounce
this->initialize();
}
else
{
printf(0, 0, "Balls: %d ", this->nBalls);
printf(100, 0, "Score: %d ", this->nScore);
}
}