4
\$\begingroup\$

I've already made a version of this game which was supposed to be played on the console. I got it reviewed and applied almost all of the recommendations I received in that topic. Thus you can consider this topic a follow-up from this one. This would be the first time I got in touch with SFML. I must mention that all the user interactions are still going through the console.

Most of the files remained the same from the original question (with the recommendations applied), so I will post only the new files/parts that have changed as those concern me the most. However, I've got the project hosted on GitHub if you're interested in other aspects.

CardsTexturesHolder.hpp

#pragma once
#include <unordered_map>
#include <filesystem>

#include <SFML/Graphics/Texture.hpp>

#include "Exceptions.hpp"
#include "Card.hpp"

namespace std
{
    template <>
    struct hash<Card>
    {
        constexpr std::size_t operator()(Card const& card) const noexcept
        {
            return (static_cast<int>(card.rank()) + static_cast<int>(card.suit()));
        }
    };
}

class CardsTexturesHolder
{
public:
    template <typename Container>
    CardsTexturesHolder(Container const& cardIdentifiers, std::string_view path);

    template <typename Iterator>
    CardsTexturesHolder(Iterator first, Iterator last, std::string_view path);

    const sf::Texture& operator[](Card const& card) const noexcept;

private:
    std::unordered_map<Card, sf::Texture> _textures;

    template <typename Container>
    void load(Container const& cardIdentifiers, std::string_view path);

    template <typename Iterator>
    void load(Iterator first, Iterator last, std::string_view path);
};

template <typename Container>
CardsTexturesHolder::CardsTexturesHolder(Container const& cardIdentifiers, std::string_view path)
{
    load(cardIdentifiers, path);
}

template <typename Iterator>
CardsTexturesHolder::CardsTexturesHolder(Iterator first, Iterator last, std::string_view path)
{
    load(first, last, path);
}

template <typename Iterator>
void CardsTexturesHolder::load(Iterator first, Iterator last, std::string_view path)
{
    sf::Texture texture;
    auto constexpr iteratorAdvanceOffset{ 1 };

    for (auto const& file : std::filesystem::directory_iterator(path))
    {
        if (texture.loadFromFile(file.path().string()))
        {
            _textures[*first] = texture;
            std::advance(first, iteratorAdvanceOffset);
        }
        else
            throw CouldNotLoadTextureException("A certain card texture could not be loaded. Consider checking the textures directory.");
    }
}

template <typename Container>
void CardsTexturesHolder::load(Container const& cardIdentifiers, std::string_view path)
{
    sf::Texture texture;
    auto currentCardIterator = std::begin(cardIdentifiers);
    auto constexpr iteratorAdvanceOffset{ 1 };

    for (auto const& file : std::filesystem::directory_iterator(path))
    {
        if (texture.loadFromFile(file.path().string()))
        {
            _textures[*currentCardIterator] = texture;
            std::advance(currentCardIterator, iteratorAdvanceOffset);
        }
        else
            throw CouldNotLoadTextureException("A certain card texture could not be loaded. Consider checking the textures directory.");
    }
}

CardsTexturesHolder.cpp

#include "CardsTexturesHolder.hpp"

const sf::Texture& CardsTexturesHolder::operator[](Card const& card) const noexcept
{
    auto const it = _textures.find(card);
    return it->second;
}

There are 2 aspects of this class that I don't like.

  1. The Cards in the cardIdentifiers container must have a strict ordering that matches the one in the Textures folder (see it on GitHub). If one changes any card's place in that container, the textures won't be corresponding to the actual card anymore.
  2. I had to name all the textures in such a way that the folder will be traversed in alphabetical order. If one changes the name of a texture file, everything will be messed up.

FontsHolder.hpp

#pragma once
#include <SFML/Graphics/Font.hpp>

#include <unordered_map>

class FontsHolder
{
public:

    explicit FontsHolder(std::string_view path);

    const sf::Font& operator[](std::string const& fontName) const;

private:

    const std::string extractFileName(std::string const& filePath) const noexcept;

    void loadFonts(std::string_view path);

    std::unordered_map<std::string, sf::Font> _fonts;
};

