Classic 15 puzzle

Dependencies:   PokittoLib

main.c

Committer:
HomineLudens
Date:
2018-05-15
Revision:
2:6d5ef2d31e52
Parent:
0:b0c9d3b227f6

File content as of revision 2:6d5ef2d31e52:


#include "Pokitto.h"
#include <cstddef>
#include "gfx.h"
#include "math.h"

#define GRID_COLUMNS 4
#define GRID_ROWS 4
#define TILE_SIZE 40
#define SHUFFLE_MOVES 20
#define MAX_LEVEL 7

Pokitto::Core g;

uint8_t ShuffleCount=0;
uint16_t PlayerMoves=0;
int8_t LevelActive=0;
int8_t TextColor=7;
bool HighlightNumbers=false;
uint16_t FrameCount=0;

char *AuthorActive;

uint16_t Score=0;

typedef enum
{
    Intro,
    LoadAndShuffle,
    Game,
    Menu,
    Victory,
    Outro,
    GameOver
} SSM;
SSM ssm=SSM::Intro;

struct Tile
{
    int8_t val=0;
    int8_t c=0;
    float xa=0;
    float ya=0;
    float speed=0;
    float dir=0;
};
Tile tiles[GRID_ROWS][GRID_COLUMNS];

const char * Menus[]= {"Back","Title screen","Select level","Shuffle"};
const uint8_t MenusSize = sizeof(Menus) / sizeof(Menus[0]);
typedef enum
{
    Back,
    TitleScreen,
    SelectLevel,
    Shuffle
} MenuItems;
MenuItems menuItem=MenuItems::Back;

const char * Info =
{
    "Pok15 is the remake of the classic 15-puzzle.\r\n"
    "Purpose of the game:slide all tiles in order.\r\n"
    "Press B to highlight.\r\n"
    "\r\n"
    "Have fun."
};


void InitTiles()
{
    int8_t i=0;
    for (int8_t r=0; r<GRID_ROWS; r++)
    {
        for (int8_t c=0; c<GRID_COLUMNS; c++)
        {
            tiles[r][c].val=i;
            tiles[r][c].c=i;
            tiles[r][c].xa=0;
            tiles[r][c].ya=0;
            tiles[r][c].speed=random(10,20);
            tiles[r][c].dir=random(0,2*PI);
            i++;
        }
    }
}


//This kind of magic thanks to @Pherap the C++ Guru
template<std::size_t numberOfFrames, std::size_t frameSize>
void DrawTiles(const uint16_t * palette, const uint8_t (&images)[numberOfFrames][frameSize],bool drawBorder,bool drawNumbers,bool highlightNumbers, bool drawFull )
{
    int x=0;
    int y=0;

    //Load/reload palette
    g.display.load565Palette(palette);

    //Center on screen
    int xo = (g.display.width - (TILE_SIZE*GRID_COLUMNS))/2;
    int yo = (g.display.height - (TILE_SIZE*GRID_ROWS))-4;

    for (int8_t r=0; r<GRID_ROWS; r++)
    {
        for (int8_t c=0; c<GRID_COLUMNS; c++)
        {
            if(drawFull || tiles[r][c].val!=15 )
            {
                x=c*TILE_SIZE+tiles[r][c].xa;
                y=r*TILE_SIZE+tiles[r][c].ya;

                //Draw flat tile
                g.display.setColor(tiles[r][c].c);
                //g.display.fillRect(xo+x,yo+y,TILE_SIZE,TILE_SIZE);

                //Draw image
                g.display.drawBitmap(xo+x,yo+y,images[tiles[r][c].val]);

                if(drawBorder)
                {
                    //Draw border
                    g.display.setColor(7);
                    g.display.drawRect(xo+x,yo+y,TILE_SIZE,TILE_SIZE);
                }

                if(drawNumbers)
                {
                    //Draw number of the tile1
                    if(highlightNumbers)
                        g.display.setInvisibleColor(255);


                    g.display.setColor(TextColor,0);

                    g.display.print(xo+x+1/2+ (g.display.fontWidth/2),
                                    yo+y+1/2+(g.display.fontHeight/2),
                                    tiles[r][c].val+1);//val + 1
                    g.display.setColor(0,0);
                    g.display.setInvisibleColor(0);
                }
            }
        }
    }
}

template <typename T> T
ClampVal(const T& value, const T& low, const T& high)
{
    return value < low ? low : (value > high ? high : value);
}

