3
\$\begingroup\$

So after getting a very thorough review and feedback from indi in my previous attempt, SDL/C++ High-Low Guessing Game, I decided to go through the game and fix as many mistakes as I could. But going through the feedback also just opened up even more questions.

Things I was unable to do

  • Get rid of all global variables. I was able to remove or move most of the global variables, but I had an issue with the SDL_Window*, SDL_Renderer, and TTF_Font. It was suggested that I used a std::unique_ptr, so I made the window a std::unique_ptr, but I was unable to use that window as a parameter for creating the renderer because there was no suitable conversion from a std::unique_ptr to a SDL_Window*. Thus I just kept them in global until I can figure out how to make it work.

  • Use the struct "tags" for my LTexture class. It was suggested to use struct from_surface_tag {} from_surface; and struct from_text_tag {} from_text; to indicate which version of the constructor I was using. The compiler just kept saying that from_text or from_surface were undefined. I realize that there mainly for the reader of the code, so I just made an enum to use as the tags.

  • Used the state machine that was suggested. I make the engine that was suggested, but I failed miserably. Instead I created a very basic state machine that hopefully works and gets rid of the playAgain -> mainMenu -> playAgain loop. Hopefully I'll be able to understand how to other one once I get more experience.

  • Lastly, I did not make not make the LTexture class it's own file, mainly because I struggled getting the SDL one's to work.

Here is my code:

SDLInit.h

#pragma once

class sdl
{
public:
    sdl();

    sdl(sdl&& other) noexcept;


    ~sdl();

    auto operator=(sdl&& other) noexcept->sdl&;

    // Non-copyable
    sdl(sdl const&) = delete;
    auto operator=(sdl const&)->sdl& = delete;

    friend void _swap(sdl& a, sdl& b) noexcept;

private:

    bool _own = false;
};

class sdl_img
{
public:
    sdl_img();

    sdl_img(sdl_img&& other) noexcept;


    ~sdl_img();

    auto operator=(sdl_img&& other) noexcept->sdl_img&;

    // Non-copyable
    sdl_img(sdl_img const&) = delete;
    auto operator=(sdl_img const&)->sdl_img& = delete;

    friend void _swap(sdl_img& a, sdl_img& b) noexcept;

private:

    bool _own = false;
};

class sdl_ttf
{
public:
    sdl_ttf();

    sdl_ttf(sdl_ttf&& other) noexcept;


    ~sdl_ttf();

    auto operator=(sdl_ttf&& other) noexcept->sdl_ttf&;

    // Non-copyable
    sdl_ttf(sdl_ttf const&) = delete;
    auto operator=(sdl_ttf const&)->sdl_ttf& = delete;

    friend void _swap(sdl_ttf& a, sdl_ttf& b) noexcept;

private:

    bool _own = false;
};

SDLInit.cpp

#include <iostream>

#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>

#include "SDLInit.h"

//Initialize SDL
sdl::sdl()
{
    auto const result = SDL_Init(SDL_INIT_VIDEO);
    if (result != 0)
        std::cout << "SDL could not initialize";
}

sdl::sdl(sdl&& other) noexcept
{
    _swap(*this, other);
}

sdl::~sdl()
{
    if (_own)
        SDL_Quit();
}

auto sdl::operator=(sdl&& other) noexcept -> sdl&
{
    _swap(*this, other);
    return *this;
}

void _swap(sdl& a, sdl& b) noexcept
{
    using std::swap;
    swap(a._own, b._own);
}

//Initialize SDL Image
sdl_img::sdl_img()
{
    auto const result = IMG_Init(IMG_INIT_PNG);
    if (result == 0)
        std::cout << "SDL Image could not initialize";
}

sdl_img::sdl_img(sdl_img&& other) noexcept
{
    _swap(*this, other);
}

sdl_img::~sdl_img()
{
    if (_own)
        IMG_Quit();
}

auto sdl_img::operator=(sdl_img&& other) noexcept -> sdl_img&
{
    _swap(*this, other);
    return *this;
}

void _swap(sdl_img& a, sdl_img& b) noexcept
{
    using std::swap;
    swap(a._own, b._own);
}

