6
\$\begingroup\$

I have made a simple sliding puzzle/game of fifteen using SDL. I originally attempted this to learn about game states, but it turned into a general exercise in code organisation and SDL use. Therefore, I would especially appreciate comments regarding organisation and clarity, but general suggestions or comments are very welcome as well.

I should note that there is a bug that occasionally causes the program to remain open (despite being closed according to XCode and Activity Monitor) until you hard reboot the computer. I'm not sure what causes this bug (please tell me if you do), but I'd advise against running the code on your computer!

Posted below are Game.cpp, Gamestate_MainGame.cpp, Gamestate_Menu.cpp, Graphics.cpp and Tile.h. The full source of this project can be found here

Game.cpp

#include <iostream>
#include <random>
#include "Game.h"
#include "Tile.h"
#include "Gamestate.h"


Game::Game() {
    quit = false;
}

bool Game::init(GameState* state) {
    graphics.setup();
    pushState(state);
    return true;
}

void Game::loop()
{
    while(quit == false)
    {
        update();
        render();
    }
    quitGame();
}

void Game::update()
{
    while(SDL_PollEvent(&event))
    {
        if(states.size() > 0){
            states.back()->handleEvents(event);
        }
        if(states.size() > 0){
            states.back()->update();
        }
    }
}

void Game::render()
{
    if(states.size() > 0)
        states.back()->render();
}

void Game::setQuit() {
    quit = true;
}

void Game::toggleCatMode() {
    if (catMode == false)
        catMode = true;
    else if (catMode == true)
        catMode = false;
}

void Game::pushState(GameState* state) {
    states.push_back(state);
    if(state->init(&graphics, this) == false)
        quit = true;
}

void Game::popState() {
    delete states.back();
    states.pop_back();

    if(states.size() == 0)
        quit = true;
}

void Game::quitGame() {
    while(states.size() > 0)
    {
        delete states.back();
        states.pop_back();
    }
    graphics.destroy();
    SDL_Quit();
}

Gamestate_MainGame.cpp

#include <iostream>
#include <random>
#include "Gamestate_MainGame.h"

bool MainGame::init(Graphics* graph, Game* g) {
    game     = g;
    graphics = graph;
    int shiftamount = 3;

    // Fill vector<SDL_Rect> 'positions' with possible positions of n*n tiles & make shadow positions
    loadPositions(positions, graphics->gridSize());
    loadPositions(shadowPositions, graphics->gridSize(), shiftamount);
    // Assign these starting positions to n*n tiles in vector<Tile> 'tiles' & make tile shadows
    makeTiles(tiles, positions, Tile::type::button);
    makeTiles(shadowTiles, shadowPositions, Tile::type::shadow);

    // Audio loading
    Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
    click = Mix_LoadWAV("assets/hover.wav");
    if( click == NULL ) {
        std::cerr << "Failed to load beat 'click', error:" << Mix_GetError() << std::endl;
        return false;
    }
    cat1 = Mix_LoadWAV("assets/meow1.wav");
    if( cat1 == NULL ) {
        std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
        return false;
    }
    cat2 = Mix_LoadWAV("assets/meow2.wav");
    if( cat2 == NULL ) {
        std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
        return false;
    }
    cat3 = Mix_LoadWAV("assets/meow3.wav");
    if( cat3 == NULL ) {
        std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
        return false;
    }

    return true;
}

void MainGame::loadPositions(std::vector<SDL_Rect>& positions, const int& gridsize, const int& shiftPx) {
    int x = graphics->winPadding();
    int y = graphics->winPadding();

    for (int i = 0; i < gridsize; ++i) {
        y = graphics->winPadding() + i*graphics->tileSize() + i*graphics->tilePadding() + shiftPx;
        for (int j = 0; j < gridsize; ++j) {
            x = graphics->winPadding() + j*graphics->tileSize() + j*graphics->tilePadding() + shiftPx;
            positions.push_back( SDL_Rect{ x, y, graphics->tileSize(), graphics->tileSize() } );
        }
    }
}

void MainGame::makeTiles(std::vector<Tile>& tiles, const std::vector<SDL_Rect>& positions, const int& tiletype) {
    for (int i = 0; i < positions.size(); ++i) {
        tiles.push_back(Tile{positions[i], i+1, tiletype}); // start counting from 1 for positions
    }
    tiles[tiles.size()-1].setTileType(Tile::type::invisible); // last tile should be invisible
}

