Maxint R&D / Mbed 2 deprecated RETRO_BallAndHoles

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);
    }
}