//Initialize SDL True Type Fonts
sdl_ttf::sdl_ttf()
{
    auto const result = TTF_Init();
    if (result != 0)
        std::cout << "SDL TTF could not initialize";
}

sdl_ttf::sdl_ttf(sdl_ttf&& other) noexcept
{
    _swap(*this, other);
}

sdl_ttf::~sdl_ttf()
{
    if (_own)
        TTF_Quit();
}

auto sdl_ttf::operator=(sdl_ttf&& other) noexcept -> sdl_ttf&
{
    _swap(*this, other);
    return *this;
}

void _swap(sdl_ttf& a, sdl_ttf& b) noexcept
{
    using std::swap;
    swap(a._own, b._own);
}

Main.cpp

#include <iostream>
#include <random>
#include <string>

#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>

#include "SDLInit.h"

constexpr int SCREEN_WIDTH = 640;
constexpr int SCREEN_HEIGHT = 480;

SDL_Window* gWindow = nullptr;
SDL_Renderer* gRenderer = nullptr;
TTF_Font* gFont = nullptr;

enum class GameStates
{
    MAIN_MENU,
    MAIN_LOOP,
    PLAY_AGAIN,
    EXIT,
};

struct Game
{
    int numberofGuesses = 0;
    const int min = 1;
    int max = 0;
    int randomNumber = 0;
    bool game_was_won = false;
};

std::mt19937& random_engine()
{
    static std::mt19937 mersenne(std::random_device{}());
    return mersenne;
}

int getRandomNumber(int x, int y)
{
    std::uniform_int_distribution<> dist{ x,y };
    return dist(random_engine());
}

//used to tag how the texture was rendered
enum TextureType { from_surface, from_text };

class LTexture
{
public:
    LTexture(TextureType type, SDL_Renderer* renderer, std::string const& path);
    LTexture(TextureType type, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width);

    LTexture(LTexture&& other) noexcept
    {
        swap(*this, other);
    }

    ~LTexture();

    void render(SDL_Renderer* renderer, int x, int y);

    LTexture& operator=(LTexture&& other) noexcept
    {
        swap(*this, other);
        return *this;
    }

    SDL_Texture* mTexture = nullptr;
    int mWidth = 0;
    int mHeight = 0;
    SDL_Rect mButton = {};

    LTexture(LTexture const&) = delete;
    LTexture& operator=(LTexture const&) = delete;

    friend void swap(LTexture& a, LTexture& b) noexcept;
};

LTexture::LTexture(TextureType type, SDL_Renderer* renderer, std::string const& path)
{
    SDL_Surface *surface = IMG_Load(path.c_str());
    if (!surface)
        std::cout << "Failed to create surface";

    mTexture = SDL_CreateTextureFromSurface(gRenderer, surface);
    if (!mTexture)
        std::cout << "Failed to create texture";
    mWidth = surface->w;
    mHeight = surface->h;
}

LTexture::LTexture(TextureType type, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width)
{
    SDL_Surface* textSurface = TTF_RenderText_Blended_Wrapped(gFont, text.c_str(), color, width);
    if (!textSurface)
        std::cout << "Failed to create surface";

    mTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
    if (!mTexture)
        std::cout << "Failed to created texture";

    mWidth = textSurface->w;
    mHeight = textSurface->h;

    SDL_FreeSurface(textSurface);
}

void LTexture::render(SDL_Renderer* renderer, int x, int y)
{
    SDL_Rect destRect = { x, y, mWidth, mHeight };
    SDL_RenderCopy(renderer, mTexture, nullptr, &destRect);
    //create a rectangle that coincides with texture to check for button presses
    mButton = { x, y, mWidth, mHeight };
}

void swap(LTexture& a, LTexture& b) noexcept
{
    using std::swap;
    swap(a.mTexture, b.mTexture);
    swap(a.mWidth, b.mWidth);
    swap(a.mHeight, b.mHeight);
    swap(a.mButton, b.mButton);
}

LTexture::~LTexture()
{
    if (mTexture)
        SDL_DestroyTexture(mTexture);
}