void MainGame::handleEvents(SDL_Event& event) {
    if (event.type == SDL_QUIT)
        game->setQuit();

    if (event.type == SDL_KEYDOWN) {
        if (event.key.keysym.sym == SDLK_ESCAPE)
            game->setQuit();
    }

    if (event.type == SDL_KEYDOWN) {
        if (event.key.keysym.sym == SDLK_SPACE)
            game->popState();
    }

    if (event.type == SDL_MOUSEBUTTONDOWN) {
        if (gameWin == true)
            restart();

        else {
            int x; int y;
            SDL_GetMouseState(&x, &y);
            clickedTile = getClickedTile(x, y); // retreives clicked tile number or -1
        }
    }
}

void MainGame::restart() {
    gameWin = false;
    clicks = 0;
    scrambleTiles(tiles, shadowTiles);
}

void MainGame::update() {
    if (isSolved()) {
        gameWin = true;
    }

    else if (clickedTile >= 0) { // if mouse clicked on a tile
        if (isNeighbour(tiles[clickedTile], tiles[tiles.size()-1])) {
            tiles[clickedTile].swap(tiles[tiles.size()-1]);
            shadowTiles[clickedTile].swap(shadowTiles[shadowTiles.size()-1]);
            clickedTile = -1; // reset to default

            ++clicks;

            if (game->isCatMode()){
                std::random_device rd;
                std::mt19937 rng(rd());
                std::uniform_int_distribution<int> range(0, 2);

                int random = range(rd);
                switch(random) {
                    case 0: Mix_PlayChannel(-1, cat1, 0); break;
                    case 1: Mix_PlayChannel(-1, cat2, 0); break;
                    case 2: Mix_PlayChannel(-1, cat3, 0); break;
                }
            }
            else
                Mix_PlayChannel(-1, click, 0);
        }

    }
}

void MainGame::scrambleTiles(std::vector<Tile>& t, std::vector<Tile>& tshadow) {
    std::random_device rd;
    std::mt19937 rng(rd());
    std::uniform_int_distribution<int> tilerange(0, t.size()-2); // don't scramble last ('empty') tile

    for (int i = t.size()-2; i >= 0; --i) {
        int n = tilerange(rng);
        t[i].swap(t[n]);
        tshadow[i].swap(tshadow[n]);
    }
}

int MainGame::getClickedTile(const int &x, const int &y) {
    int tilenum = -1;
    for (int i = 0; i < tiles.size(); ++i) {
        if (! (x < tiles[i].position().x || x > tiles[i].position().x + graphics->tileSize() ||
               y < tiles[i].position().y || y > tiles[i].position().y + graphics->tileSize() ))
            tilenum = i ;
    }
    return tilenum;
}

bool MainGame::isNeighbour(const Tile& a, const Tile& b) {
    if (a.posNumber() == b.posNumber() - 1 ||
        a.posNumber() == b.posNumber() + 1 ||
        a.posNumber() == b.posNumber() + graphics->gridSize() ||
        a.posNumber() == b.posNumber() - graphics->gridSize())
        return true;
    return false;
}

bool MainGame::isSolved() {
    int correctTilesN = 0;

    for (int i = 0; i < tiles.size(); ++i) {
        if (tiles[i].position().x == positions[i].x &&
            tiles[i].position().y == positions[i].y)
            correctTilesN += 1;
    }
    if (correctTilesN == graphics->gridSize()*graphics->gridSize())
        return true;
    return false;
}

void MainGame::render() {
    graphics->renderClear();
    graphics->drawBoard(shadowTiles);
    graphics->drawBoard(tiles, true);

    if (game->isCatMode()) {
        graphics->renderCat(tiles, positions);
    }

    graphics->updateClicks(clicks);
    graphics->winMessage(gameWin, clicks, "You win! Click anywhere to restart"); // make sure it's not the start of the game
    graphics->menuInstruction("Press spacebar for main menu");
    graphics->update();
}

MainGame::~MainGame() {
    Mix_Quit();
    game     = NULL;
    graphics = NULL;
    click    = NULL;
    cat1     = NULL;
    cat2     = NULL;
    cat3     = NULL;
}

Gamestate_Menu.cpp

#include "Gamestate_Menu.h"
#include "Gamestate_MainGame.h"
#include <iostream>

