568 lines
15 KiB
C++
568 lines
15 KiB
C++
#include<iostream>
|
|
#include<vector>
|
|
#include<filesystem>
|
|
|
|
#include<algorithm>
|
|
#include<random>
|
|
#include<chrono>
|
|
|
|
#include<SDL.h>
|
|
|
|
#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<PSX_Texture> 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<PSX_Texture> pageIndicator;
|
|
pageIndicator.reserve(static_cast<int>(thumbs.size())/thumbAmountW/thumbAmountH + 1);
|
|
std::vector<SDL_Rect> rPageIndicator;
|
|
rPageIndicator.resize(static_cast<int>(thumbs.size())/thumbAmountW/thumbAmountH + 1);
|
|
for(int i = 0; i < static_cast<int>(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<int>(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<bool> fCompletedThumbs;
|
|
fCompletedThumbs.resize(thumbs.size());
|
|
for(int i = 0; i < static_cast<int>(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<int>(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<std::vector<pos2d>> puzzleGrid;
|
|
|
|
// Random number list generation
|
|
std::vector<int> 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;
|
|
}
|
|
}
|