bool button_is_pressed(SDL_Event const& event, SDL_Rect const& button_rect)
{
    if (event.type == SDL_MOUSEBUTTONDOWN)
    {
        auto const& mouse_button_event = event.button;

        auto const mouse_position = SDL_Point{ mouse_button_event.x, mouse_button_event.y };

        return (mouse_button_event.button == SDL_BUTTON_LEFT) && SDL_PointInRect(&mouse_position, &button_rect);
    }

    return false;
}

// compare random number to guess player provided
int compare(int randomNumber, int guess)
{
    if (randomNumber < guess)
    {
        return -1;
    }
    if (randomNumber > guess)
    {
        return 1;
    }

    return 0;
}

bool playAgain_input(GameStates& gameState, LTexture const& yesButton, LTexture const& noButton)
{
    SDL_Event e;

    while (SDL_PollEvent(&e) != 0)
    {
        if (e.type == SDL_QUIT || button_is_pressed(e, noButton.mButton))
        {
            gameState = GameStates::EXIT;
            return false;
        }

        if (button_is_pressed(e, yesButton.mButton))
        {
            gameState = GameStates::MAIN_MENU;
            return false;
        }
    }

    return true;
}

bool playAgain_update()
{
    return true;
}

bool playAgain_render(LTexture& highLowTexture, LTexture& winlose, LTexture& playAgain, LTexture& yesButton, LTexture& noButton)
{
    SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
    SDL_RenderClear(gRenderer);

    highLowTexture.render(gRenderer, 0, 0);
    winlose.render(gRenderer, 335, 70);
    playAgain.render(gRenderer, 325, 300);
    yesButton.render(gRenderer, 300, 350);
    noButton.render(gRenderer, 300 + yesButton.mWidth + 10, 350);

    SDL_RenderPresent(gRenderer);

    return true;
}
void playAgain(GameStates& gameState, Game game)
{
    SDL_Color const textColor = { 0,0,0 };
    auto const textWidth = 250;

    auto highLowTexture = LTexture(from_surface, gRenderer, "Resources/HiLo.png");
    auto playAgain = LTexture(from_text, gRenderer, "Play Again?", textColor, textWidth);
    auto yesButton = LTexture{ from_surface, gRenderer, "Resources/HiLoYes.png" };
    auto noButton = LTexture{ from_surface, gRenderer, "Resources/HiLoNo.png" };

    std::string dialogue;
    //change dialogue based on if player won or not
    if (game.game_was_won)
    {
        dialogue = "YOU WON!!! The correct answer was " + std::to_string(game.randomNumber) + ".";
    }
    else
    {
        dialogue = "You lose. The correct answer was " + std::to_string(game.randomNumber) + ".";
    }

    auto winlose = LTexture{ from_text, gRenderer, dialogue, textColor, textWidth };

    while (playAgain_input(gameState, yesButton, noButton) && playAgain_update() && playAgain_render(highLowTexture, winlose, playAgain, yesButton, noButton))
    {}
}

bool gameLoop_input(GameStates& gameState, std::string& guessInput, int& guess, int& guessCount)
{
    SDL_Event e;

    SDL_StartTextInput();

    while (SDL_PollEvent(&e) != 0)
    {
        if (e.type == SDL_QUIT)
        {
            gameState = GameStates::EXIT;
            return false;
        }
        if (e.type == SDL_TEXTINPUT)
        {
            //if input is a numeric value, add to string.
            if (e.text.text[0] == '0' || e.text.text[0] == '1' || e.text.text[0] == '2' || e.text.text[0] == '3' || e.text.text[0] == '4' ||
                e.text.text[0] == '5' || e.text.text[0] == '6' || e.text.text[0] == '7' || e.text.text[0] == '8' || e.text.text[0] == '9')
            {
                guessInput += e.text.text;
            }
        }
        if (e.type == SDL_KEYDOWN)
        {
            if (e.key.keysym.sym == SDLK_RETURN || e.key.keysym.sym == SDLK_KP_ENTER)
            {
                //if input is not empty
                if (guessInput != " ")
                {
                    //convert guess to int
                    guess = stoi(guessInput);
                    //reset string
                    guessInput = " ";
                    ++guessCount;
                }
            }
            else if (e.key.keysym.sym == SDLK_BACKSPACE && guessInput.length() > 0)
            {
                guessInput.pop_back();
            }
        }
    }

    SDL_StopTextInput();
    return true;
}