bool MenuState::init(Graphics* graph, Game* g){
    graphics = graph;
    game = g;
    int shiftAmount = 6;

    // Make menu tiles
    loadPositions(positions, gridSize);
    makeTiles(tiles, positions, Tile::type::button);
    // Make menu 'shadow' tiles
    loadPositions(shadowPositions, gridSize, shiftAmount);
    makeTiles(shadowTiles, shadowPositions, Tile::type::shadow);

    // Load sounds
    Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
    rollOver = Mix_LoadWAV("assets/hover.wav");
    if( rollOver == NULL ) {
        std::cerr << "Failed to load beat 'rollover', error:" << Mix_GetError() << std::endl;
        return false;
    }
    clickSound = Mix_LoadWAV("assets/press.wav");
    if( clickSound == NULL ) {
        std::cerr << "Failed to load beat 'clicksound', error:" << Mix_GetError() << std::endl;
        return false;
    }
    return true;
}

void MenuState::loadPositions(std::vector<SDL_Rect>& shadowPositions, const int& gridsize, const int& shiftPx) {
    int x = graphics->winPadding();
    int y = graphics->winPadding();

    for (int i = 0; i < gridsize; ++i) {
        y = graphics->winPadding() + i*tileSize + i*tilePadding + shiftPx;
        for (int j = 0; j < gridsize; ++j) {
            x = graphics->winPadding() + j*tileSize + j*tilePadding + shiftPx;
            shadowPositions.push_back( SDL_Rect{ x, y, tileSize, tileSize } );
        }
    }
}

void MenuState::makeTiles(std::vector<Tile>& tiles, const std::vector<SDL_Rect>& positions, const int& tileType) {
    for (int i = 0; i < positions.size(); ++i)
        tiles.push_back(Tile{positions[i], i, tileType});
}

void MenuState::handleEvents(SDL_Event& e) {
    if (e.type == SDL_QUIT)
        game->setQuit();

    if (e.type == SDL_KEYDOWN) {
        if (e.key.keysym.sym == SDLK_ESCAPE)
            game->setQuit();
    }

    if (e.type == SDL_MOUSEMOTION) {
        int x; int y;
        SDL_GetMouseState(&x, &y);
        mousePos = getActiveTile(x, y);
    }

    if (e.type == SDL_MOUSEBUTTONDOWN)
        click = true;
}

int MenuState::getActiveTile(const int &x, const int &y) {
    int tilenum = -1;
    for (int i = tiles.size(); i >= 0; --i) {
        if ( !(x < tiles[i].position().x || x > tiles[i].position().x + tiles[i].position().w ||
               y < tiles[i].position().y || y > tiles[i].position().y + tiles[i].position().h) )
            tilenum = i ;
    }
    return tilenum;
}

void MenuState::update() {
    // if cursor landed on a button
    if (mousePos >=0) {

        setRollOver();

        if (click == true) {
            switch(mousePos) {
                case 0: graphics->setGridSize(3);
                        pushButton();
                    break;
                case 1: graphics->setGridSize(4);
                        pushButton();
                    break;
                case 2: graphics->setGridSize(5);
                        pushButton();
                    break;
                case 3: graphics->setGridSize(6);
                        pushButton();
                    break;
                case 4: graphics->setGridSize(7);
                        pushButton();
                    break;
                case 5: graphics->setGridSize(8);
                        pushButton();
                    break;
                case 6: pushButton();
                    break;
                case 7: game->pushState(new MainGame);
                    break;
            }
            click = false;
        }
    }
    else if (prevMousePos >= 0) {// unset rollover
        if (tiles[prevMousePos].tileType() != Tile::type::buttonpressed)
            tiles[prevMousePos].setTileType(Tile::type::button);
    }
}

void MenuState::setRollOver() {
    // Play sound if cursor first landed on button
    if (tiles[mousePos].tileType() != Tile::type::rollover &&
        tiles[mousePos].tileType() != Tile::type::buttonpressed)
        Mix_PlayChannel(-1, rollOver, 0);

    // reset all tiles
    for (int i = tiles.size(); i >= 0; --i) {
        if (tiles[i].tileType() != Tile::type::buttonpressed)
            tiles[i].setTileType(Tile::type::button);
    }
    // set rollover tile
    if (tiles[mousePos].tileType() != Tile::type::buttonpressed)
        tiles[mousePos].setTileType(Tile::type::rollover);

    prevMousePos = mousePos;
}

void MenuState::pushButton() {
    // Catmode button can be turned on/off
    if (mousePos == 6) {
        Mix_PlayChannel(-1, clickSound, 0);
        if (game->isCatMode())
            liftTile(mousePos);
        else
            pushTile(mousePos);
        game->toggleCatMode();
    }
    // all other tiles can only be turned on
    else if (prevButtonClicked != mousePos) {
        Mix_PlayChannel(-1, clickSound, 0);
        pushTile(mousePos);
        if (prevButtonClicked >= 0) {
            liftTile(prevButtonClicked);
        }
        prevButtonClicked = mousePos;
    }
}

void MenuState::pushTile(const int& mousepos) {
    tiles[mousepos].setTileShift(2, 2);
    tiles[mousepos].setTileType(Tile::type::buttonpressed);
}
void MenuState::liftTile(const int& prevbuttonClicked) {
    tiles[prevbuttonClicked].setTileShift(-2, -2);
    tiles[prevbuttonClicked].setTileType(Tile::type::button);
}

void MenuState::render() {
    graphics->renderClear();
    graphics->drawBoard(shadowTiles);
    graphics->drawBoard(tiles);
    graphics->menuText(tiles, game->isCatMode());
    graphics->update();
}

MenuState::~MenuState() {
    Mix_Quit();
    graphics   = NULL;
    game       = NULL;
    rollOver   = NULL;
    clickSound = NULL;
}

Graphics.cpp

#include "Graphics.h"
#include <iostream>
#include <SDL2_ttf/SDL_ttf.h>
#include <SDL2_image/SDL_image.h>