FontsHolder.cpp

#include "FontsHolder.hpp"
#include "Exceptions.hpp"
#include <algorithm>
#include <filesystem>

FontsHolder::FontsHolder(std::string_view path)
{
    loadFonts(path);
}

const sf::Font& FontsHolder::operator[](std::string const& fontName) const
{
    auto const it = _fonts.find(fontName);
    if (it != _fonts.cend())
        return it->second;
    else
        throw FontNotFoundException("The requested font was not found.");
}

const std::string FontsHolder::extractFileName(std::string const& filePath) const noexcept
{
    auto constexpr newDirectoryIdentifier = '\\';
    auto constexpr fileIdentifier = '.';

    auto const lastPositionOfDirectoryIdentifier = filePath.find_last_of(newDirectoryIdentifier);
    auto const firstPositionOfFileIdentifier = filePath.find_first_of(fileIdentifier);

    return std::string(std::next(filePath.cbegin(),lastPositionOfDirectoryIdentifier + 1), std::next(filePath.cbegin(), firstPositionOfFileIdentifier));
}

void FontsHolder::loadFonts(std::string_view path)
{
    sf::Font font;
    std::string filePath;
    for (auto const& file : std::filesystem::directory_iterator(path))
    {
        filePath = file.path().string();

        if (font.loadFromFile(filePath))
            _fonts[extractFileName(filePath)] = font;
        else
            throw CouldNotLoadFontException("A certain font could not be loaded. Consider checking the textures directory.");
    }
}

Renderer.hpp

#pragma once
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/Sprite.hpp>

#include "FontsHolder.hpp"
#include "CardsTexturesHolder.hpp"

#include "Deck.hpp"

#include <vector>

class Renderer
{
public:
    template <typename Container>
    Renderer(Container const& cardsIdentifiers, std::string_view pathToTextures, std::string_view pathToFonts);

    template <typename Iterator>
    Renderer(Iterator first, Iterator last, std::string_view pathToTextures, std::string_view pathToFonts);


    template <typename Container>
    void drawPlayerCards(sf::RenderTarget& target, Container const& cards) const;

    void drawPileTopCard(sf::RenderTarget& target, Card const& card) const;

    void drawEscapeText(sf::RenderTarget& target, std::string const& style) const;

    void drawPlayerName(sf::RenderTarget& target, std::string const& name, sf::Vector2f position, std::string const& style) const;

    void drawWinner(sf::RenderTarget& target, std::string const& outputData, std::string const& style) const;

    void drawInformationAboutSpecialCard(sf::RenderTarget& target, std::string const& information, sf::Vector2f position, std::string const& style) const;

private:
    CardsTexturesHolder _textures;
    FontsHolder _fonts;
};

template<typename Container>
inline Renderer::Renderer(Container const & cardsIdentifiers, std::string_view pathToTextures, std::string_view pathToFonts)
    : _textures(cardsIdentifiers, pathToTextures),
    _fonts(pathToFonts)
{}

template<typename Iterator>
inline Renderer::Renderer(Iterator first, Iterator last, std::string_view pathToTextures, std::string_view pathToFonts)
    : _textures(first, last, pathToTextures),
    _fonts(pathToFonts)
{}

template <typename Container>
void Renderer::drawPlayerCards(sf::RenderTarget & target, Container const & cards) const
{
    auto const defaultScaleValues = sf::Vector2f(0.2f, 0.2f);

    auto constexpr xDirectionOffset{ 135 };
    auto constexpr yDirectionOffset{ 220 };

    sf::Sprite cardSprite;

    auto constexpr initialValueForX{ 0 };
    auto constexpr initialValueForY{ 250 };

    float x{ initialValueForX };
    float y{ initialValueForY };

    for (auto const& card : cards)
    {
        cardSprite.setTexture(_textures[card]);
        cardSprite.setPosition(x, y);
        cardSprite.setScale(defaultScaleValues);

        target.draw(cardSprite);

        x += xDirectionOffset;

        if (x + cardSprite.getGlobalBounds().width > target.getSize().x)
        {
            x = initialValueForX;
            y += yDirectionOffset;
        }
    }
}

