Classic 15 puzzle

Dependencies:   PokittoLib

Revision:
0:b0c9d3b227f6
Child:
2:6d5ef2d31e52
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.c	Mon May 07 18:24:02 2018 +0000
@@ -0,0 +1,623 @@
+
+#include "Pokitto.h" // include Pokitto library
+#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 6
+
+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;
+
+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, is to slide all the tiles to put them in order.\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;
+
+    bool sequenceOk=true;
+    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(City_pal,City,drawBorder,drawNumbers,highlightNumbers,drawFull);
+        TextColor=7;
+        break;
+    case 1:
+        DrawTiles(Forest_pal,Forest,drawBorder,drawNumbers,highlightNumbers,drawFull);
+        TextColor=7;
+        break;
+    case 2:
+        DrawTiles(StarWars_pal,StarWars,drawBorder,drawNumbers,highlightNumbers,drawFull);
+        TextColor=7;
+        break;
+    case 3:
+        DrawTiles(Mage_pal,Mage,drawBorder,drawNumbers,highlightNumbers,drawFull);
+        TextColor=7;
+        break;
+    case 4:
+        DrawTiles(Gnome_pal,Gnome,drawBorder,drawNumbers,highlightNumbers,drawFull);
+        TextColor=7;
+        break;
+    case 5:
+        DrawTiles(Kid_pal,Kid,drawBorder,drawNumbers,highlightNumbers,drawFull);
+        TextColor=7;
+        break;
+    case 6:
+        DrawTiles(Sword_pal,Sword,drawBorder,drawNumbers,highlightNumbers,drawFull);
+        TextColor=7;
+        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;
+        }
+    }
+
+}
+
+void StateVictory()
+{
+    g.display.clear();
+    DrawLevel(LevelActive,false,false,false,true);
+    g.display.color=random(15);
+    g.display.drawRect(10,1,g.display.width-20,g.display.height-2);
+
+    if(A_B_Pressed() || FrameCount>40)
+    {
+        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,
+                    " YOU WIN!");
+    //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
+}
+