void Graphics::setup() {
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
        std::cerr << "Error: init" << SDL_GetError() << std::endl;

    else {
        window = SDL_CreateWindow("Sliding Puzzle", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
        if (window == nullptr)
            std::cerr << "Error: Create window" << SDL_GetError() << std::endl;

        else {
            renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
            if (renderer == nullptr)
                std::cerr << "Error: Create renderer" << SDL_GetError() << std::endl;

            else {
                if( TTF_Init() == -1 )
                    std::cerr << "SDL_ttf could not initialise" << TTF_GetError() << std::endl;
                else {
                    cat = IMG_LoadTexture(renderer, "assets/cat2.png");
                    if (cat == nullptr)
                        std::cerr << "Error: Load cat texture";
                }
            }
        }
    }
}

void Graphics::destroy() {
    TTF_Quit();
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    window     = NULL;
    renderer   = NULL;
    tilenumber = NULL;
    clicks     = NULL;
    wintext    = NULL;
    menutext   = NULL;
    menuinstr  = NULL;
    cat        = NULL;
}

void Graphics::setGridSize(const int& n) {
    grid = n;
    rawSize = (WINDOW_WIDTH - 2*WINDOW_PADDING) / grid;
    TILE_PADDING = rawSize / 10;
    TILE_SIZE    = rawSize - TILE_PADDING;
}

void Graphics::drawBoard(const std::vector<Tile>& t, const bool& drawnumber) {
    for (int i = 0; i < t.size(); ++i) {
        SDL_Rect temp = t[i].position();

        if      (t[i].tileType() == Tile::type::invisible)
            SDL_SetRenderDrawColor(renderer, 163, 118, 172, 1); // bg colour

        else if (t[i].tileType() == Tile::type::rollover || t[i].tileType() == Tile::type::buttonpressed)
            SDL_SetRenderDrawColor(renderer, 255, 255, 255, 1); // highlight colour

        else if (t[i].tileType() == Tile::type::shadow)
            SDL_SetRenderDrawColor(renderer, 100, 65, 107, 1); // shadow colour

        else
            SDL_SetRenderDrawColor(renderer, 237, 229, 239, 1); // default colour

        SDL_RenderFillRect(renderer, &temp);

        if (drawnumber){
            if (t[i].tileType() != Tile::type::invisible) {
                std::string num = std::to_string(i+1); // position numbers count from 1
                tilenumber = renderText(num, "assets/Calibrib.ttf", fontcolour, 25);
                SDL_QueryTexture(tilenumber, NULL, NULL, &temp.w, &temp.h);
                renderTexture(tilenumber, temp.x+(TILE_SIZE/2 - temp.w/2), temp.y+(TILE_SIZE/2 - temp.h/2), nullptr);
            }
        }
    }
}

void Graphics::renderCat(const std::vector<Tile>& tiles, const std::vector<SDL_Rect>& positions) {
    // for all but the last (invisible) tile
    for (int i = tiles.size()-2; i >= 0; --i) {
        for (int j = tiles.size()-2; j >= 0; --j) {
            // 'attach' tile position n to position of cat photo
            SDL_Rect temp = positions[j];
            renderTexture(cat, tiles[j].position().x, tiles[j].position().y, &temp);
        }
    }
}

void Graphics::updateClicks(const int& n) {
    std::string count = std::to_string(n);
    std::string message = "Clicks: " + count;
    clicks = renderText(message, "assets/GreenFlame.ttf", fontcolour, 15);
    int W; int H;
    SDL_QueryTexture(clicks, NULL, NULL, &W, &H);

    SDL_Rect clickBanner       { WINDOW_WIDTH/2-W, WINDOW_HEIGHT/6-3*H, 2*W, 2*H };
    SDL_Rect clickBannerShadow { WINDOW_WIDTH/2-W + 3, WINDOW_HEIGHT/6-3*H + 3, 2*W, 2*H};
    SDL_SetRenderDrawColor     (renderer, 100, 65, 107, 1);
    SDL_RenderFillRect         (renderer, &clickBannerShadow);
    SDL_SetRenderDrawColor     (renderer, 237, 229, 239, 1);
    SDL_RenderFillRect         (renderer, &clickBanner);
    renderTexture(clicks, (0.77*(clickBanner.x+clickBanner.w) - W/2), (0.8*(clickBanner.y+clickBanner.h) - H/2), nullptr);
}

void Graphics::winMessage(const bool& win, const int& clicks, const std::string& message) {
    if (win == true && clicks > 0){
        wintext = renderText(message, "assets/Calibrib.ttf", fontcolour, 15);
        int W; int H;
        SDL_QueryTexture(wintext, NULL, NULL, &W, &H);

        SDL_Rect winBannerShadow { WINDOW_PADDING + 3, WINDOW_PADDING+TILE_SIZE+TILE_PADDING + 3,
                                   3*TILE_SIZE + 2*TILE_PADDING, TILE_SIZE};
        SDL_Rect winBanner       { WINDOW_PADDING, WINDOW_PADDING+TILE_SIZE+TILE_PADDING,
                                   3*TILE_SIZE + 2*TILE_PADDING, TILE_SIZE};
        SDL_SetRenderDrawColor   (renderer, 100, 65, 107, 1);
        SDL_RenderFillRect       (renderer, &winBannerShadow);
        SDL_SetRenderDrawColor   (renderer, 237, 229, 239, 1);
        SDL_RenderFillRect       (renderer, &winBanner);
        renderTexture(wintext, (0.5*WINDOW_WIDTH - 0.5*W), (0.5*WINDOW_HEIGHT - H), nullptr);
    }
}

void Graphics::menuInstruction(const std::string& message) {
    SDL_Color darkpink { 100, 65, 107, 1 };
    menuinstr = renderText(message, "assets/GreenFlame.ttf", darkpink, 12);
    int W; int H;
    SDL_QueryTexture(menuinstr, NULL, NULL, &W, &H);
    renderTexture(menuinstr, (0.5*WINDOW_WIDTH - 0.5*W), (WINDOW_HEIGHT - 0.5*WINDOW_PADDING), nullptr);
}

void Graphics::menuText(std::vector<Tile> tiles, const bool& catmode) {
    int number = 3;
    int W; int H;

    for (int i = 0; i < 6; ++i){
        std::string numStr = std::to_string(number);
        std::string message = numStr + " x " + numStr;

        menutext = renderText(message, "assets/Calibrib.ttf", fontcolour, 25);
        SDL_QueryTexture(menutext, NULL, NULL, &W, &H);
        renderTexture(menutext, (tiles[i].position().x + 0.5*tiles[i].position().w - 0.5*W),
                                (tiles[i].position().y + 0.5*tiles[i].position().h - 0.5*H), nullptr);
        number += 1;
    }
    if (catmode){
        SDL_Color tilecolour { 237, 229, 239, 1 };
        menutext = renderText("Cat Mode", "assets/Calibrib.ttf", tilecolour, 20);
        SDL_QueryTexture(menutext, NULL, NULL, &W, &H);
        renderTexture(menutext, (tiles[6].position().x + 0.5*tiles[6].position().w - 0.5*W + 2),
                                (tiles[6].position().y + 0.5*tiles[6].position().h - 0.5*H + 2), nullptr);
    }

    menutext = renderText("START", "assets/Calibrib.ttf", fontcolour, 25);
    SDL_QueryTexture(menutext, NULL, NULL, &W, &H);
    renderTexture(menutext, (tiles[7].position().x + 0.5*tiles[7].position().w - 0.5*W),
                            (tiles[7].position().y + 0.5*tiles[7].position().h - 0.5*H), nullptr);

    menutext = renderText("Select your grid size:", "assets/GreenFlame.ttf", fontcolour, 20);
    SDL_QueryTexture(menutext, NULL, NULL, &W, &H);
    renderTexture(menutext, 0.5*WINDOW_WIDTH - 0.5*W, WINDOW_PADDING-1.5*H, nullptr);
}

void Graphics::renderTexture(SDL_Texture *tex, int x, int y, SDL_Rect *clip = nullptr) {
    SDL_Rect dst;
    dst.x = x;
    dst.y = y;
    if (clip != nullptr){
        dst.w = clip->w;
        dst.h = clip->h;
    }
    else {
        SDL_QueryTexture(tex, NULL, NULL, &dst.w, &dst.h);
    }
    SDL_RenderCopy(renderer, tex, clip, &dst);
}

SDL_Texture* Graphics::renderText(const std::string &message, const std::string &filepath, SDL_Color colour, int fontsize) {
    //Open the font
    TTF_Font *font = TTF_OpenFont(filepath.c_str(), fontsize);
    if (font == nullptr){
        std::cerr << "TTF_OpenFont" << TTF_GetError;
        return nullptr;
    }
    //render to a surface as that's what TTF_RenderText returns
    SDL_Surface *surf = TTF_RenderText_Blended(font, message.c_str(), colour);
    if (surf == nullptr){
        TTF_CloseFont(font);
        std::cout << "TTF_RenderText" << TTF_GetError;
        return nullptr;
    }
    //load that surface into texture
    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surf);
    if (texture == nullptr){
        std::cout << "CreateTexture" << SDL_GetError();
    }
    //Clean up surface and font
    SDL_FreeSurface(surf);
    TTF_CloseFont(font);
    return texture;
}