Renderer.cpp

#include "Renderer.hpp"
#include <SFML/Graphics/Text.hpp>

void Renderer::drawPileTopCard(sf::RenderTarget& target, Card const& card) const
{
    sf::Vector2f const defaultScaleValues(0.2f, 0.2f);
    sf::Sprite cardSprite{ _textures[card] };

    cardSprite.setScale(defaultScaleValues);

    auto constexpr yDirectionOffset{ 1 };
    cardSprite.setPosition(sf::Vector2f(target.getSize().x / 2, yDirectionOffset));
    target.draw(cardSprite);
}

void Renderer::drawPlayerName(sf::RenderTarget & target, std::string const& name, sf::Vector2f position, std::string const& style) const
{
    sf::Text playerName;

    playerName.setFont(_fonts[style]);
    playerName.setString(name);
    playerName.setPosition(position);

    target.draw(playerName);
}

void Renderer::drawEscapeText(sf::RenderTarget & target, std::string const & style) const
{
    static std::string const outputData{ "Press Enter to continue..." };
    sf::Text output;

    output.setFont(_fonts[style]);
    output.setString(outputData);

    static const auto targetSize = target.getSize();
    static const auto point = sf::Vector2f(10, 10);
    output.setPosition(point);

    target.draw(output);
}

void Renderer::drawWinner(sf::RenderTarget & target, std::string const & outputData, std::string const & style) const
{
    int constexpr characterSize{ 60 };
    sf::Text output;

    output.setFont(_fonts[style]);
    output.setString(outputData);
    output.setCharacterSize(characterSize);

    static auto const targetSize = target.getSize();
    static auto const middlePoint = sf::Vector2f(targetSize.x / 2, targetSize.y / 2);
    output.setPosition(middlePoint);

    target.draw(output);
}

void Renderer::drawInformationAboutSpecialCard(sf::RenderTarget& target, std::string const& information, sf::Vector2f position, std::string const& style) const
{
    sf::Text specialCardInformation;

    specialCardInformation.setFont(_fonts[style]);
    specialCardInformation.setString(information);
    specialCardInformation.setPosition(position);

    target.draw(specialCardInformation);
}

VisualGame.hpp

#pragma once
#include <SFML/Graphics/RenderWindow.hpp>

#include "Player.hpp"
#include "FontsHolder.hpp"
#include "Renderer.hpp"

#include <vector>

std::string const defaultWindowName{ "MacaoGame" };

class VisualGame
{
public:
    template <typename InputContainer>
    VisualGame(InputContainer&& players, std::string_view pathToCardTextures
    , std::string_view pathToFonts, std::size_t width, std::size_t height);

    template <typename InputIterator>
    VisualGame(InputIterator first, InputIterator last, 
        std::string_view pathToCardTextures, std::size_t width, std::size_t height);

    void run();

private:

    constexpr void validateNumberOfPlayers() const;

    constexpr bool areCompatible(Card const& left, Card const& right) const noexcept;

    void prepareGame() noexcept;

    void dealInitialCardsToEachPlayer() noexcept;

    void receiveCardsFromDeck(Player& player, std::size_t numberOfCards);

    void putCardToPile(Player& player, std::size_t cardNumber) noexcept;

    bool isSpecial(Card const& card) const noexcept;

    void printInformationAboutThePlayerAndTheTopCardFromPile(Player const& player) noexcept;

    void printInformationAboutThePlayerAndTheTopCardFromPile(Player const& player, Rank rank) noexcept;

    void normalCardPlayerTurn(Player& player);

    void specialCardPlayerTurn(Player& player);

    std::unique_ptr<Player> findWinner() const noexcept;

    auto receiveAndValidateInput() const noexcept->int;

    auto checkBounds(Player const& player, int cardNumber) const noexcept->int;

    void validatePlayerCardCompatibilityWithPileTopCard(Player const& player, int& cardNumber) const noexcept;

    sf::RenderWindow _mainWindow;

    Renderer _renderer;

    std::vector<std::unique_ptr<Player>> _players;

    Deck _deck;

    Pile _pile;

    bool _gameOver;
};