bool SwapTiles(int8_t fx,int8_t fy,int8_t tx,int8_t ty)
{
    //Verify if allowed
    if(tx>=0 && ty>=0 && tx<GRID_COLUMNS && ty<GRID_ROWS)
    {
        Tile f=tiles[fx][fy];
        Tile t=tiles[tx][ty];

        tiles[tx][ty]=f;
        tiles[fx][fy]=t;
        return true;
    }
    return false;
}

bool MoveTiles(int8_t dx,int8_t dy)
{
    //Find movable tile
    int8_t re=0;
    int8_t ce=0;
    for (int8_t r=0; r<GRID_ROWS; r++)
    {
        for (int8_t c=0; c<GRID_COLUMNS; c++)
        {
            //found
            if(tiles[r][c].val==15)
            {
                re=r;
                ce=c;
                break;
            }
        }
    }
    return SwapTiles(re,ce,re+dx,ce+dy);
}

void ShuffleTiles()
{
    bool moved=false;
    do
    {
        switch (random(0,4))
        {
        case 0:
            moved=MoveTiles(1,0);
            break;
        case 1:
            moved=MoveTiles(-1,0);
            break;
        case 2:
            moved=MoveTiles(0,1);
            break;
        case 3:
            moved=MoveTiles(0,-1);
            break;
        default:
            moved=false;
            break;
        }
    }
    while(!moved);
    ShuffleCount--;
}


bool IsWinning()
{
    //During shuffle win is not valid
    if(ShuffleCount>0) return false;

    int8_t index=0;
    for (int8_t r=0; r<GRID_ROWS; r++)
    {
        for (int8_t c=0; c<GRID_COLUMNS; c++)
        {
            //if reach last tile --> Wins!
            if (index==15)
            {
                return true;
            }
            //If wrong index --> Not Win.
            if(tiles[r][c].val!=index)
            {
                return false;
            }
            index++;
        }
    }
}

bool A_B_Pressed()
{
    return g.buttons.pressed(BTN_A) || g.buttons.pressed(BTN_B);
}

void PlayerInput()
{
    //Move on key pressed
    bool moved=false;
    if(g.buttons.pressed(BTN_UP))
    {
        moved=MoveTiles(1,0);
    }
    if(g.buttons.pressed(BTN_DOWN))
    {
        moved=MoveTiles(-1,0);
    }
    if(g.buttons.pressed(BTN_LEFT))
    {
        moved=MoveTiles(0,1);
    }
    if(g.buttons.pressed(BTN_RIGHT))
    {
        moved=MoveTiles(0,-1);
    }
    //Count valid moves
    if(moved)
    {
        PlayerMoves++;
    }

    //Cheat to win
    if(g.buttons.upBtn() && g.buttons.aBtn() )
    {
        ssm=SSM::Victory;
    }

    if(g.buttons.pressed(BTN_B))
    {
        HighlightNumbers=!HighlightNumbers;
    }

    //Enter Menu
    if(g.buttons.cBtn())
    {
        menuItem=MenuItems::Back;
        ssm=SSM::Menu;
    }
}

void DrawLevel(int level,bool drawBorder,bool drawNumbers,bool highlightNumbers,bool drawFull)
{
    switch(level)
    {
    case 0:
        DrawTiles(jonne_pal,jonne,drawBorder,drawNumbers,highlightNumbers,drawFull);
        TextColor=7;
        AuthorActive="Art by @jonne";
        break;
    case 1:
        DrawTiles(Vampirics_pal,Vampirics,drawBorder,drawNumbers,highlightNumbers,drawFull);
        TextColor=7;
        AuthorActive="by Vampirics";
        break;
    case 2:
        DrawTiles(adekto_pal,adekto,drawBorder,drawNumbers,highlightNumbers,drawFull);
        TextColor=7;
        AuthorActive="by @adekto";
        break;
    case 3:
        DrawTiles(JAP_pal,JAP,drawBorder,drawNumbers,highlightNumbers,drawFull);
        TextColor=7;
        AuthorActive="by JAP";
        break;
    case 4:
        DrawTiles(lancelot_gao_pal,lancelot_gao,drawBorder,drawNumbers,highlightNumbers,drawFull);
        TextColor=7;
        AuthorActive="by lancelot_gao";
        break;
    case 5:
        DrawTiles(jonne2_pal,jonne2,drawBorder,drawNumbers,highlightNumbers,drawFull);
        TextColor=7;
        AuthorActive="by @jonne";
        break;
    case 6:
        DrawTiles(buch_pal,buch,drawBorder,drawNumbers,highlightNumbers,drawFull);
        TextColor=7;
        AuthorActive="by Buch";
        break;
    case 7:
        DrawTiles(jonne3_pal,jonne3,drawBorder,drawNumbers,highlightNumbers,drawFull);
        TextColor=7;
        AuthorActive="by @jonne";
        break;
    default:
        break;
    }
}