Tile.h

#ifndef Tile_h
#define Tile_h
#include <SDL2/SDL.h>

class Tile {
public:
    Tile(SDL_Rect r, int c, int t) :
    tilePosition{r}, currentNumber{c}, tiletype{t} {}

    void swap(Tile& t);
    void setTileType(int n)   { tiletype = n; }
    void setTileShift(const int& x, const int& y) { tilePosition.x += x; tilePosition.y += y; }

    SDL_Rect position() const { return tilePosition; }
    int posNumber()     const { return currentNumber; }
    int tileType()      const { return tiletype; }

    enum type { button = 0, rollover, shadow, invisible, buttonpressed};

private:
    SDL_Rect tilePosition;
    int currentNumber;
    int tiletype;
};

#endif
\$\endgroup\$
1
  • \$\begingroup\$ You have some copy&pastes that you could improve, i will explain later \$\endgroup\$
    – amchacon
    Nov 2, 2016 at 10:42

1 Answer 1

3
\$\begingroup\$

Overall it is good quality. I only have these complaints:

MainGame::~MainGame() {
    Mix_Quit();
    game     = NULL;
    graphics = NULL;
    click    = NULL;
    cat1     = NULL;
    cat2     = NULL;
    cat3     = NULL;
}

The indentation of these expressions can be dangerous. People may see the NULL value, but ignore the variable's name.

I use this indentation instead:

MainGame::~MainGame() {
    Mix_Quit();
    game = NULL;
    graphics = NULL;
    click = NULL;
    cat1 = NULL;
    cat2 = NULL;
    cat3 = NULL;
}

Variable's names stand out more now.

Speaking about NULL, C++11 introduces the nullptr. You should use that and not the NULL macro:

MainGame::~MainGame() {
        Mix_Quit();
        game = nullptr;
        graphics = nullptr;
        click = nullptr;
        cat1 = nullptr;
        cat2 = nullptr;
        cat3 = nullptr;
    }

Moving to another point:

