#include #include #include #include #include #include #include #include"PSX_Texture.h" #include"PSX_Button.h" void init(); void close(); const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; SDL_Window* gWindow = NULL; SDL_Renderer* gRenderer = NULL; bool quit; struct pos2d{ int x; int y; }; pos2d mousePos; void mainMenu(); bool game(PSX_Texture* puzzle, int numPieces, bool directMode, bool fCompleted); bool isHovering(SDL_Rect* rect); void init(){ SDL_Init(SDL_INIT_VIDEO); gWindow = SDL_CreateWindow("pssp",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,SCREEN_WIDTH,SCREEN_HEIGHT,SDL_WINDOW_SHOWN); gRenderer = SDL_CreateRenderer(gWindow,-1,SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); SDL_SetRenderDrawColor(gRenderer,0xFF,0xFF,0xFF,0xFF); quit = false; } void close(){ SDL_DestroyRenderer(gRenderer); SDL_DestroyWindow(gWindow); gWindow = NULL; gRenderer = NULL; SDL_Quit(); } namespace fs = std::filesystem; //int main(int argc, char* argv[]){ int main(){ init(); // Defaults int numPieces = 3; bool directMode = false; /* Loading screen */ PSX_Texture loading(gRenderer); loading.loadText("loading...", SDL_Color{0xFF,0xFF,0xFF,0xFF},"assets/font.otf",40); SDL_Rect rLoading = { SCREEN_WIDTH/2 - loading.getW()/2, SCREEN_HEIGHT/2 - loading.getH()/2, loading.getW(), loading.getH() }; SDL_SetRenderDrawColor(gRenderer,0,0,0,0xFF); SDL_RenderClear(gRenderer); loading.render(&rLoading); SDL_RenderPresent(gRenderer); /* Title */ PSX_Texture title(gRenderer); title.loadText( "Posweg's Simple SDL2 Puzzle", SDL_Color{0xFF,0xFF,0xFF,0xFF}, "assets/font.otf", 25 ); SDL_Rect rTitle = {SCREEN_WIDTH/2 - title.getW()/2, 15, title.getW(), title.getH()}; /* Side panel */ int sSidePanelW = 200; int sidePanelMargin = 10; SDL_Rect rSidePanel = { SCREEN_WIDTH-sSidePanelW, rTitle.y*2+rTitle.h, sSidePanelW - sidePanelMargin, SCREEN_HEIGHT- (rTitle.y*2+rTitle.h) - sidePanelMargin, }; // Number of pieces PSX_Texture numText(gRenderer); numText.loadText("Number of pieces:",SDL_Color{0xFF,0xFF,0xFF,0xFF},"assets/font.otf",20); SDL_Rect rNumText = {rSidePanel.x,rSidePanel.y,numText.getW(),numText.getH()}; SDL_Rect rNum1 = {rNumText.x,rNumText.y + rNumText.h + sidePanelMargin, 30,30}; PSX_Button num1(gRenderer, &rNum1, "2"); SDL_Rect rNum2 = {rNumText.x + rNum1.w + 10, rNumText.y + rNumText.h + sidePanelMargin, 30,30}; PSX_Button num2(gRenderer, &rNum2, "3"); SDL_Rect rNum3 = {rNumText.x + rNum2.w*2 + 10*2, rNumText.y + rNumText.h + sidePanelMargin, 30,30}; PSX_Button num3(gRenderer, &rNum3, "4"); // Mode of play PSX_Texture modeText(gRenderer); modeText.loadText("Mode:",SDL_Color{0xFF,0xFF,0xFF,0xFF},"assets/font.otf",20); SDL_Rect rModeText = { rSidePanel.x, rNum1.y + rNum1.h + sidePanelMargin, modeText.getW(), modeText.getH() }; // Quit button SDL_Rect rQuitButton = {rSidePanel.x,rSidePanel.y + rSidePanel.h - 50, rSidePanel.w, 50}; PSX_Button quitButton(gRenderer, &rQuitButton, "Quit"); /* Thumbs */ // Thumb layout composition int thumbW = 50; int thumbH = thumbW; int margin = 10; int outerMargin = 10; int topMargin = rTitle.y*2 + rTitle.h; int containerW = SCREEN_WIDTH - 200; int containerH = SCREEN_HEIGHT - topMargin - topMargin; int actualPage = 0; int thumbAmountW = 1 + (containerW-outerMargin*2 - thumbW) / (thumbW + margin); int thumbAmountH = 1 + (containerH - thumbH) / (thumbH + margin); int leftMargin = (containerW-thumbAmountW*(thumbW + margin) + margin)/2; // Individual thumb creation std::vector thumbs; thumbs.reserve(1000); // Without reservation the textures // glitch out very ugly PSX_Texture thumbName(gRenderer); std::string finalMessage; SDL_Rect rThumbsName; // SDL_Texture creation of every JPG image in the folder specified int k = 0; for(const auto & entry : fs::directory_iterator("images")){ if(entry.path().extension() == ".jpg"){ SDL_SetRenderDrawColor(gRenderer,0,0,0,0xFF); SDL_RenderClear(gRenderer); loading.render(&rLoading); thumbs.push_back(PSX_Texture(gRenderer)); thumbs[k].loadTexture(entry.path().string()); finalMessage = ""; finalMessage.append("Importing texture: "); finalMessage.append(entry.path()); thumbName.loadText(finalMessage,SDL_Color{127,127,127,0xFF},"assets/font.otf",15); rThumbsName = { SCREEN_WIDTH/2 - thumbName.getW()/2, rLoading.y + rLoading.h + 10, thumbName.getW(), thumbName.getH() }; thumbName.render(&rThumbsName); SDL_RenderPresent(gRenderer); k++; } } // Page changer and indicator std::vector pageIndicator; pageIndicator.reserve(static_cast(thumbs.size())/thumbAmountW/thumbAmountH + 1); std::vector rPageIndicator; rPageIndicator.resize(static_cast(thumbs.size())/thumbAmountW/thumbAmountH + 1); for(int i = 0; i < static_cast(thumbs.size())/thumbAmountW/thumbAmountH + 1; i++){ pageIndicator.push_back(PSX_Texture(gRenderer)); pageIndicator[i].loadText( std::to_string(i+1) + " / " + std::to_string(static_cast(thumbs.size())/thumbAmountW/thumbAmountH + 1), SDL_Color{0xFF,0xFF,0xFF,0xFF}, "assets/font.otf",20 ); rPageIndicator[i].x = containerW/2 - pageIndicator[i].getW()/2; rPageIndicator[i].y = SCREEN_HEIGHT - pageIndicator[i].getH()/2 - topMargin/2; rPageIndicator[i].w = pageIndicator[i].getW(); rPageIndicator[i].h = pageIndicator[i].getH(); } SDL_Rect rPreviousButton = { rPageIndicator[0].x - 150 - 20, rPageIndicator[0].y - 5, 150, rPageIndicator[0].h + 10 }; PSX_Button previousButton(gRenderer, &rPreviousButton, "Previous page"); SDL_Rect rNextButton = { rPageIndicator[0].x + rPageIndicator[0].w + 20, rPreviousButton.y, rPreviousButton.w, rPreviousButton.h }; PSX_Button nextButton(gRenderer, &rNextButton, "Next page"); // To track completed images std::vector fCompletedThumbs; fCompletedThumbs.resize(thumbs.size()); for(int i = 0; i < static_cast(thumbs.size()); i++){ fCompletedThumbs[i] = false; } /* Rendering */ SDL_Event e; while(!quit){ bool click = false; while(SDL_PollEvent(&e)!=0){ if(e.type == SDL_QUIT){ quit = true; } if(e.type == SDL_MOUSEBUTTONDOWN){ click = true; } } SDL_SetRenderDrawBlendMode(gRenderer, SDL_BLENDMODE_BLEND); // The white rect won't let the image // behind be seen without setting the blendmode SDL_SetRenderDrawColor(gRenderer,0,0,0,0xFF); SDL_RenderClear(gRenderer); SDL_GetMouseState(&mousePos.x, &mousePos.y); title.render(&rTitle); // Side panel numText.render(&rNumText); num1.render(isHovering(&rNum1)); num2.render(isHovering(&rNum2)); num3.render(isHovering(&rNum3)); if(isHovering(&rQuitButton)){ quitButton.render(true); if(click){ quit = true; } } else quitButton.render(false); if(numPieces == 2) num1.render(3); else if(isHovering(&rNum1)){ num1.render(true); if(click) numPieces = 2; } else num1.render(false); if(numPieces == 3) num2.render(3); else if(isHovering(&rNum2) or numPieces == 3){ num2.render(true); if(click) numPieces = 3; } else num2.render(false); if(numPieces == 4) num3.render(3); else if(isHovering(&rNum3) or numPieces == 4){ num3.render(true); if(click) numPieces = 4; } else num3.render(false); //modeText.render(&rModeText); if(actualPage <= 0){ previousButton.render(3); } else if(isHovering(&rPreviousButton)){ previousButton.render(true); if(click) actualPage--; } else previousButton.render(false); if(actualPage >= static_cast(thumbs.size())/thumbAmountW/thumbAmountH){ nextButton.render(3); } else if(isHovering(&rNextButton)){ nextButton.render(true); if(click) actualPage++; } else nextButton.render(false); pageIndicator[actualPage].render(&rPageIndicator[actualPage]); // Thumbs loop for(int i = 0; i < k; i++){ if(i/thumbAmountW/thumbAmountH == actualPage){ // Position every thumb SDL_Rect tempRect = { leftMargin + (thumbW + margin) * (i - (i/thumbAmountW)*thumbAmountW), topMargin + (thumbH + margin) * ( i/thumbAmountW - (i/thumbAmountW/thumbAmountH)*thumbAmountH), thumbW, thumbH }; thumbs[i].render(&tempRect); if(fCompletedThumbs[i] == false){ SDL_SetRenderDrawColor(gRenderer,0,0,0,200); SDL_RenderFillRect(gRenderer, &tempRect); } if(isHovering(&tempRect)){ SDL_SetRenderDrawColor(gRenderer, 0xFF,0xFF,0xFF,127); SDL_RenderFillRect(gRenderer, &tempRect); if(click){ click = false; fCompletedThumbs[i] = game( &thumbs[i], numPieces, directMode, fCompletedThumbs[i] ); } } } } SDL_RenderPresent(gRenderer); } close(); return 0; } bool game(PSX_Texture* puzzle, int numPieces, bool directMode, bool fCompleted){ int puzzleDivX = numPieces; // Number of horizontal blocks int puzzleDivY = puzzleDivX; // Number of vertical block int space = 0; // Space between blocks SDL_Rect puzzleRect; puzzleRect.w = 360; puzzleRect.h = puzzleRect.w; puzzleRect.y = SCREEN_HEIGHT/2 - puzzleRect.h/2; puzzleRect.x = SCREEN_WIDTH/2 - puzzleRect.w/2; SDL_Rect puzzleSubRect = {0,0,0,0}; SDL_Rect puzzleSubFrame = {0,0,0,0}; pos2d puzzleGridBubble = {0,0}; pos2d puzzleGridHole = {0,0}; std::vector> puzzleGrid; // Random number list generation std::vector gridList; gridList.resize(puzzleDivX*puzzleDivY); for(int i = 0; i < puzzleDivX*puzzleDivY; i++){ gridList[i] = i; } std::random_device rd; std::mt19937 g(rd()); std::shuffle(gridList.begin(), gridList.end(), g); // The list is generated in one dimension to // assure there won't be repeatings and make // the alorith easier. Later it's going to be // transformed back again to a 2D Matrix. // Assing the random number list to the grid puzzleGrid.resize(puzzleDivX); for(int i = 0; i < puzzleDivX; i++){ puzzleGrid[i].resize(puzzleDivY); for(int j = 0; j < puzzleDivY; j++){ // Convert from list of integers to 2D grid and assign puzzleGrid[i][j].x = gridList[i*puzzleDivX+j]/puzzleDivX; puzzleGrid[i][j].y = gridList[i*puzzleDivX+j] - puzzleGrid[i][j].x * puzzleDivX; // The [i*PuzzleDivX+j] is to keep track of the list // index number, since we're in nested for loops // and not a plain one. 2D matrix -> 1D list // Inverse order mode /*puzzleGrid[i][j].x = puzzleDivX - (i + 1); puzzleGrid[i][j].y = puzzleDivY - (j + 1);*/ // Detect where the hole is in the random grid if( puzzleGrid[i][j].x == puzzleDivY-1 and puzzleGrid[i][j].y == puzzleDivX-1 ){ puzzleGridHole = {i,j}; } } } /* Bottom buttons */ // Return button SDL_Rect rBackButton; rBackButton.w = 200; rBackButton.h = 50; rBackButton.y = SCREEN_HEIGHT - rBackButton.h - (SCREEN_HEIGHT - puzzleRect.h - rBackButton.h) / 3; rBackButton.x = SCREEN_WIDTH - rBackButton.w - (SCREEN_WIDTH/2 - ( rBackButton.w*2 + (SCREEN_HEIGHT-(rBackButton.y+rBackButton.h))) /2 ); PSX_Button backButton(gRenderer, &rBackButton, "Back"); // Shuffle again button SDL_Rect rShuffleButton = { SCREEN_WIDTH - rBackButton.x - rBackButton.w, rBackButton.y, rBackButton.w, rBackButton.h }; PSX_Button shuffleButton(gRenderer, &rShuffleButton, "Delete"); SDL_Event e; while(!quit){ bool click = false; while(SDL_PollEvent(&e)!=0){ if(e.type == SDL_QUIT){ quit = true; } if(e.type == SDL_MOUSEBUTTONDOWN){ click = true; } } SDL_SetRenderDrawBlendMode(gRenderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(gRenderer,0,0,0,0xFF); SDL_RenderClear(gRenderer); SDL_GetMouseState(&mousePos.x, &mousePos.y); if(!fCompleted){ fCompleted = true; for(int i = 0; i < puzzleDivX; i++){ for(int j = 0; j < puzzleDivY; j++){ // Position the pieces puzzleSubRect = { puzzleRect.x + i * puzzleRect.w / puzzleDivX + space * i, puzzleRect.y + j * puzzleRect.h / puzzleDivY + space * j, puzzleRect.w / puzzleDivX, puzzleRect.h / puzzleDivY, }; // Set the section of the image to show in the actual piece puzzleSubFrame = { (puzzleGrid[i][j].x * puzzle->getW()) / puzzleDivX, (puzzleGrid[i][j].y * puzzle->getH()) / puzzleDivY, puzzle->getW() / puzzleDivX, puzzle->getH() / puzzleDivY, }; // Piece interaction, ignore the hole piece if(puzzleGrid[i][j].x != puzzleDivY-1 or puzzleGrid[i][j].y != puzzleDivX-1){ puzzle->render(&puzzleSubRect, &puzzleSubFrame); if(isHovering(&puzzleSubRect)){ // Hover effect SDL_SetRenderDrawColor(gRenderer, 0xFF,0xFF,0xFF,127); SDL_RenderFillRect(gRenderer, &puzzleSubRect); if( // Direct motion mode click and( !directMode or (puzzleGridHole.x+1 == i and puzzleGridHole.y == j) or (puzzleGridHole.x-1 == i and puzzleGridHole.y == j) or (puzzleGridHole.x == i and puzzleGridHole.y-1 == j) or (puzzleGridHole.x == i and puzzleGridHole.y+1 == j) ) ){ // Bubble swap the selected piece with the hole puzzleGridBubble = puzzleGrid [puzzleGridHole.x] [puzzleGridHole.y]; puzzleGrid [puzzleGridHole.x] [puzzleGridHole.y] = puzzleGrid[i][j]; puzzleGrid[i][j] = puzzleGridBubble; // Update where the hole is puzzleGridHole = {i,j}; } } } // Check if piece is in the correct spot. If not, set // the completed flag to false if(i != puzzleGrid[i][j].x or j != puzzleGrid[i][j].y) fCompleted = false; } } } if(fCompleted){ puzzleRect.y = SCREEN_HEIGHT - (rBackButton.y + rBackButton.h); if(isHovering(&rBackButton)){ backButton.render(true); if(click){ click = false; return true; } } else backButton.render(false); if(isHovering(&rShuffleButton)){ shuffleButton.render(true); if(click){ click = false; return false; } } else shuffleButton.render(false); puzzle->render(&puzzleRect); } SDL_RenderPresent(gRenderer); } return false; } bool isHovering(SDL_Rect* rect){ if( mousePos.x > rect->x and mousePos.x < rect->x + rect->w and mousePos.y > rect->y and mousePos.y < rect->y + rect->h ){ return true; } else{ return false; } }