void DrawGameGUI()
{
    //Draw moves
    g.display.color=TextColor;
    g.display.setCursor(0,2);
    g.display.print("Mov:");
    g.display.print(PlayerMoves);

    //Draw score
    g.display.color=TextColor;
    g.display.setCursor((g.display.width-(9*g.display.fontWidth))/2,2);
    g.display.print("Score:");
    g.display.print(Score);

    //Draw Level
    g.display.color=TextColor;
    g.display.setCursor(g.display.width-(5*g.display.fontWidth),2);
    g.display.print("Lvl:");
    g.display.print(LevelActive+1);
}


void PrintCenter(uint8_t x,uint8_t y,char text[],int lenght)
{
    g.display.print(x-((lenght*g.display.fontWidth)/2),
                    y-(g.display.fontHeight/2),
                    text);
}

void StateIntro()
{
    g.display.load565Palette(Title_pal);
    g.display.drawBitmap(0,20,Title);
    g.display.color=(FrameCount/3)%15;
    PrintCenter(g.display.width/2,10,"POK15",sizeof("POK15"));

    g.display.color=(FrameCount/3)%15;
    PrintCenter(g.display.width/2,g.display.height-10,"@HomineLudens",sizeof("@HomineLudens"));

    LevelActive=0;
    Score=0;

    if(A_B_Pressed())
        ssm=SSM::LoadAndShuffle;
}

void StateLoadAndShuffle()
{
    InitTiles();
    ShuffleCount=SHUFFLE_MOVES*(LevelActive+1);
    PlayerMoves=0;
    ssm=SSM::Game;
}

void StateGame()
{
    if(ShuffleCount>0)
    {
        ShuffleTiles();
    }
    else
    {
        PlayerInput();
    }
    DrawLevel(LevelActive,true,true,HighlightNumbers,false);
    DrawGameGUI();

    if(IsWinning())
    {
        FrameCount=0;
        ssm=SSM::Victory;
    }
}

void StateMenu()
{
    //Load palette for menu
    g.display.loadRGBPalette(palettePico);

    //User input
    if(g.buttons.pressed(BTN_UP))
    {
        menuItem=(MenuItems)((int)(menuItem)-1);
    }
    if(g.buttons.pressed(BTN_DOWN))
    {
        menuItem=(MenuItems)((int)(menuItem)+1);
    }
    menuItem =(MenuItems) ClampVal((int)menuItem,0,MenusSize-1);

    if(menuItem==MenuItems::SelectLevel)
    {
        if(g.buttons.pressed(BTN_LEFT))
        {
            LevelActive--;
        }
        if(g.buttons.pressed(BTN_RIGHT))
        {
            LevelActive++;
        }
    }
    LevelActive = ClampVal((int)LevelActive,0,MAX_LEVEL);


    g.display.color=8;
    g.display.fillRect(10,8+(menuItem)*20,150,10);

    //Draw menu
    for(int8_t i=0; i<MenusSize; i++)
    {
        g.display.color=12;
        g.display.print(10,10+(i)*20,Menus[i]);
        if(i==MenuItems::SelectLevel)
        {
            g.display.print(120,10+(i)*20, LevelActive+1);
        }
    }

    //Info
    g.display.print(0,100,Info);

    //Buttons
    if(g.buttons.pressed(BTN_A))
    {
        switch(menuItem)
        {
        case MenuItems::Back:
            ssm=SSM::Game;
            break;
        case MenuItems::TitleScreen:
            ssm=SSM::Intro;
            break;
        case MenuItems::SelectLevel:
            ssm=SSM::LoadAndShuffle;
            break;
        case MenuItems::Shuffle:
            ssm=SSM::LoadAndShuffle;
            break;
        }
    }
}

uint16_t StringLenght(char *s)
{
    uint16_t l=0;
    while(true)
    {
        if(s[l]==NULL)
            return l;
        l++;
    }
}

