Macau Card Game (with graphics)
Clash 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.
- 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. - 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.
c++ object-oriented game playing-cards sfml
add a comment |Â
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.
- 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. - 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.
c++ object-oriented game playing-cards sfml
add a comment |Â
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.
- 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. - 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.
c++ object-oriented game playing-cards sfml
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.
- 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. - 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.
c++ object-oriented game playing-cards sfml
edited Aug 3 at 5:03
Jamalâ¦
30.1k11114225
30.1k11114225
asked Aug 2 at 16:37
I. S.
9515
9515
add a comment |Â
add a comment |Â
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.
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.
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()
?
Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
â I. S.
yesterday
add a comment |Â
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.
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.
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()
?
Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
â I. S.
yesterday
add a comment |Â
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.
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.
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()
?
Thank you for the review. I like the fact that it addresses exactly what I've put emphasis on.
â I. S.
yesterday
add a comment |Â
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.
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.
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()
?
A few random, disconnected thoughts:
Reading Card Textures
You say:
There are 2 aspects of this class that I don't like.
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.
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()
?
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
add a comment |Â
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
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password