Classic 15 puzzle
Diff: main.c
- 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 +} +