void StateVictory()
{
    g.display.clear();
    DrawLevel(LevelActive,false,false,false,true);

    //Draw author
    g.display.color=random(15);
    g.display.print((g.display.width - strlen(AuthorActive)*g.display.fontWidth)/2 ,0,AuthorActive);

    g.display.color=random(15);
    g.display.drawRect(g.display.width/2-80,10,160,160);

    if(A_B_Pressed() || FrameCount>600)
    {
        Score+=(LevelActive+1)*1000/(PlayerMoves+1);
        ssm=SSM::Outro;
    }
}

void StateOutro()
{
    g.display.persistence=true;
    g.display.setInvisibleColor(255);

    int xo = (g.display.width - (TILE_SIZE*GRID_COLUMNS))/2;
    int yo = (g.display.height - (TILE_SIZE*GRID_ROWS))/2;
    int x,y;
    float dx,dy;
    for (int8_t r=0; r<GRID_ROWS; r++)
    {
        for (int8_t c=0; c<GRID_COLUMNS; c++)
        {
            //Detect walls to bump
            x=xo+c*TILE_SIZE+tiles[r][c].xa;
            y=yo+r*TILE_SIZE+tiles[r][c].ya;
            if(x<0 || x>g.display.width-TILE_SIZE)
            {
                tiles[r][c].dir=-tiles[r][c].dir;
            }
            else if(y<0 || y>g.display.height-TILE_SIZE)
            {
                tiles[r][c].dir=PI-tiles[r][c].dir;
            }

            //Friction
            tiles[r][c].speed*=0.99f;

            //Apply forces
            dx=tiles[r][c].speed * sin(tiles[r][c].dir);
            dy=(tiles[r][c].speed * cos(tiles[r][c].dir))+0.9f;

            //Clamp on walls
            if(x<0 && dx<0) dx=0;
            if(x>g.display.width-TILE_SIZE && dx>0) dx=0;
            if(y<0 && dy<0) dy=0;
            if(y>g.display.height-TILE_SIZE && dy>0) dy=0;

            //Apply movement
            tiles[r][c].xa+=dx;
            tiles[r][c].ya+=dy;
        }
    }
    DrawLevel(LevelActive,true,false,false,true);

    //Draw win sign
    g.display.color=random(0);
    g.display.fillRect((g.display.width-(g.display.fontWidth*10))/2,
                       (g.display.height-g.display.fontHeight*3)/2,
                       g.display.fontWidth*12,
                       g.display.fontHeight*4);
    g.display.color=random(0,15);
    g.display.drawRect((g.display.width-(g.display.fontWidth*10))/2,
                       (g.display.height-g.display.fontHeight*3)/2,
                       g.display.fontWidth*12,
                       g.display.fontHeight*4);
    g.display.color=random(0,15);
    g.display.print((g.display.width-(g.display.fontWidth*7))/2,
                    (g.display.height-g.display.fontHeight)/2,
                    "WELL DONE");
    //g.display.color=random(0,15);
    g.display.setCursor((g.display.width-(g.display.fontWidth*8))/2,
                        (g.display.height-g.display.fontHeight)/2+10);
    g.display.print("Score:");
    g.display.print(Score);

    if(A_B_Pressed())
    {
        g.display.persistence=false;
        g.display.setInvisibleColor(0);
        LevelActive++;
        if(LevelActive<=MAX_LEVEL)
            ssm=SSM::LoadAndShuffle;
        else
            ssm=SSM::GameOver;
    }
}

void StateGameOver()
{
    PrintCenter(g.display.width/2,g.display.height/2,"GAME OVER",sizeof("GAME OVER"));

    DrawGameGUI();

    if(A_B_Pressed())
    {
        ssm=SSM::Intro;
    }

    if(g.buttons.cBtn())
    {
        menuItem=MenuItems::Back;
        ssm=SSM::Menu;
    }
}

int main ()
{
    g.begin(); // start the application
    g.setFrameRate(20);
    g.display.font = fontC64;
    g.display.setInvisibleColor(0);

    while (g.isRunning())
    {
        if (g.update())
        {
            switch(ssm)
            {
            case SSM::Intro:
                StateIntro();
                break;
            case SSM::Menu:
                StateMenu();
                break;
            case SSM::LoadAndShuffle:
                StateLoadAndShuffle();
                break;
            case SSM::Game:
                StateGame();
                break;
            case SSM::Victory:
                StateVictory();
                break;
            case SSM::Outro:
                StateOutro();
                break;
            case SSM::GameOver:
                StateGameOver();
                break;
            default:
                break;
            }

            FrameCount++;
        }
    }
    return 0; // this is "good programming manners". Program informs it ended without errors
}