Macau Card Game (with graphics)

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












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

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
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
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
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.







share|improve this question



























    up vote
    4
    down vote

    favorite












    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

    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
    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
    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
    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.







    share|improve this question























      up vote
      4
      down vote

      favorite









      up vote
      4
      down vote

      favorite











      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

      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
      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
      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
      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.







      share|improve this question













      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

      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
      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
      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
      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.









      share|improve this question












      share|improve this question




      share|improve this question








      edited Aug 3 at 5:03









      Jamal♦

      30.1k11114225




      30.1k11114225









      asked Aug 2 at 16:37









      I. S.

      9515




      9515




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          4
          down vote



          accepted










          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()?






          share|improve this answer























          • Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
            – I. S.
            yesterday










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f200833%2fmacau-card-game-with-graphics%23new-answer', 'question_page');

          );

          Post as a guest






























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          4
          down vote



          accepted










          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()?






          share|improve this answer























          • Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
            – I. S.
            yesterday














          up vote
          4
          down vote



          accepted










          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()?






          share|improve this answer























          • Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
            – I. S.
            yesterday












          up vote
          4
          down vote



          accepted







          up vote
          4
          down vote



          accepted






          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()?






          share|improve this answer















          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()?







          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited Aug 3 at 5:26


























          answered Aug 3 at 5:09









          user1118321

          10.1k11144




          10.1k11144











          • Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
            – I. S.
            yesterday
















          • Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
            – I. S.
            yesterday















          Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
          – I. S.
          yesterday




          Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
          – I. S.
          yesterday












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f200833%2fmacau-card-game-with-graphics%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation