Classic 15 puzzle
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 }