inline constexpr void VisualGame::validateNumberOfPlayers() const
{
    static constexpr auto minimumNumberOfPlayers{ 2 };
    static constexpr auto maximumNumberOfPlayers{ 9 };

    auto const numberOfPlayers = std::distance(_players.cbegin(), _players.cend());

    if (numberOfPlayers < minimumNumberOfPlayers || numberOfPlayers > maximumNumberOfPlayers)
        throw InvalidNumberOfPlayersException("The minimum number of players is 2, while the maximum is 9.");
}

template<typename InputContainer>
inline VisualGame::VisualGame(InputContainer && players, std::string_view pathToCardTextures, 
    std::string_view pathToFonts, std::size_t width, std::size_t height)
    : _players(std::move(players)),
    _renderer(defaultDeck, pathToCardTextures, pathToFonts),
    _mainWindow(sf::VideoMode(width, height), defaultWindowName),
    _gameOver(false)
{
    validateNumberOfPlayers();
}

template<typename InputIterator>
inline VisualGame::VisualGame(InputIterator first, InputIterator last,
    std::string_view pathToCardTextures, std::size_t width, std::size_t height)
    :_players(std::make_move_iterator(std::begin(players)), std::make_move_iterator(std::end(players))),
    _renderer(vectorDefaultDeck, pathToCardTextures, pathToFonts),
    _mainWindow(sf::VideoMode(width, height), defaultWindowName),
    _gameOver(false)
{
    validateNumberOfPlayers();
}

VisualGame.cpp

#include <SFML/Graphics.hpp>

#include "VisualGame.hpp"
#include "Exceptions.hpp"

#include <iostream>
#include <cctype>

auto const defaultBackgroundColor = sf::Color::Color(51, 51, 255);
std::string const defaultFont{ "FredokaOne" };

namespace
{
    constexpr auto specialCards = std::array
    {
        Card{ Rank::Two, Suit::Clubs },
        Card{ Rank::Two, Suit::Diamonds },
        Card{ Rank::Two, Suit::Spades },
        Card{ Rank::Two, Suit::Hearts },

        Card{ Rank::Three, Suit::Clubs },
        Card{ Rank::Three, Suit::Diamonds },
        Card{ Rank::Three, Suit::Spades },
        Card{ Rank::Three, Suit::Hearts },

        Card{ Rank::Ace, Suit::Diamonds },
        Card{ Rank::Ace, Suit::Clubs },
        Card{ Rank::Ace, Suit::Spades },
        Card{ Rank::Ace, Suit::Hearts },
    };
}

constexpr bool VisualGame::areCompatible(Card const& left, Card const& right) const noexcept
{
    return left.rank() == right.rank() || left.suit() == right.suit();
}

void VisualGame::prepareGame() noexcept
{
    _deck.shuffle();
    dealInitialCardsToEachPlayer();
    _pile.add(_deck.deal());

    while (isSpecial(_pile.topCard()))
    {
        _deck.add(_pile.deal());
        _deck.shuffle();
        _pile.add(_deck.deal());
    }
}

void VisualGame::dealInitialCardsToEachPlayer() noexcept
{
    static constexpr auto numberOfCards{ 5 };
    static std::vector<Card> playerCards;

    for (auto const& player : _players)
    {
        playerCards = _deck.deal(numberOfCards);
        player->_hand.add(playerCards.cbegin(), playerCards.cend());
    }
}

void VisualGame::printInformationAboutThePlayerAndTheTopCardFromPile(const Player& player) noexcept
{
    _mainWindow.clear(defaultBackgroundColor);

    _renderer.drawPileTopCard(_mainWindow, _pile.topCard());
    _renderer.drawPlayerName(_mainWindow, player.name(), sf::Vector2f(10, 10), defaultFont);
    _renderer.drawPlayerCards(_mainWindow, player._hand.cards());

    _mainWindow.display();
}