bool gameLoop_update(GameStates& gameState, std::string& dialogue, int guess, int guessCount, Game& game)
{
    if (guessCount == 0)
    {
        dialogue = "I'm thinking of a number between " + std::to_string(game.min) + " and " + std::to_string(game.max) + ". You have " + std::to_string(game.numberofGuesses) + " guesses.";
    }

    // if player ran out of moves
    else if (guessCount == game.numberofGuesses)
    {
        game.game_was_won = false;
        gameState = GameStates::PLAY_AGAIN;
        return false;
    }
    else
    {
        int cmp = compare(game.randomNumber, guess);

        //change dialogue based on comparison
        if (cmp > 0)
        {
            dialogue = "Your guess was too low.";
        }
        else if (cmp < 0)
        {
            dialogue = "Your guess was too high.";
        }
        else
        {
            game.game_was_won = true;
            gameState = GameStates::PLAY_AGAIN;
            return false;
        }
    }

    return true;
}

bool gameLoop_render(LTexture& highLowTexture, LTexture& guessPrompt, std::string guessInput, std::string dialogue, SDL_Color textColor, int textWidth, int guessCount, Game game)
{
    SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
    SDL_RenderClear(gRenderer);

    highLowTexture.render(gRenderer, 0, 0);
    guessPrompt.render(gRenderer, 350, 250);

    auto bubbleText = LTexture(from_text, gRenderer, dialogue, textColor, textWidth);
    bubbleText.render(gRenderer, 335, 70);

    auto guessCounter = LTexture(from_text, gRenderer, "Guesses remaining: " + std::to_string(game.numberofGuesses - guessCount), textColor, textWidth);
    guessCounter.render(gRenderer, 350, 200);

    auto inputTexture = LTexture(from_text, gRenderer, guessInput, textColor, textWidth);
    if (guessInput == "")
    {
        inputTexture = LTexture(from_text, gRenderer, " ", textColor, textWidth);
    }
    inputTexture.render(gRenderer, 350 + guessPrompt.mWidth, textWidth);

    SDL_RenderPresent(gRenderer);

    return true;
}

void gameLoop(GameStates& gameState, Game& game)
{
    std::string guessInput = " ";
    std::string dialogue = " ";

    SDL_Color const textColor = { 0,0,0 };
    auto const textWidth = 250;

    auto highLowTexture = LTexture(from_surface, gRenderer, "Resources/HiLo.png");
    auto guessPrompt = LTexture(from_text, gRenderer, "Enter a number:", textColor, textWidth);

    int guessCount = 0;
    int guess = 0;
    while (gameLoop_input(gameState, guessInput, guess, guessCount) &&
        gameLoop_update(gameState, dialogue, guess, guessCount, game) &&
        gameLoop_render(highLowTexture, guessPrompt, guessInput, dialogue, textColor, textWidth, guessCount, game))
    {}
}

bool mainMenu_input(GameStates& gameState, Game& game, LTexture& tenButton, LTexture& hundredButton, LTexture& thousandButton)
{
    SDL_Event e;

    while (SDL_PollEvent(&e) != 0)
    {
        if (e.type == SDL_QUIT)
        {
            gameState = GameStates::EXIT;
            return false;
        }
        //change game settings based on button
        if (button_is_pressed(e, tenButton.mButton))
        {
            game.numberofGuesses = 5;
            game.max = 10;
            game.randomNumber = getRandomNumber(game.min, game.max);
            gameState = GameStates::MAIN_LOOP;
            //used to make sure game works properly
            std::cout << game.randomNumber;
            return false;
        }
        else if (button_is_pressed(e, hundredButton.mButton))
        {
            game.numberofGuesses = 7;
            game.max = 100;
            game.randomNumber = getRandomNumber(game.min, game.max);
            gameState = GameStates::MAIN_LOOP;
            std::cout << game.randomNumber;
            return false;
        }
        else if (button_is_pressed(e, thousandButton.mButton))
        {
            game.numberofGuesses = 9;
            game.max = 1000;
            game.randomNumber = getRandomNumber(game.min, game.max);
            gameState = GameStates::MAIN_LOOP;
            std::cout << game.randomNumber;
            return false;
        }
    }
    return true;
}