bool MainGame::init(Graphics* graph, Game* g) {
    game     = g;
    graphics = graph;
    int shiftamount = 3;

    // Fill vector<SDL_Rect> 'positions' with possible positions of n*n tiles & make shadow positions
    loadPositions(positions, graphics->gridSize());
    loadPositions(shadowPositions, graphics->gridSize(), shiftamount);
    // Assign these starting positions to n*n tiles in vector<Tile> 'tiles' & make tile shadows
    makeTiles(tiles, positions, Tile::type::button);
    makeTiles(shadowTiles, shadowPositions, Tile::type::shadow);

    // Audio loading
    Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
    click = Mix_LoadWAV("assets/hover.wav");
    if( click == NULL ) {
        std::cerr << "Failed to load beat 'click', error:" << Mix_GetError() << std::endl;
        return false;
    }
    cat1 = Mix_LoadWAV("assets/meow1.wav");
    if( cat1 == NULL ) {
        std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
        return false;
    }
    cat2 = Mix_LoadWAV("assets/meow2.wav");
    if( cat2 == NULL ) {
        std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
        return false;
    }
    cat3 = Mix_LoadWAV("assets/meow3.wav");
    if( cat3 == NULL ) {
        std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
        return false;
    }

    return true;
}

This function is really long and has duplicated code.

Let's start at the beginning: Refactoring the work in sub-functions:

   bool MainGame::init(Graphics* graph, Game* g) {
        game     = g;
        graphics = graph;
        int shiftamount = 3;

        loadPositions();
        makeTiles();
        return loadAudio();
    }

    void MainGame::loadPositions()
    {
        loadPositions(positions, graphics->gridSize());
        loadPositions(shadowPositions, graphics->gridSize(), shiftamount);
    }

    void MainGame::makeTiles()
    {
        makeTiles(tiles, positions, Tile::type::button);
        makeTiles(shadowTiles, shadowPositions, Tile::type::shadow);
    }

    bool MainGame::loadAudio() {
        // Audio loading
        Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
        click = Mix_LoadWAV("assets/hover.wav");
        if( click == NULL ) {
            std::cerr << "Failed to load beat 'click', error:" << Mix_GetError() << std::endl;
            return false;
        }
        cat1 = Mix_LoadWAV("assets/meow1.wav");
        if( cat1 == NULL ) {
            std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
            return false;
        }
        cat2 = Mix_LoadWAV("assets/meow2.wav");
        if( cat2 == NULL ) {
            std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
            return false;
        }
        cat3 = Mix_LoadWAV("assets/meow3.wav");
        if( cat3 == NULL ) {
            std::cerr << "Failed to load beat 'meow', error:" << Mix_GetError() << std::endl;
            return false;
        }

        return true;
    }

loadAudio() has a lot duplicated code. Just write a function and call it:

bool MainGame::loadAudio() {
        // Audio loading
        Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);

        if (!loadWav(click,"assets/hover.wav"))
           return false;
        if (!loadWav(cat1,",assets/meow1.wav"))
           return false;
        // And so on...

        return true;
    }

    //  I don't know what handler type is, so i will do a template
    template<typename T>
    bool loadWav(T& handler,const char* route)
    {
        handler = Mix_LoadWAV("assets/hover.wav");
        if(handler == NULL ) {
           std::cerr << "Failed to load " << route <<, error:" << Mix_GetError() << std::endl;
        return false;
    }
        return true;
    }

You have some similar problems in Graphics and Gamestate_menu.

And in last:

void Graphics::setup() {
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
        std::cerr << "Error: init" << SDL_GetError() << std::endl;

    else {
        window = SDL_CreateWindow("Sliding Puzzle", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
        if (window == nullptr)
            std::cerr << "Error: Create window" << SDL_GetError() << std::endl;

        else {
            renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
            if (renderer == nullptr)
                std::cerr << "Error: Create renderer" << SDL_GetError() << std::endl;

            else {
                if( TTF_Init() == -1 )
                    std::cerr << "SDL_ttf could not initialise" << TTF_GetError() << std::endl;
                else {
                    cat = IMG_LoadTexture(renderer, "assets/cat2.png");
                    if (cat == nullptr)
                        std::cerr << "Error: Load cat texture";
                }
            }
        }
    }
}

This if/else structure looks ugly. Maybe you could do ifs and interrupt the execution with a return:

if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
   std::cerr << "Error: init" << SDL_GetError() << std::endl;
   return;
}

window = SDL_CreateWindow("Sliding Puzzle", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
if (window == nullptr)
{
     std::cerr << "Error: Create window" << SDL_GetError() << std::endl;
     return;
}

renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (renderer == nullptr)
{
   std::cerr << "Error: Create renderer" << SDL_GetError() << std::endl;
   return;
}

// And so son...
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.