void VisualGame::printInformationAboutThePlayerAndTheTopCardFromPile(Player const & player, Rank rank) noexcept
{
    static auto const cardInformationPosition = sf::Vector2f(500, 1);
    static auto const playerNamePosition = sf::Vector2f(10, 10);
    static auto const specialCardInformationPosition = sf::Vector2f(0, _mainWindow.getSize().x / 2);
    switch (rank)
    {
    case Rank::Two:
    {
        _mainWindow.clear(defaultBackgroundColor);

        _renderer.drawPileTopCard(_mainWindow, _pile.topCard());
        _renderer.drawPlayerName(_mainWindow, player.name(), playerNamePosition, defaultFont);

        static const std::string cardInformation{ "This is 2 card and you will need to receive 2 cards from the deck.\n"
            "If you have a 4 card that has the same suit, then you can stop it.\n" };

        _renderer.drawInformationAboutSpecialCard(_mainWindow, cardInformation, specialCardInformationPosition, defaultFont);
        _renderer.drawPlayerCards(_mainWindow, player._hand.cards());

        _mainWindow.display();
        break;
    }
    case Rank::Three:
    {
        _mainWindow.clear(defaultBackgroundColor);

        _renderer.drawPileTopCard(_mainWindow, _pile.topCard());
        _renderer.drawPlayerName(_mainWindow, player.name(), playerNamePosition, defaultFont);

        static const std::string cardInformation{ "This is 3 card and you will need to receive 3 cards from the deck.\n"
            "If you have a 4 card that has the same suit, then you can stop it.\n" };

        _renderer.drawInformationAboutSpecialCard(_mainWindow, cardInformation, specialCardInformationPosition, defaultFont);
        _renderer.drawPlayerCards(_mainWindow, player._hand.cards());

        _mainWindow.display();

        break;
    }
    //case Rank::Seven:
    //{
    //  _renderer.draw(_mainWindow, _pile.topCard());
    //  _renderer.draw(_mainWindow, player._hand.cards());
    //  std::cout << "\nTop card from pile: " << _pile.top() << "\nThis is a 7 card and you will need to put down a card that has the specified suit. If you have a Joker, then"
    //      << " you can put it. Enter 0 if you don't have such a compatible card.\n" << player << "\n";
    //  break;
    //}
    case Rank::Ace:
    {
        _mainWindow.clear(defaultBackgroundColor);

        _renderer.drawPileTopCard(_mainWindow, _pile.topCard());
        _renderer.drawPlayerName(_mainWindow, player.name(), sf::Vector2f(10, 10), "FredokaOne");

        _renderer.drawPlayerCards(_mainWindow, player._hand.cards());

        _mainWindow.display();

        break;
    }
    }
}

void VisualGame::validatePlayerCardCompatibilityWithPileTopCard(Player const& player, int& cardNumber) const noexcept
{
    while (!areCompatible(player._hand.get(cardNumber), _pile.topCard()))
    {
        std::cout << "This card is incompatible.\nEnter another card or enter 0 to skip your turn.\n";

        cardNumber = receiveAndValidateInput();

        if (!cardNumber)
            break;
    }
}

std::unique_ptr<Player> VisualGame::findWinner() const noexcept
{
    auto const it = std::find_if(_players.cbegin(), _players.cend(),
        [](auto const& player) { return !player->_hand.numberOfCards(); });

    if (it == _players.cend())
        return std::make_unique<Player>(nullptr);

    return std::make_unique<Player>(**it);
}

auto VisualGame::checkBounds(Player const& player, int cardNumber) const noexcept->int
{
    static constexpr auto lowerBound{ 0 };

    while (cardNumber < lowerBound || cardNumber > player._hand.numberOfCards())
    {
        std::cout << "That card doesn't exist. You can enter 0 to skip your turn.\n";
        std::cin >> cardNumber;
    }

    return cardNumber;
}

auto VisualGame::receiveAndValidateInput() const noexcept->int
{
    std::string cardNumber{};
    std::getline(std::cin, cardNumber);

    while (!std::all_of(cardNumber.cbegin(), cardNumber.cend(), [](auto const character) {return std::isdigit(character); })
        || cardNumber.empty())
    {
        std::cout << "Empty input/Not an integer.\n";
        std::getline(std::cin, cardNumber);
    }

    return std::stoi(cardNumber);
}


void VisualGame::specialCardPlayerTurn(Player& player)
{
    static constexpr auto neededCardsToPickForTwo{ 2 };
    static constexpr auto neededCardsToPickForThree{ 3 };

    int cardNumber{ 0 };

    switch (_pile.topCard().rank())
    {
    case Rank::Two:
    {
        printInformationAboutThePlayerAndTheTopCardFromPile(player, Rank::Two);
        int cardNumber = receiveAndValidateInput();
        cardNumber = checkBounds(player, cardNumber);

        if (!cardNumber)
            receiveCardsFromDeck(player, neededCardsToPickForTwo);
        else if (player._hand.get(cardNumber).rank() == Rank::Four && areCompatible(player._hand.get(cardNumber), _pile.topCard()))
            putCardToPile(player, cardNumber);
        else
        {
            receiveCardsFromDeck(player, neededCardsToPickForTwo);
            /*  validatePlayerCardCompatibilityWithPileTopCard(player, cardNumber);
            if (!cardNumber)

            else
            putCardToPile(player, cardNumber);*/
        }
        break;
    }

    case Rank::Three:
    {
        printInformationAboutThePlayerAndTheTopCardFromPile(player, Rank::Three);
        int cardNumber = receiveAndValidateInput();
        cardNumber = checkBounds(player, cardNumber);

        if (!cardNumber)
            receiveCardsFromDeck(player, neededCardsToPickForThree);
        else if (player._hand.get(cardNumber).rank() == Rank::Four && areCompatible(player._hand.get(cardNumber), _pile.topCard()))
            putCardToPile(player, cardNumber);
        else
        {
            receiveCardsFromDeck(player, neededCardsToPickForThree);
            /*      validatePlayerCardCompatibilityWithPileTopCard(player, cardNumber);
            if (!cardNumber)

            else
            putCardToPile(player, cardNumber);*/
        }
        break;
    }
    case Rank::Ace:
    {
        printInformationAboutThePlayerAndTheTopCardFromPile(player, Rank::Ace);
        break;
    }
    }

    if (!player._hand.numberOfCards())
        _gameOver = true;
}

void VisualGame::normalCardPlayerTurn(Player& player)
{
    static constexpr auto defaultNumberOfCardsToPick{ 1 };

    printInformationAboutThePlayerAndTheTopCardFromPile(player);
    int cardNumber = receiveAndValidateInput();
    cardNumber = checkBounds(player, cardNumber);

    if (!cardNumber)
        receiveCardsFromDeck(player, defaultNumberOfCardsToPick);
    else
    {
        validatePlayerCardCompatibilityWithPileTopCard(player, cardNumber);
        cardNumber = checkBounds(player, cardNumber);
        if (!cardNumber)
            receiveCardsFromDeck(player, defaultNumberOfCardsToPick);
        else
            putCardToPile(player, cardNumber);
    }

    if (!player._hand.numberOfCards())
        _gameOver = true;
}

void VisualGame::run()
{
    prepareGame();

    Card lastCard{ _pile.topCard() };
    bool specialCardHadEffect{ false };
    while (_mainWindow.isOpen())
    {
        sf::Event event;
        while (_mainWindow.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                _mainWindow.close();            
        }

        for (auto& currentPlayer : _players)
        {
            if (!isSpecial(_pile.topCard()))
                normalCardPlayerTurn(*currentPlayer);
            else
            {
                if (isSpecial(_pile.topCard()) && !specialCardHadEffect)
                {
                    specialCardPlayerTurn(*currentPlayer);
                    specialCardHadEffect = true;
                }
                else
                    normalCardPlayerTurn(*currentPlayer);
            }

            if (_pile.topCard() != lastCard)
            {
                specialCardHadEffect = false;
                lastCard = _pile.topCard();
            }

            if (_gameOver)
                break;
        }

        while (_gameOver)
        {
            _mainWindow.clear(defaultBackgroundColor);
            _renderer.drawWinner(_mainWindow, findWinner()->name() + " wins!", defaultFont);
            _renderer.drawEscapeText(_mainWindow, defaultFont);
            _mainWindow.display();

            sf::Event event;
            if (_mainWindow.waitEvent(event))
            {
                if(sf::Keyboard::isKeyPressed(sf::Keyboard::Enter))
                    _mainWindow.close();
            }
        }

    }
}