bool mainMenu_update()
{
    return true;
}

bool mainMenu_render(LTexture& menuTexture, LTexture& tenButton, LTexture& hundredButton, LTexture& thousandButton)
{
    SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
    SDL_RenderClear(gRenderer);

    menuTexture.render(gRenderer, 0, 0);
    tenButton.render(gRenderer, (SCREEN_WIDTH / 2) - (tenButton.mWidth / 2), 175);
    hundredButton.render(gRenderer, (SCREEN_WIDTH / 2) - (tenButton.mWidth / 2), 225);
    thousandButton.render(gRenderer, (SCREEN_WIDTH / 2) - (tenButton.mWidth / 2), 275);

    SDL_RenderPresent(gRenderer);

    return true;
}

void mainMenu(GameStates& gameState, Game& game)
{
    auto menuTexture = LTexture(from_surface, gRenderer, "Resources/HiLoMenu.png");
    auto tenButton = LTexture(from_surface, gRenderer, "Resources/HiLo10.png");
    auto hundredButton = LTexture(from_surface, gRenderer, "Resources/HiLo100.png");
    auto thousandButton = LTexture(from_surface, gRenderer, "Resources/HiLo1000.png");

    while (mainMenu_input(gameState, game, tenButton, hundredButton, thousandButton) &&
        mainMenu_update() &&
        mainMenu_render(menuTexture, tenButton, hundredButton, thousandButton))
    {}
}

void init()
{
    auto sdlinit = sdl{};
    auto img = sdl_img{};
    auto ttf = sdl_ttf{};

    gWindow = SDL_CreateWindow("HiLo", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    if (gWindow == nullptr)
    {
        std::cout << "Failed to create window";
    }
    gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (gRenderer == nullptr)
    {
        std::cout << "Failed to create renderer";
    }
    gFont = TTF_OpenFont("Resources/opensans.ttf", 20);
    if (gFont == nullptr)
    {
        std::cout << "Failed to open font";
    }
}

void close()
{
    if (gFont)
    {
        TTF_CloseFont(gFont);
    }
    if (gWindow)
    {
        SDL_DestroyWindow(gWindow);
    }
    if (gRenderer)
    {
        SDL_DestroyRenderer(gRenderer);
    }
}

int main(int argc, char* args[])
{
    init();

    GameStates gameState = GameStates::MAIN_MENU;
    Game game;

    while (gameState != GameStates::EXIT)
    {
        switch (gameState)
        {
        case GameStates::MAIN_MENU:
            mainMenu(gameState, game);
            break;
        case GameStates::MAIN_LOOP:
            gameLoop(gameState, game);
            break;
        case GameStates::PLAY_AGAIN:
            playAgain(gameState, game);
            break;
        }
    }

    close();

    return 0;
}

I know I still have a lot to learn, but any additional feedback would be greatly appreciated.

I also have a few more questions:

  • I was previously told that my code looks very "C-ish". If it still is, what makes it "C-ish"? I probably just don't know enough about the difference between the two to really tell.

  • What is the difference between doing something like sdl sdlinit; and auto sdlinit = sdl{}? Additionally, how do you determine when to use auto and when to specify?

Thanks again for your time.

\$\endgroup\$
3
  • \$\begingroup\$ You should flag a moderator and request your accounts be merged \$\endgroup\$
    – Summer
    Commented Aug 1, 2018 at 4:02
  • \$\begingroup\$ Regarding auto usage, have a look at this quite good answer which hopefully makes things a bit clearer. \$\endgroup\$
    – yuri
    Commented Aug 1, 2018 at 6:17
  • \$\begingroup\$ The correct way of using smart ptr for Window & Renderer is : std::unique_ptr<SDL_Window, void(*)(SDL_Window*)> window(SDL_CreateWindow(...), SDL_DestroyWindow); std::unique_ptr<SDL_Renderer, void(*)(SDL_Renderer*)> renderer(SDL_CreateRenderer(window.get(), ...), SDL_DestroyRenderer); Then you use window.get() which returns SDL_Window*, and renderer.get() which returns SDL_Renderer*. \$\endgroup\$ Commented Aug 1, 2018 at 21:10

0

Your Answer

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

Browse other questions tagged or ask your own question.