And the driver code:

SFML.cpp

#include "VisualGame.hpp"
#include <iostream>

using vectorOfPlayers = std::vector<std::unique_ptr<Player>>;
using paths = std::pair<std::string, std::string>;

vectorOfPlayers getPlayers() noexcept
{
    vectorOfPlayers players;

    std::cout << "Enter players.\n"
        "\t* Enter an empty name when done adding players.\n";

    for (auto name = std::string{}; std::getline(std::cin, name); )
    {
        if (name == "")
            break;

        players.push_back(std::make_unique<Player>(std::move(name)));
    }

    return players;
}

paths getPaths() noexcept
{
    std::cout << "Enter the path to the textures folder.\n";
    std::string texturesPath;
    std::getline(std::cin, texturesPath);

    std::cout << "Enter the path to the fonts folder.\n";
    std::string fontsPath;
    std::getline(std::cin, fontsPath);

    return std::make_pair(texturesPath, fontsPath);
}

int main()
{
    while (true)
    {
        try
        {
            int const monitorWidth = sf::VideoMode::getDesktopMode().width;
            int const monitorHeight = sf::VideoMode::getDesktopMode().height;

            vectorOfPlayers players = getPlayers();
            paths resourcePaths = getPaths();

            //constexpr std::string_view firstPath("G:/Visual Studio projects/MacaoGame/Debug/Textures");
            //constexpr std::string_view secondPath("G:/Visual Studio projects/MacaoGame/Debug/Fonts");

            VisualGame myVisualGame(players, resourcePaths.first, resourcePaths.second, monitorWidth, monitorHeight);
            myVisualGame.run();
            break;
        }
        catch (CouldNotLoadTextureException const& e)
        {
            std::cerr << e.what() << '\n';
            return -1;
        }
        catch (CouldNotLoadFontException const& e)
        {
            std::cerr << e.what() << '\n';
            return -1;
        }
        catch (InvalidNumberOfPlayersException const& e)
        {
            std::cerr << e.what() << '\n';
            return -1;
        }
    }
    return 0;
}

Also, what I don't like here is that I need to give absolute paths, otherwise std::filesystem::directory_iterator will throw an exception.

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

A few random, disconnected thoughts:

Reading Card Textures

You say:

There are 2 aspects of this class that I don't like.

  1. The Cards in the cardIdentifiers container must have a strict ordering that matches the one in the Textures folder (see it on GitHub). If one changes any card's place in that container, the textures won't be corresponding to the actual card anymore.

  2. I had to name all the textures in such a way that the folder will be traversed in alphabetical order. If one changes the name of a texture file, everything will be messed up.

I agree this is less than ideal. Why not name the images based on their rank and suit? For example, instead of 11.png, why not name it 2clubs.png or something like that? Then you can match the rank with the image by parsing the name. If someone puts a new image in the directory, either it automatically gets matched up with the correct card, or you immediately know something's wrong and can display an error and bail (or whatever's appropriate).

Rendering

Your rendering code is sprinkled throughout your game logic. I think your architecture would be cleaner if instead you drew the current game state, then handled the user input and updated the game state. I'd have a function like drawCurrentGameState(), and call it at the start of the game loop. It would draw everything using the current state of the game. This would make it easier to change the drawing code in the future, since it would all be in one place.

Naming

I like most of your variable and function naming. However, these monstrosities have got to go:

void printInformationAboutThePlayerAndTheTopCardFromPile(Player const& player) noexcept;

void printInformationAboutThePlayerAndTheTopCardFromPile(Player const& player, Rank rank) noexcept;

First, you're not printing anything. You're drawing now, so I'd rename them to draw . Second, the names are simply too verbose. You don't need to say "InformationAboutThe". Anything related to the player that you're going to draw or print is information about the player. So how about just drawPlayerAndTopCard() or drawPlayerAndCardPile()?

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on. \$\endgroup\$
    – I. S.
    Commented Aug 5, 2018 at 12:22

Your Answer

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

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