Viewport for Card Match Game

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
4
down vote
favorite
This is the 2nd half of my Memory game. The first half can be found here. The repository can be found on Github.
I am using SFML 2.4.2. In this project I focused on a few key aspects of design and development.
- Top-down Design
- DRY & SRP
- Encapsulation & Abstraction
In an effort to eliminate magic numbers I shoved all of my constexpr as well as my enums, a static logger and a static PRNG in a single header file. I don't know if this approach is best practice or not and would appreciate input. On one hand I could move the expressions to the appropriate headers where they are localized, but many of them are used repeatedly throughout the codebase. Should I localize the values that are used once and only keep a header file for the reused items (PRNG, Enums, etc...)?
note: in the previous answer it was recommended to me to use double over float. I chose not to do this as SFML takes floats for its parameters and the number of casts made the code unreadable. I could let the computer narrow implicitly but then I would need to turn of wX which I prefer to keep on. It would also lead to a significant number of warnings.
Expressions.h
#ifndef BRUGLESCO_MEMORY_EXPRESSIONS_H
#define BRUGLESCO_MEMORY_EXPRESSIONS_H
#include <fstream>
#include <random>
namespace bruglesco
static std::random_device rd;
static std::mt19937 generator(rd());
static std::ofstream debuggStream("DebugLog.txt"); // remove for release
constexpr unsigned screen_width = 1500u;
constexpr unsigned screen_height = 800u;
constexpr float menu_button_width = 256.f;
constexpr float menu_button_height = 256.f;
constexpr float play_button_x = 622.f;
constexpr float play_button_y = 96.f;
constexpr float six_button_x = 95.f;
constexpr float eight_button_x = 446.f;
constexpr float twelve_button_x = 797.f;
constexpr float sixteen_button_x = 1148.f;
constexpr float pair_button_y = 448.f;
constexpr float play_string_x_offset = 18.f;
constexpr float play_string_y_offset = 28.f;
constexpr float six_string_x_offset = 80.f;
constexpr float eight_string_x_offset = 40.f;
constexpr float twelve_string_x_offset = 15.f;
constexpr float sixteen_string_x_offset = 8.f;
constexpr float pair_string_x_offset = 40.f;
constexpr float pair_string_y_offset = 100.f;
constexpr float game_button_width = 128.f;
constexpr float game_button_height = 64.f;
constexpr float pause_x = 1244.f;
constexpr float reset_x = 1372.f;
constexpr float pause_offset = 12.f;
constexpr float reset_offset = 5.f;
constexpr float display_x = 1244.f;
constexpr float player_one_display_y = 64.f;
constexpr float player_two_display_y = 432.f;
constexpr float display_offset = 20.f;
constexpr float display_width = 256.f;
constexpr float display_height = 368.f;
constexpr float canvas_width = screen_width - display_width;
constexpr float canvas_height = static_cast<float>(screen_height);
constexpr float player_string_x = 1265.f;
constexpr float player_one_string_y = 72.f;
constexpr float player_two_string_y = 440.f;
constexpr float player_card_x = 1265.f;
constexpr float player_card_x_offset = 15.f;
constexpr float player_one_card_y = 120.f;
constexpr float player_two_card_y = 488.f;
constexpr float card_width = 128.f;
constexpr float card_height = 128.f;
constexpr unsigned max_pairs = 16u;
constexpr float win_width = screen_width - 300.f;
constexpr float win_height = screen_height - 150.f;
constexpr float win_x = 150.f;
constexpr float win_y = 75.f;
constexpr float winstring_x = 400.f;
constexpr float winstring_y = 100.f;
constexpr float player_winstring_x = winstring_x - 50.f;
constexpr float player_winstring_y = winstring_y + 200.f;
enum class DeckSize
six = 6,
eight = 8,
twelve = 12,
sixteen = 16
;
enum class CardState
unmatched,
checking,
matched
;
enum class menuMouseIn
none,
play,
six,
eight,
twelve,
sixteen
;
enum class gameMouseIn
none,
pause,
reset
;
enum class winState
none,
draw,
playerOne,
playerTwo
;
#endif // !BRUGLESCO_MEMORY_EXPRESSIONS_H
My main is intended to be small. Memory is the class that represents the entirety of the application.
Main.cpp
#include "Memory.h"
int main()
Memory memory;
memory.run();
The Memory class contains the ModelData and Viewport class. These classes represent the model logic and presentation logic respectively.
Memory.h
#ifndef BRUGLESCO_MEMORY_MEMORY_H
#define BRUGLESCO_MEMORY_MEMORY_H
#include "Expressions.h"
#include "ModelData.h"
#include "Viewport.h"
#include <SFMLGraphics.hpp>
class Memory
public:
void run();
private:
ModelData data;
Viewport view data ;
bool playing true ;
void input();
void update();
void draw();
;
#endif // !BRUGLESCO_MEMORY_MEMORY_H
The ModelData class was the main focus of the last post. In this one I'd like to focus on the Vieport
This class contains the window for rendering, as well as the MenuScreen and GameScreen classes. These are the only two screens in the program.
First the MenuScreen. It is a simple arrangement of buttons that are highlighted dynamically both on selection and mouseover. The trackMouse method is to check for mouseover, the input method responds to user input (in this case clicks), and the update method dynamically maintains your selected game size and highlights the buttons according to that selection and mouseover.
MenuScreen.h
#ifndef BRUGLESCO_MEMORY_MENUSCREEN_H
#define BRUGLESCO_MEMORY_MENUSCREEN_H
#include "Expressions.h"
#include "GameScreen.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
class MenuScreen
public:
MenuScreen(ModelData& data, GameScreen& game, sf::Font& font);
void trackMouse(const sf::Vector2f& mousePos);
void input(const sf::Vector2f& mousePos);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
GameScreen& game;
sf::Font& font;
sf::RectangleShape playButton sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text playString "Play", font ;
sf::RectangleShape sixPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text sixString "Six", font ;
sf::RectangleShape eightPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text eightString "Eight", font ;
sf::RectangleShape twelvePairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text twelveString "Twelve", font ;
sf::RectangleShape sixteenPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text sixteenString "Sixteen", font ;
std::vector<sf::Text> pairString 4, "Pairs", font ;
bruglesco::menuMouseIn mouseIn bruglesco::menuMouseIn::none ;
bruglesco::DeckSize deckSize bruglesco::DeckSize::six ;
void highlightButton();
;
#endif // !BRUGLESCO_MEMORY_MENUSCREEN_H
MenuScreen.cpp
#include "MenuScreen.h"
MenuScreen::MenuScreen(ModelData& data, GameScreen& game, sf::Font& font) :
data data ,
game game ,
font font
playButton.setPosition(bruglesco::play_button_x, bruglesco::play_button_y);
playButton.setFillColor(sf::Color(120, 120, 120, 255));
playString.setPosition(bruglesco::play_button_x + bruglesco::play_string_x_offset, bruglesco::play_button_y + bruglesco::play_string_y_offset);
playString.setCharacterSize(150);
playString.setFillColor(sf::Color::Black);
sixPairs.setPosition(bruglesco::six_button_x, bruglesco::pair_button_y);
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setPosition(bruglesco::six_button_x + bruglesco::six_string_x_offset, bruglesco::pair_button_y);
sixString.setCharacterSize(100);
sixString.setFillColor(sf::Color::Red);
pairString[0].setPosition(bruglesco::six_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[0].setCharacterSize(100);
pairString[0].setFillColor(sf::Color::Red);
eightPairs.setPosition(bruglesco::eight_button_x, bruglesco::pair_button_y);
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setPosition(bruglesco::eight_button_x + bruglesco::eight_string_x_offset, bruglesco::pair_button_y);
eightString.setCharacterSize(100);
eightString.setFillColor(sf::Color::Black);
pairString[1].setPosition(bruglesco::eight_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[1].setCharacterSize(100);
pairString[1].setFillColor(sf::Color::Black);
twelvePairs.setPosition(bruglesco::twelve_button_x, bruglesco::pair_button_y);
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setPosition(bruglesco::twelve_button_x + bruglesco::twelve_string_x_offset, bruglesco::pair_button_y);
twelveString.setCharacterSize(100);
twelveString.setFillColor(sf::Color::Black);
pairString[2].setPosition(bruglesco::twelve_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[2].setCharacterSize(100);
pairString[2].setFillColor(sf::Color::Black);
sixteenPairs.setPosition(bruglesco::sixteen_button_x, bruglesco::pair_button_y);
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setPosition(bruglesco::sixteen_button_x + bruglesco::sixteen_string_x_offset, bruglesco::pair_button_y);
sixteenString.setCharacterSize(100);
sixteenString.setFillColor(sf::Color::Black);
pairString[3].setPosition(bruglesco::sixteen_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[3].setCharacterSize(100);
pairString[3].setFillColor(sf::Color::Black);
void MenuScreen::trackMouse(const sf::Vector2f& mousePos)
if (playButton.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::play;
else if (sixPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::six;
else if (eightPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::eight;
else if (twelvePairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::twelve;
else if (sixteenPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::sixteen;
else
mouseIn = bruglesco::menuMouseIn::none;
void MenuScreen::input(const sf::Vector2f& mousePos)
if (playButton.getGlobalBounds().contains(mousePos))
data.setSize(deckSize);
data.play();
game.setGame();
mouseIn = bruglesco::menuMouseIn::none;
else if (sixPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::six;
data.setSize(deckSize);
else if (eightPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::eight;
data.setSize(deckSize);
else if (twelvePairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::twelve;
data.setSize(deckSize);
else if (sixteenPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::sixteen;
data.setSize(deckSize);
void MenuScreen::update()
highlightButton();
void MenuScreen::draw(sf::RenderWindow& window)
window.draw(playButton);
window.draw(playString);
window.draw(sixPairs);
window.draw(sixString);
window.draw(eightPairs);
window.draw(eightString);
window.draw(twelvePairs);
window.draw(twelveString);
window.draw(sixteenPairs);
window.draw(sixteenString);
for (std::vector<sf::Text>::iterator string = pairString.begin(); string != pairString.end(); ++string)
window.draw(*string);
void MenuScreen::highlightButton()
if (mouseIn == bruglesco::menuMouseIn::play)
playButton.setFillColor(sf::Color(60, 60, 60, 255));
playString.setFillColor(sf::Color::Red);
else
playButton.setFillColor(sf::Color(120, 120, 120, 255));
playString.setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::six)
sixPairs.setFillColor(sf::Color(60, 60, 60, 255));
sixString.setFillColor(sf::Color::Red);
pairString[0].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::six)
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setFillColor(sf::Color::Red);
pairString[0].setFillColor(sf::Color::Red);
else
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setFillColor(sf::Color::Black);
pairString[0].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::eight)
eightPairs.setFillColor(sf::Color(60, 60, 60, 255));
eightString.setFillColor(sf::Color::Red);
pairString[1].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::eight)
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setFillColor(sf::Color::Red);
pairString[1].setFillColor(sf::Color::Red);
else
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setFillColor(sf::Color::Black);
pairString[1].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::twelve)
twelvePairs.setFillColor(sf::Color(60, 60, 60, 255));
twelveString.setFillColor(sf::Color::Red);
pairString[2].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::twelve)
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setFillColor(sf::Color::Red);
pairString[2].setFillColor(sf::Color::Red);
else
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setFillColor(sf::Color::Black);
pairString[2].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::sixteen)
sixteenPairs.setFillColor(sf::Color(60, 60, 60, 255));
sixteenString.setFillColor(sf::Color::Red);
pairString[3].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::sixteen)
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setFillColor(sf::Color::Red);
pairString[3].setFillColor(sf::Color::Red);
else
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setFillColor(sf::Color::Black);
pairString[3].setFillColor(sf::Color::Black);
Next is the GameScreen class. Here is the meat of the presentation logic. The HUDisplay class is a subsection that updates the players score as strings to display them. It also maintains a space to draw matched cards in a stack in the players "hand".
HUDisplay.h
#ifndef BRUGLESCO_MEMORY_HUDISPLAY_H
#define BRUGLESCO_MEMORY_HUDISPLAY_H
#include "Expressions.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
#include <string>
class HUDisplay
public:
HUDisplay(ModelData& data, sf::Font& font);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
sf::Font& font;
sf::RectangleShape playerOneDisplay sf::Vector2f(bruglesco::display_width, bruglesco::display_height) ;
sf::RectangleShape playerTwoDisplay sf::Vector2f(bruglesco::display_width, bruglesco::display_height) ;
unsigned playerOneScore 0 ;
sf::Text playerOne "Player One: " + std::to_string(playerOneScore), font ;
unsigned playerTwoScore 0 ;
sf::Text playerTwo "Player Two: " + std::to_string(playerTwoScore), font ;
;
#endif // !BRUGLESCO_MEMORY_HUDISPLAY_H
HUDisplay.cpp
#include "HUDisplay.h"
HUDisplay::HUDisplay(ModelData& data, sf::Font& font) :
data data ,
font font
playerOneDisplay.setPosition(bruglesco::display_x, bruglesco::player_one_display_y);
playerOneDisplay.setFillColor(sf::Color(120, 120, 120, 255));
playerTwoDisplay.setPosition(bruglesco::display_x, bruglesco::player_two_display_y);
playerTwoDisplay.setFillColor(sf::Color(120, 120, 120, 255));
playerOne.setPosition(bruglesco::display_x + bruglesco::display_offset, bruglesco::player_one_display_y);
playerOne.setCharacterSize(50);
playerOne.setFillColor(sf::Color::Red);
playerTwo.setPosition(bruglesco::display_x + bruglesco::display_offset, bruglesco::player_two_display_y);
playerTwo.setCharacterSize(50);
playerTwo.setFillColor(sf::Color::Black);
void HUDisplay::update()
if (data.playerOneTurn())
playerOneScore = data.getPlayer().getScore();
playerOne.setString("Player One: " + std::to_string(playerOneScore));
playerOne.setFillColor(sf::Color::Red);
playerTwo.setFillColor(sf::Color::Black);
else
playerTwoScore = data.getPlayer().getScore();
playerTwo.setString("Player Two: " + std::to_string(playerTwoScore));
playerOne.setFillColor(sf::Color::Black);
playerTwo.setFillColor(sf::Color::Red);
void HUDisplay::draw(sf::RenderWindow& window)
window.draw(playerOneDisplay);
window.draw(playerOne);
window.draw(playerTwoDisplay);
window.draw(playerTwo);
The trackMouse method here works the same as in the Menu. The input method is extended to registering clicks on card images. The logic is then passed over to the Model class which is maintained as a visitor to the View and passed in as a dependency. The positionCards function is able to position the cards dynamically based on the size of deck the player chose on the menu screen. The makeCards function creates the number of display objects based on deck size.
The matchFailDelay is an attempt at keeping the images displayed for a short period of time after a failed match. endFailDelay updates the Model so the display can update accordingly. moveMatched sets the position of matched cards according to the player who made the match and how many they've made. adjustTexture is done this way to consistently vary the images used and to swap back and forth between face up and face down.
GameScreen.h
#ifndef BRUGLESCO_MEMORY_GAMESCREEN_H
#define BRUGLESCO_MEMORY_GAMESCREEN_H
#include "Card.h"
#include "Expressions.h"
#include "HUDisplay.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
#include <vector>
class GameScreen
public:
GameScreen(ModelData& data, sf::Font& font);
void setGame();
void trackMouse(const sf::Vector2f& mousePos);
void input(const sf::Vector2f& mousePos);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
sf::Font& font;
sf::Texture cardMap;
sf::RectangleShape pauseButton sf::Vector2f(bruglesco::game_button_width, bruglesco::game_button_height) ;
sf::Text pauseString "Pause", font ;
sf::RectangleShape returnToMain sf::Vector2f(bruglesco::game_button_width, bruglesco::game_button_height) ;
sf::Text resetString "Return", font ;
HUDisplay hud data, font ;
sf::RectangleShape pauseForeground sf::Vector2f(static_cast<float>(bruglesco::screen_width), static_cast<float>(bruglesco::screen_height)) ;
sf::RectangleShape endGameForeground sf::Vector2f(bruglesco::win_width, bruglesco::win_height) ;
sf::Text winString "Winner", font ;
sf::Text playerWinString "", font ;
std::vector<sf::RectangleShape> deck;
bruglesco::gameMouseIn mouseIn bruglesco::gameMouseIn::none ;
bool paused false ;
std::vector<unsigned> imageIdentifiers;
unsigned delay 0 ;
void makeCards();
void positionCards();
void highlightButtons();
void matchFailDelay();
void endFailDelay();
void moveMatched();
void adjustTexture();
;
#endif // !BRUGLESCO_MEMORY_GAMESCREEN_H
GameScreen.cpp
#include "GameScreen.h"
GameScreen::GameScreen(ModelData& data, sf::Font& font) :
data data ,
font font
if (!cardMap.loadFromFile("spritesheet.png"))
bruglesco::debuggStream << "you aren't loading your textures"; // remove for release
pauseButton.setPosition(bruglesco::pause_x, 0);
pauseButton.setFillColor(sf::Color(120, 120, 120, 255));
pauseString.setPosition(bruglesco::pause_x + bruglesco::pause_offset, 0);
pauseString.setCharacterSize(50);
pauseString.setFillColor(sf::Color::Black);
returnToMain.setPosition(bruglesco::reset_x, 0);
returnToMain.setFillColor(sf::Color(120, 120, 120, 255));
resetString.setPosition(bruglesco::reset_x + bruglesco::reset_offset, 0);
resetString.setCharacterSize(50);
resetString.setFillColor(sf::Color::Black);
pauseForeground.setFillColor(sf::Color(120, 120, 120, 120));
endGameForeground.setPosition(bruglesco::win_x, bruglesco::win_y);
endGameForeground.setFillColor(sf::Color(90, 90, 90, 180));
winString.setPosition(bruglesco::winstring_x, bruglesco::winstring_y);
winString.setCharacterSize(250);
winString.setFillColor(sf::Color::Green);
playerWinString.setPosition(bruglesco::player_winstring_x, bruglesco::player_winstring_y);
playerWinString.setCharacterSize(250);
playerWinString.setFillColor(sf::Color::Green);
for (unsigned i = 0; i < bruglesco::max_pairs; ++i)
imageIdentifiers.push_back(i);
void GameScreen::setGame()
std::shuffle(std::begin(imageIdentifiers), std::end(imageIdentifiers), bruglesco::generator);
makeCards();
positionCards();
void GameScreen::trackMouse(const sf::Vector2f& mousePos)
if (pauseButton.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::gameMouseIn::pause;
else if (returnToMain.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::gameMouseIn::reset;
else
mouseIn = bruglesco::gameMouseIn::none;
void GameScreen::input(const sf::Vector2f& mousePos)
if (pauseButton.getGlobalBounds().contains(mousePos))
if (!data.isOver())
paused = !paused;
else if (!paused && returnToMain.getGlobalBounds().contains(mousePos))
data.quit();
else if (!paused && delay == 0)
for (std::pair<std::vector<Card>::iterator, std::vector<sf::RectangleShape>::iterator> card(data.getDeck().begin(), deck.begin());
card.first != data.getDeck().end() && card.second != deck.end(); ++card.first, ++card.second)
if (card.second->getGlobalBounds().contains(mousePos))
card.first->flip();
void GameScreen::update()
highlightButtons();
matchFailDelay();
moveMatched();
adjustTexture();
hud.update();
if (delay == 1)
endFailDelay();
if (delay > 0)
--delay;
if (data.isOver())
playerWinString.setString("Player " + std::to_string(data.getPlayer().getIdentity()));
void GameScreen::draw(sf::RenderWindow& window)
window.draw(pauseButton);
window.draw(pauseString);
window.draw(returnToMain);
window.draw(resetString);
hud.draw(window);
for (std::vector<sf::RectangleShape>::iterator card = deck.begin(); card != deck.end(); ++card)
window.draw(*card);
if (paused)
window.draw(pauseForeground);
if (data.isOver())
window.draw(endGameForeground);
window.draw(winString);
window.draw(playerWinString);
void GameScreen::makeCards()
deck.clear();
unsigned k = 0;
for (std::vector<Card>::iterator card = data.getDeck().begin(); card != data.getDeck().end(); ++card)
deck.push_back(sf::RectangleShape(sf::Vector2f(bruglesco::card_width, bruglesco::card_height)));
deck[k].setTexture(&cardMap);
deck[k].setTextureRect(sf::IntRect(2048, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
++k;
void GameScreen::positionCards()
unsigned rows = 0;
for (unsigned i = 2; i * i <= deck.size(); ++i)
if (deck.size() % i == 0)
rows = i;
unsigned columns = deck.size() / rows;
float padding_x = (bruglesco::canvas_width - columns * 128.f) / (columns + 1);
float padding_y = (bruglesco::canvas_height - rows * 128.f) / (rows + 1);
float x = padding_x;
float y = padding_y;
unsigned counter = 1;
for (std::vector<sf::RectangleShape>::iterator card = deck.begin(); card != deck.end(); ++card)
card->setPosition(x, y);
if (counter < columns)
++counter;
x += padding_x + bruglesco::card_width;
else
counter = 1;
x = padding_x;
y += padding_y + bruglesco::card_height;
void GameScreen::highlightButtons()
mouseIn == bruglesco::gameMouseIn::pause)
pauseButton.setFillColor(sf::Color(60, 60, 60, 255));
pauseString.setFillColor(sf::Color::Red);
else
pauseButton.setFillColor(sf::Color(120, 120, 120, 255));
pauseString.setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::gameMouseIn::reset)
returnToMain.setFillColor(sf::Color(60, 60, 60, 255));
resetString.setFillColor(sf::Color::Red);
else
returnToMain.setFillColor(sf::Color(120, 120, 120, 255));
resetString.setFillColor(sf::Color::Black);
void GameScreen::matchFailDelay()
if (delay == 0 && data.getFailedCards().size() == 2)
delay = 240;
void GameScreen::endFailDelay()
data.resetDeck();
void GameScreen::moveMatched()
if (data.getMatchedCards().size() == 2)
float x = bruglesco::player_card_x + bruglesco::player_card_x_offset * data.getPlayer().getScore();
float y;
if (data.playerOneTurn())
y = bruglesco::player_one_card_y;
else
y = bruglesco::player_two_card_y;
for (unsigned i = 0; i < data.getMatchedCards().size(); ++i)
deck[data.getMatchedCards()[i]].setPosition(x, y);
data.resetDeck();
void GameScreen::adjustTexture()
for (unsigned i = 0; i < data.getDeck().size(); ++i)
if (data.getDeck()[i].checkState() == bruglesco::CardState::unmatched)
deck[i].setTextureRect(sf::IntRect(2048, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
else
deck[i].setTextureRect(sf::IntRect(imageIdentifiers[data.getDeck()[i].getMatch()] * 128, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
for (unsigned i = 0; i < data.getFailedCards().size(); ++i)
deck[data.getFailedCards()[i]].setTextureRect(sf::IntRect(imageIdentifiers[data.getDeck()[data.getFailedCards()[i]].getMatch()] * 128, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
I tried to be a little more thorough in my description of my code this time around but that is not a skill I am great at yet. I hope that if you need any further explanation you will leave a comment and I will provide it.
If I have any particular concerns for the code it would be the GameScreen class. It just seems to be large enough to worry. Should I try to abstract it further? any suggestions how? Of course I want any suggestions on any aspect of the code.
c++ object-oriented game mvvm sfml
add a comment |Â
up vote
4
down vote
favorite
This is the 2nd half of my Memory game. The first half can be found here. The repository can be found on Github.
I am using SFML 2.4.2. In this project I focused on a few key aspects of design and development.
- Top-down Design
- DRY & SRP
- Encapsulation & Abstraction
In an effort to eliminate magic numbers I shoved all of my constexpr as well as my enums, a static logger and a static PRNG in a single header file. I don't know if this approach is best practice or not and would appreciate input. On one hand I could move the expressions to the appropriate headers where they are localized, but many of them are used repeatedly throughout the codebase. Should I localize the values that are used once and only keep a header file for the reused items (PRNG, Enums, etc...)?
note: in the previous answer it was recommended to me to use double over float. I chose not to do this as SFML takes floats for its parameters and the number of casts made the code unreadable. I could let the computer narrow implicitly but then I would need to turn of wX which I prefer to keep on. It would also lead to a significant number of warnings.
Expressions.h
#ifndef BRUGLESCO_MEMORY_EXPRESSIONS_H
#define BRUGLESCO_MEMORY_EXPRESSIONS_H
#include <fstream>
#include <random>
namespace bruglesco
static std::random_device rd;
static std::mt19937 generator(rd());
static std::ofstream debuggStream("DebugLog.txt"); // remove for release
constexpr unsigned screen_width = 1500u;
constexpr unsigned screen_height = 800u;
constexpr float menu_button_width = 256.f;
constexpr float menu_button_height = 256.f;
constexpr float play_button_x = 622.f;
constexpr float play_button_y = 96.f;
constexpr float six_button_x = 95.f;
constexpr float eight_button_x = 446.f;
constexpr float twelve_button_x = 797.f;
constexpr float sixteen_button_x = 1148.f;
constexpr float pair_button_y = 448.f;
constexpr float play_string_x_offset = 18.f;
constexpr float play_string_y_offset = 28.f;
constexpr float six_string_x_offset = 80.f;
constexpr float eight_string_x_offset = 40.f;
constexpr float twelve_string_x_offset = 15.f;
constexpr float sixteen_string_x_offset = 8.f;
constexpr float pair_string_x_offset = 40.f;
constexpr float pair_string_y_offset = 100.f;
constexpr float game_button_width = 128.f;
constexpr float game_button_height = 64.f;
constexpr float pause_x = 1244.f;
constexpr float reset_x = 1372.f;
constexpr float pause_offset = 12.f;
constexpr float reset_offset = 5.f;
constexpr float display_x = 1244.f;
constexpr float player_one_display_y = 64.f;
constexpr float player_two_display_y = 432.f;
constexpr float display_offset = 20.f;
constexpr float display_width = 256.f;
constexpr float display_height = 368.f;
constexpr float canvas_width = screen_width - display_width;
constexpr float canvas_height = static_cast<float>(screen_height);
constexpr float player_string_x = 1265.f;
constexpr float player_one_string_y = 72.f;
constexpr float player_two_string_y = 440.f;
constexpr float player_card_x = 1265.f;
constexpr float player_card_x_offset = 15.f;
constexpr float player_one_card_y = 120.f;
constexpr float player_two_card_y = 488.f;
constexpr float card_width = 128.f;
constexpr float card_height = 128.f;
constexpr unsigned max_pairs = 16u;
constexpr float win_width = screen_width - 300.f;
constexpr float win_height = screen_height - 150.f;
constexpr float win_x = 150.f;
constexpr float win_y = 75.f;
constexpr float winstring_x = 400.f;
constexpr float winstring_y = 100.f;
constexpr float player_winstring_x = winstring_x - 50.f;
constexpr float player_winstring_y = winstring_y + 200.f;
enum class DeckSize
six = 6,
eight = 8,
twelve = 12,
sixteen = 16
;
enum class CardState
unmatched,
checking,
matched
;
enum class menuMouseIn
none,
play,
six,
eight,
twelve,
sixteen
;
enum class gameMouseIn
none,
pause,
reset
;
enum class winState
none,
draw,
playerOne,
playerTwo
;
#endif // !BRUGLESCO_MEMORY_EXPRESSIONS_H
My main is intended to be small. Memory is the class that represents the entirety of the application.
Main.cpp
#include "Memory.h"
int main()
Memory memory;
memory.run();
The Memory class contains the ModelData and Viewport class. These classes represent the model logic and presentation logic respectively.
Memory.h
#ifndef BRUGLESCO_MEMORY_MEMORY_H
#define BRUGLESCO_MEMORY_MEMORY_H
#include "Expressions.h"
#include "ModelData.h"
#include "Viewport.h"
#include <SFMLGraphics.hpp>
class Memory
public:
void run();
private:
ModelData data;
Viewport view data ;
bool playing true ;
void input();
void update();
void draw();
;
#endif // !BRUGLESCO_MEMORY_MEMORY_H
The ModelData class was the main focus of the last post. In this one I'd like to focus on the Vieport
This class contains the window for rendering, as well as the MenuScreen and GameScreen classes. These are the only two screens in the program.
First the MenuScreen. It is a simple arrangement of buttons that are highlighted dynamically both on selection and mouseover. The trackMouse method is to check for mouseover, the input method responds to user input (in this case clicks), and the update method dynamically maintains your selected game size and highlights the buttons according to that selection and mouseover.
MenuScreen.h
#ifndef BRUGLESCO_MEMORY_MENUSCREEN_H
#define BRUGLESCO_MEMORY_MENUSCREEN_H
#include "Expressions.h"
#include "GameScreen.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
class MenuScreen
public:
MenuScreen(ModelData& data, GameScreen& game, sf::Font& font);
void trackMouse(const sf::Vector2f& mousePos);
void input(const sf::Vector2f& mousePos);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
GameScreen& game;
sf::Font& font;
sf::RectangleShape playButton sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text playString "Play", font ;
sf::RectangleShape sixPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text sixString "Six", font ;
sf::RectangleShape eightPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text eightString "Eight", font ;
sf::RectangleShape twelvePairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text twelveString "Twelve", font ;
sf::RectangleShape sixteenPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text sixteenString "Sixteen", font ;
std::vector<sf::Text> pairString 4, "Pairs", font ;
bruglesco::menuMouseIn mouseIn bruglesco::menuMouseIn::none ;
bruglesco::DeckSize deckSize bruglesco::DeckSize::six ;
void highlightButton();
;
#endif // !BRUGLESCO_MEMORY_MENUSCREEN_H
MenuScreen.cpp
#include "MenuScreen.h"
MenuScreen::MenuScreen(ModelData& data, GameScreen& game, sf::Font& font) :
data data ,
game game ,
font font
playButton.setPosition(bruglesco::play_button_x, bruglesco::play_button_y);
playButton.setFillColor(sf::Color(120, 120, 120, 255));
playString.setPosition(bruglesco::play_button_x + bruglesco::play_string_x_offset, bruglesco::play_button_y + bruglesco::play_string_y_offset);
playString.setCharacterSize(150);
playString.setFillColor(sf::Color::Black);
sixPairs.setPosition(bruglesco::six_button_x, bruglesco::pair_button_y);
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setPosition(bruglesco::six_button_x + bruglesco::six_string_x_offset, bruglesco::pair_button_y);
sixString.setCharacterSize(100);
sixString.setFillColor(sf::Color::Red);
pairString[0].setPosition(bruglesco::six_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[0].setCharacterSize(100);
pairString[0].setFillColor(sf::Color::Red);
eightPairs.setPosition(bruglesco::eight_button_x, bruglesco::pair_button_y);
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setPosition(bruglesco::eight_button_x + bruglesco::eight_string_x_offset, bruglesco::pair_button_y);
eightString.setCharacterSize(100);
eightString.setFillColor(sf::Color::Black);
pairString[1].setPosition(bruglesco::eight_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[1].setCharacterSize(100);
pairString[1].setFillColor(sf::Color::Black);
twelvePairs.setPosition(bruglesco::twelve_button_x, bruglesco::pair_button_y);
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setPosition(bruglesco::twelve_button_x + bruglesco::twelve_string_x_offset, bruglesco::pair_button_y);
twelveString.setCharacterSize(100);
twelveString.setFillColor(sf::Color::Black);
pairString[2].setPosition(bruglesco::twelve_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[2].setCharacterSize(100);
pairString[2].setFillColor(sf::Color::Black);
sixteenPairs.setPosition(bruglesco::sixteen_button_x, bruglesco::pair_button_y);
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setPosition(bruglesco::sixteen_button_x + bruglesco::sixteen_string_x_offset, bruglesco::pair_button_y);
sixteenString.setCharacterSize(100);
sixteenString.setFillColor(sf::Color::Black);
pairString[3].setPosition(bruglesco::sixteen_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[3].setCharacterSize(100);
pairString[3].setFillColor(sf::Color::Black);
void MenuScreen::trackMouse(const sf::Vector2f& mousePos)
if (playButton.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::play;
else if (sixPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::six;
else if (eightPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::eight;
else if (twelvePairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::twelve;
else if (sixteenPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::sixteen;
else
mouseIn = bruglesco::menuMouseIn::none;
void MenuScreen::input(const sf::Vector2f& mousePos)
if (playButton.getGlobalBounds().contains(mousePos))
data.setSize(deckSize);
data.play();
game.setGame();
mouseIn = bruglesco::menuMouseIn::none;
else if (sixPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::six;
data.setSize(deckSize);
else if (eightPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::eight;
data.setSize(deckSize);
else if (twelvePairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::twelve;
data.setSize(deckSize);
else if (sixteenPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::sixteen;
data.setSize(deckSize);
void MenuScreen::update()
highlightButton();
void MenuScreen::draw(sf::RenderWindow& window)
window.draw(playButton);
window.draw(playString);
window.draw(sixPairs);
window.draw(sixString);
window.draw(eightPairs);
window.draw(eightString);
window.draw(twelvePairs);
window.draw(twelveString);
window.draw(sixteenPairs);
window.draw(sixteenString);
for (std::vector<sf::Text>::iterator string = pairString.begin(); string != pairString.end(); ++string)
window.draw(*string);
void MenuScreen::highlightButton()
if (mouseIn == bruglesco::menuMouseIn::play)
playButton.setFillColor(sf::Color(60, 60, 60, 255));
playString.setFillColor(sf::Color::Red);
else
playButton.setFillColor(sf::Color(120, 120, 120, 255));
playString.setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::six)
sixPairs.setFillColor(sf::Color(60, 60, 60, 255));
sixString.setFillColor(sf::Color::Red);
pairString[0].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::six)
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setFillColor(sf::Color::Red);
pairString[0].setFillColor(sf::Color::Red);
else
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setFillColor(sf::Color::Black);
pairString[0].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::eight)
eightPairs.setFillColor(sf::Color(60, 60, 60, 255));
eightString.setFillColor(sf::Color::Red);
pairString[1].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::eight)
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setFillColor(sf::Color::Red);
pairString[1].setFillColor(sf::Color::Red);
else
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setFillColor(sf::Color::Black);
pairString[1].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::twelve)
twelvePairs.setFillColor(sf::Color(60, 60, 60, 255));
twelveString.setFillColor(sf::Color::Red);
pairString[2].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::twelve)
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setFillColor(sf::Color::Red);
pairString[2].setFillColor(sf::Color::Red);
else
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setFillColor(sf::Color::Black);
pairString[2].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::sixteen)
sixteenPairs.setFillColor(sf::Color(60, 60, 60, 255));
sixteenString.setFillColor(sf::Color::Red);
pairString[3].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::sixteen)
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setFillColor(sf::Color::Red);
pairString[3].setFillColor(sf::Color::Red);
else
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setFillColor(sf::Color::Black);
pairString[3].setFillColor(sf::Color::Black);
Next is the GameScreen class. Here is the meat of the presentation logic. The HUDisplay class is a subsection that updates the players score as strings to display them. It also maintains a space to draw matched cards in a stack in the players "hand".
HUDisplay.h
#ifndef BRUGLESCO_MEMORY_HUDISPLAY_H
#define BRUGLESCO_MEMORY_HUDISPLAY_H
#include "Expressions.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
#include <string>
class HUDisplay
public:
HUDisplay(ModelData& data, sf::Font& font);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
sf::Font& font;
sf::RectangleShape playerOneDisplay sf::Vector2f(bruglesco::display_width, bruglesco::display_height) ;
sf::RectangleShape playerTwoDisplay sf::Vector2f(bruglesco::display_width, bruglesco::display_height) ;
unsigned playerOneScore 0 ;
sf::Text playerOne "Player One: " + std::to_string(playerOneScore), font ;
unsigned playerTwoScore 0 ;
sf::Text playerTwo "Player Two: " + std::to_string(playerTwoScore), font ;
;
#endif // !BRUGLESCO_MEMORY_HUDISPLAY_H
HUDisplay.cpp
#include "HUDisplay.h"
HUDisplay::HUDisplay(ModelData& data, sf::Font& font) :
data data ,
font font
playerOneDisplay.setPosition(bruglesco::display_x, bruglesco::player_one_display_y);
playerOneDisplay.setFillColor(sf::Color(120, 120, 120, 255));
playerTwoDisplay.setPosition(bruglesco::display_x, bruglesco::player_two_display_y);
playerTwoDisplay.setFillColor(sf::Color(120, 120, 120, 255));
playerOne.setPosition(bruglesco::display_x + bruglesco::display_offset, bruglesco::player_one_display_y);
playerOne.setCharacterSize(50);
playerOne.setFillColor(sf::Color::Red);
playerTwo.setPosition(bruglesco::display_x + bruglesco::display_offset, bruglesco::player_two_display_y);
playerTwo.setCharacterSize(50);
playerTwo.setFillColor(sf::Color::Black);
void HUDisplay::update()
if (data.playerOneTurn())
playerOneScore = data.getPlayer().getScore();
playerOne.setString("Player One: " + std::to_string(playerOneScore));
playerOne.setFillColor(sf::Color::Red);
playerTwo.setFillColor(sf::Color::Black);
else
playerTwoScore = data.getPlayer().getScore();
playerTwo.setString("Player Two: " + std::to_string(playerTwoScore));
playerOne.setFillColor(sf::Color::Black);
playerTwo.setFillColor(sf::Color::Red);
void HUDisplay::draw(sf::RenderWindow& window)
window.draw(playerOneDisplay);
window.draw(playerOne);
window.draw(playerTwoDisplay);
window.draw(playerTwo);
The trackMouse method here works the same as in the Menu. The input method is extended to registering clicks on card images. The logic is then passed over to the Model class which is maintained as a visitor to the View and passed in as a dependency. The positionCards function is able to position the cards dynamically based on the size of deck the player chose on the menu screen. The makeCards function creates the number of display objects based on deck size.
The matchFailDelay is an attempt at keeping the images displayed for a short period of time after a failed match. endFailDelay updates the Model so the display can update accordingly. moveMatched sets the position of matched cards according to the player who made the match and how many they've made. adjustTexture is done this way to consistently vary the images used and to swap back and forth between face up and face down.
GameScreen.h
#ifndef BRUGLESCO_MEMORY_GAMESCREEN_H
#define BRUGLESCO_MEMORY_GAMESCREEN_H
#include "Card.h"
#include "Expressions.h"
#include "HUDisplay.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
#include <vector>
class GameScreen
public:
GameScreen(ModelData& data, sf::Font& font);
void setGame();
void trackMouse(const sf::Vector2f& mousePos);
void input(const sf::Vector2f& mousePos);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
sf::Font& font;
sf::Texture cardMap;
sf::RectangleShape pauseButton sf::Vector2f(bruglesco::game_button_width, bruglesco::game_button_height) ;
sf::Text pauseString "Pause", font ;
sf::RectangleShape returnToMain sf::Vector2f(bruglesco::game_button_width, bruglesco::game_button_height) ;
sf::Text resetString "Return", font ;
HUDisplay hud data, font ;
sf::RectangleShape pauseForeground sf::Vector2f(static_cast<float>(bruglesco::screen_width), static_cast<float>(bruglesco::screen_height)) ;
sf::RectangleShape endGameForeground sf::Vector2f(bruglesco::win_width, bruglesco::win_height) ;
sf::Text winString "Winner", font ;
sf::Text playerWinString "", font ;
std::vector<sf::RectangleShape> deck;
bruglesco::gameMouseIn mouseIn bruglesco::gameMouseIn::none ;
bool paused false ;
std::vector<unsigned> imageIdentifiers;
unsigned delay 0 ;
void makeCards();
void positionCards();
void highlightButtons();
void matchFailDelay();
void endFailDelay();
void moveMatched();
void adjustTexture();
;
#endif // !BRUGLESCO_MEMORY_GAMESCREEN_H
GameScreen.cpp
#include "GameScreen.h"
GameScreen::GameScreen(ModelData& data, sf::Font& font) :
data data ,
font font
if (!cardMap.loadFromFile("spritesheet.png"))
bruglesco::debuggStream << "you aren't loading your textures"; // remove for release
pauseButton.setPosition(bruglesco::pause_x, 0);
pauseButton.setFillColor(sf::Color(120, 120, 120, 255));
pauseString.setPosition(bruglesco::pause_x + bruglesco::pause_offset, 0);
pauseString.setCharacterSize(50);
pauseString.setFillColor(sf::Color::Black);
returnToMain.setPosition(bruglesco::reset_x, 0);
returnToMain.setFillColor(sf::Color(120, 120, 120, 255));
resetString.setPosition(bruglesco::reset_x + bruglesco::reset_offset, 0);
resetString.setCharacterSize(50);
resetString.setFillColor(sf::Color::Black);
pauseForeground.setFillColor(sf::Color(120, 120, 120, 120));
endGameForeground.setPosition(bruglesco::win_x, bruglesco::win_y);
endGameForeground.setFillColor(sf::Color(90, 90, 90, 180));
winString.setPosition(bruglesco::winstring_x, bruglesco::winstring_y);
winString.setCharacterSize(250);
winString.setFillColor(sf::Color::Green);
playerWinString.setPosition(bruglesco::player_winstring_x, bruglesco::player_winstring_y);
playerWinString.setCharacterSize(250);
playerWinString.setFillColor(sf::Color::Green);
for (unsigned i = 0; i < bruglesco::max_pairs; ++i)
imageIdentifiers.push_back(i);
void GameScreen::setGame()
std::shuffle(std::begin(imageIdentifiers), std::end(imageIdentifiers), bruglesco::generator);
makeCards();
positionCards();
void GameScreen::trackMouse(const sf::Vector2f& mousePos)
if (pauseButton.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::gameMouseIn::pause;
else if (returnToMain.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::gameMouseIn::reset;
else
mouseIn = bruglesco::gameMouseIn::none;
void GameScreen::input(const sf::Vector2f& mousePos)
if (pauseButton.getGlobalBounds().contains(mousePos))
if (!data.isOver())
paused = !paused;
else if (!paused && returnToMain.getGlobalBounds().contains(mousePos))
data.quit();
else if (!paused && delay == 0)
for (std::pair<std::vector<Card>::iterator, std::vector<sf::RectangleShape>::iterator> card(data.getDeck().begin(), deck.begin());
card.first != data.getDeck().end() && card.second != deck.end(); ++card.first, ++card.second)
if (card.second->getGlobalBounds().contains(mousePos))
card.first->flip();
void GameScreen::update()
highlightButtons();
matchFailDelay();
moveMatched();
adjustTexture();
hud.update();
if (delay == 1)
endFailDelay();
if (delay > 0)
--delay;
if (data.isOver())
playerWinString.setString("Player " + std::to_string(data.getPlayer().getIdentity()));
void GameScreen::draw(sf::RenderWindow& window)
window.draw(pauseButton);
window.draw(pauseString);
window.draw(returnToMain);
window.draw(resetString);
hud.draw(window);
for (std::vector<sf::RectangleShape>::iterator card = deck.begin(); card != deck.end(); ++card)
window.draw(*card);
if (paused)
window.draw(pauseForeground);
if (data.isOver())
window.draw(endGameForeground);
window.draw(winString);
window.draw(playerWinString);
void GameScreen::makeCards()
deck.clear();
unsigned k = 0;
for (std::vector<Card>::iterator card = data.getDeck().begin(); card != data.getDeck().end(); ++card)
deck.push_back(sf::RectangleShape(sf::Vector2f(bruglesco::card_width, bruglesco::card_height)));
deck[k].setTexture(&cardMap);
deck[k].setTextureRect(sf::IntRect(2048, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
++k;
void GameScreen::positionCards()
unsigned rows = 0;
for (unsigned i = 2; i * i <= deck.size(); ++i)
if (deck.size() % i == 0)
rows = i;
unsigned columns = deck.size() / rows;
float padding_x = (bruglesco::canvas_width - columns * 128.f) / (columns + 1);
float padding_y = (bruglesco::canvas_height - rows * 128.f) / (rows + 1);
float x = padding_x;
float y = padding_y;
unsigned counter = 1;
for (std::vector<sf::RectangleShape>::iterator card = deck.begin(); card != deck.end(); ++card)
card->setPosition(x, y);
if (counter < columns)
++counter;
x += padding_x + bruglesco::card_width;
else
counter = 1;
x = padding_x;
y += padding_y + bruglesco::card_height;
void GameScreen::highlightButtons()
mouseIn == bruglesco::gameMouseIn::pause)
pauseButton.setFillColor(sf::Color(60, 60, 60, 255));
pauseString.setFillColor(sf::Color::Red);
else
pauseButton.setFillColor(sf::Color(120, 120, 120, 255));
pauseString.setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::gameMouseIn::reset)
returnToMain.setFillColor(sf::Color(60, 60, 60, 255));
resetString.setFillColor(sf::Color::Red);
else
returnToMain.setFillColor(sf::Color(120, 120, 120, 255));
resetString.setFillColor(sf::Color::Black);
void GameScreen::matchFailDelay()
if (delay == 0 && data.getFailedCards().size() == 2)
delay = 240;
void GameScreen::endFailDelay()
data.resetDeck();
void GameScreen::moveMatched()
if (data.getMatchedCards().size() == 2)
float x = bruglesco::player_card_x + bruglesco::player_card_x_offset * data.getPlayer().getScore();
float y;
if (data.playerOneTurn())
y = bruglesco::player_one_card_y;
else
y = bruglesco::player_two_card_y;
for (unsigned i = 0; i < data.getMatchedCards().size(); ++i)
deck[data.getMatchedCards()[i]].setPosition(x, y);
data.resetDeck();
void GameScreen::adjustTexture()
for (unsigned i = 0; i < data.getDeck().size(); ++i)
if (data.getDeck()[i].checkState() == bruglesco::CardState::unmatched)
deck[i].setTextureRect(sf::IntRect(2048, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
else
deck[i].setTextureRect(sf::IntRect(imageIdentifiers[data.getDeck()[i].getMatch()] * 128, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
for (unsigned i = 0; i < data.getFailedCards().size(); ++i)
deck[data.getFailedCards()[i]].setTextureRect(sf::IntRect(imageIdentifiers[data.getDeck()[data.getFailedCards()[i]].getMatch()] * 128, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
I tried to be a little more thorough in my description of my code this time around but that is not a skill I am great at yet. I hope that if you need any further explanation you will leave a comment and I will provide it.
If I have any particular concerns for the code it would be the GameScreen class. It just seems to be large enough to worry. Should I try to abstract it further? any suggestions how? Of course I want any suggestions on any aspect of the code.
c++ object-oriented game mvvm sfml
add a comment |Â
up vote
4
down vote
favorite
up vote
4
down vote
favorite
This is the 2nd half of my Memory game. The first half can be found here. The repository can be found on Github.
I am using SFML 2.4.2. In this project I focused on a few key aspects of design and development.
- Top-down Design
- DRY & SRP
- Encapsulation & Abstraction
In an effort to eliminate magic numbers I shoved all of my constexpr as well as my enums, a static logger and a static PRNG in a single header file. I don't know if this approach is best practice or not and would appreciate input. On one hand I could move the expressions to the appropriate headers where they are localized, but many of them are used repeatedly throughout the codebase. Should I localize the values that are used once and only keep a header file for the reused items (PRNG, Enums, etc...)?
note: in the previous answer it was recommended to me to use double over float. I chose not to do this as SFML takes floats for its parameters and the number of casts made the code unreadable. I could let the computer narrow implicitly but then I would need to turn of wX which I prefer to keep on. It would also lead to a significant number of warnings.
Expressions.h
#ifndef BRUGLESCO_MEMORY_EXPRESSIONS_H
#define BRUGLESCO_MEMORY_EXPRESSIONS_H
#include <fstream>
#include <random>
namespace bruglesco
static std::random_device rd;
static std::mt19937 generator(rd());
static std::ofstream debuggStream("DebugLog.txt"); // remove for release
constexpr unsigned screen_width = 1500u;
constexpr unsigned screen_height = 800u;
constexpr float menu_button_width = 256.f;
constexpr float menu_button_height = 256.f;
constexpr float play_button_x = 622.f;
constexpr float play_button_y = 96.f;
constexpr float six_button_x = 95.f;
constexpr float eight_button_x = 446.f;
constexpr float twelve_button_x = 797.f;
constexpr float sixteen_button_x = 1148.f;
constexpr float pair_button_y = 448.f;
constexpr float play_string_x_offset = 18.f;
constexpr float play_string_y_offset = 28.f;
constexpr float six_string_x_offset = 80.f;
constexpr float eight_string_x_offset = 40.f;
constexpr float twelve_string_x_offset = 15.f;
constexpr float sixteen_string_x_offset = 8.f;
constexpr float pair_string_x_offset = 40.f;
constexpr float pair_string_y_offset = 100.f;
constexpr float game_button_width = 128.f;
constexpr float game_button_height = 64.f;
constexpr float pause_x = 1244.f;
constexpr float reset_x = 1372.f;
constexpr float pause_offset = 12.f;
constexpr float reset_offset = 5.f;
constexpr float display_x = 1244.f;
constexpr float player_one_display_y = 64.f;
constexpr float player_two_display_y = 432.f;
constexpr float display_offset = 20.f;
constexpr float display_width = 256.f;
constexpr float display_height = 368.f;
constexpr float canvas_width = screen_width - display_width;
constexpr float canvas_height = static_cast<float>(screen_height);
constexpr float player_string_x = 1265.f;
constexpr float player_one_string_y = 72.f;
constexpr float player_two_string_y = 440.f;
constexpr float player_card_x = 1265.f;
constexpr float player_card_x_offset = 15.f;
constexpr float player_one_card_y = 120.f;
constexpr float player_two_card_y = 488.f;
constexpr float card_width = 128.f;
constexpr float card_height = 128.f;
constexpr unsigned max_pairs = 16u;
constexpr float win_width = screen_width - 300.f;
constexpr float win_height = screen_height - 150.f;
constexpr float win_x = 150.f;
constexpr float win_y = 75.f;
constexpr float winstring_x = 400.f;
constexpr float winstring_y = 100.f;
constexpr float player_winstring_x = winstring_x - 50.f;
constexpr float player_winstring_y = winstring_y + 200.f;
enum class DeckSize
six = 6,
eight = 8,
twelve = 12,
sixteen = 16
;
enum class CardState
unmatched,
checking,
matched
;
enum class menuMouseIn
none,
play,
six,
eight,
twelve,
sixteen
;
enum class gameMouseIn
none,
pause,
reset
;
enum class winState
none,
draw,
playerOne,
playerTwo
;
#endif // !BRUGLESCO_MEMORY_EXPRESSIONS_H
My main is intended to be small. Memory is the class that represents the entirety of the application.
Main.cpp
#include "Memory.h"
int main()
Memory memory;
memory.run();
The Memory class contains the ModelData and Viewport class. These classes represent the model logic and presentation logic respectively.
Memory.h
#ifndef BRUGLESCO_MEMORY_MEMORY_H
#define BRUGLESCO_MEMORY_MEMORY_H
#include "Expressions.h"
#include "ModelData.h"
#include "Viewport.h"
#include <SFMLGraphics.hpp>
class Memory
public:
void run();
private:
ModelData data;
Viewport view data ;
bool playing true ;
void input();
void update();
void draw();
;
#endif // !BRUGLESCO_MEMORY_MEMORY_H
The ModelData class was the main focus of the last post. In this one I'd like to focus on the Vieport
This class contains the window for rendering, as well as the MenuScreen and GameScreen classes. These are the only two screens in the program.
First the MenuScreen. It is a simple arrangement of buttons that are highlighted dynamically both on selection and mouseover. The trackMouse method is to check for mouseover, the input method responds to user input (in this case clicks), and the update method dynamically maintains your selected game size and highlights the buttons according to that selection and mouseover.
MenuScreen.h
#ifndef BRUGLESCO_MEMORY_MENUSCREEN_H
#define BRUGLESCO_MEMORY_MENUSCREEN_H
#include "Expressions.h"
#include "GameScreen.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
class MenuScreen
public:
MenuScreen(ModelData& data, GameScreen& game, sf::Font& font);
void trackMouse(const sf::Vector2f& mousePos);
void input(const sf::Vector2f& mousePos);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
GameScreen& game;
sf::Font& font;
sf::RectangleShape playButton sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text playString "Play", font ;
sf::RectangleShape sixPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text sixString "Six", font ;
sf::RectangleShape eightPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text eightString "Eight", font ;
sf::RectangleShape twelvePairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text twelveString "Twelve", font ;
sf::RectangleShape sixteenPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text sixteenString "Sixteen", font ;
std::vector<sf::Text> pairString 4, "Pairs", font ;
bruglesco::menuMouseIn mouseIn bruglesco::menuMouseIn::none ;
bruglesco::DeckSize deckSize bruglesco::DeckSize::six ;
void highlightButton();
;
#endif // !BRUGLESCO_MEMORY_MENUSCREEN_H
MenuScreen.cpp
#include "MenuScreen.h"
MenuScreen::MenuScreen(ModelData& data, GameScreen& game, sf::Font& font) :
data data ,
game game ,
font font
playButton.setPosition(bruglesco::play_button_x, bruglesco::play_button_y);
playButton.setFillColor(sf::Color(120, 120, 120, 255));
playString.setPosition(bruglesco::play_button_x + bruglesco::play_string_x_offset, bruglesco::play_button_y + bruglesco::play_string_y_offset);
playString.setCharacterSize(150);
playString.setFillColor(sf::Color::Black);
sixPairs.setPosition(bruglesco::six_button_x, bruglesco::pair_button_y);
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setPosition(bruglesco::six_button_x + bruglesco::six_string_x_offset, bruglesco::pair_button_y);
sixString.setCharacterSize(100);
sixString.setFillColor(sf::Color::Red);
pairString[0].setPosition(bruglesco::six_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[0].setCharacterSize(100);
pairString[0].setFillColor(sf::Color::Red);
eightPairs.setPosition(bruglesco::eight_button_x, bruglesco::pair_button_y);
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setPosition(bruglesco::eight_button_x + bruglesco::eight_string_x_offset, bruglesco::pair_button_y);
eightString.setCharacterSize(100);
eightString.setFillColor(sf::Color::Black);
pairString[1].setPosition(bruglesco::eight_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[1].setCharacterSize(100);
pairString[1].setFillColor(sf::Color::Black);
twelvePairs.setPosition(bruglesco::twelve_button_x, bruglesco::pair_button_y);
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setPosition(bruglesco::twelve_button_x + bruglesco::twelve_string_x_offset, bruglesco::pair_button_y);
twelveString.setCharacterSize(100);
twelveString.setFillColor(sf::Color::Black);
pairString[2].setPosition(bruglesco::twelve_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[2].setCharacterSize(100);
pairString[2].setFillColor(sf::Color::Black);
sixteenPairs.setPosition(bruglesco::sixteen_button_x, bruglesco::pair_button_y);
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setPosition(bruglesco::sixteen_button_x + bruglesco::sixteen_string_x_offset, bruglesco::pair_button_y);
sixteenString.setCharacterSize(100);
sixteenString.setFillColor(sf::Color::Black);
pairString[3].setPosition(bruglesco::sixteen_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[3].setCharacterSize(100);
pairString[3].setFillColor(sf::Color::Black);
void MenuScreen::trackMouse(const sf::Vector2f& mousePos)
if (playButton.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::play;
else if (sixPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::six;
else if (eightPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::eight;
else if (twelvePairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::twelve;
else if (sixteenPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::sixteen;
else
mouseIn = bruglesco::menuMouseIn::none;
void MenuScreen::input(const sf::Vector2f& mousePos)
if (playButton.getGlobalBounds().contains(mousePos))
data.setSize(deckSize);
data.play();
game.setGame();
mouseIn = bruglesco::menuMouseIn::none;
else if (sixPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::six;
data.setSize(deckSize);
else if (eightPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::eight;
data.setSize(deckSize);
else if (twelvePairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::twelve;
data.setSize(deckSize);
else if (sixteenPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::sixteen;
data.setSize(deckSize);
void MenuScreen::update()
highlightButton();
void MenuScreen::draw(sf::RenderWindow& window)
window.draw(playButton);
window.draw(playString);
window.draw(sixPairs);
window.draw(sixString);
window.draw(eightPairs);
window.draw(eightString);
window.draw(twelvePairs);
window.draw(twelveString);
window.draw(sixteenPairs);
window.draw(sixteenString);
for (std::vector<sf::Text>::iterator string = pairString.begin(); string != pairString.end(); ++string)
window.draw(*string);
void MenuScreen::highlightButton()
if (mouseIn == bruglesco::menuMouseIn::play)
playButton.setFillColor(sf::Color(60, 60, 60, 255));
playString.setFillColor(sf::Color::Red);
else
playButton.setFillColor(sf::Color(120, 120, 120, 255));
playString.setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::six)
sixPairs.setFillColor(sf::Color(60, 60, 60, 255));
sixString.setFillColor(sf::Color::Red);
pairString[0].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::six)
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setFillColor(sf::Color::Red);
pairString[0].setFillColor(sf::Color::Red);
else
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setFillColor(sf::Color::Black);
pairString[0].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::eight)
eightPairs.setFillColor(sf::Color(60, 60, 60, 255));
eightString.setFillColor(sf::Color::Red);
pairString[1].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::eight)
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setFillColor(sf::Color::Red);
pairString[1].setFillColor(sf::Color::Red);
else
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setFillColor(sf::Color::Black);
pairString[1].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::twelve)
twelvePairs.setFillColor(sf::Color(60, 60, 60, 255));
twelveString.setFillColor(sf::Color::Red);
pairString[2].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::twelve)
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setFillColor(sf::Color::Red);
pairString[2].setFillColor(sf::Color::Red);
else
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setFillColor(sf::Color::Black);
pairString[2].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::sixteen)
sixteenPairs.setFillColor(sf::Color(60, 60, 60, 255));
sixteenString.setFillColor(sf::Color::Red);
pairString[3].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::sixteen)
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setFillColor(sf::Color::Red);
pairString[3].setFillColor(sf::Color::Red);
else
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setFillColor(sf::Color::Black);
pairString[3].setFillColor(sf::Color::Black);
Next is the GameScreen class. Here is the meat of the presentation logic. The HUDisplay class is a subsection that updates the players score as strings to display them. It also maintains a space to draw matched cards in a stack in the players "hand".
HUDisplay.h
#ifndef BRUGLESCO_MEMORY_HUDISPLAY_H
#define BRUGLESCO_MEMORY_HUDISPLAY_H
#include "Expressions.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
#include <string>
class HUDisplay
public:
HUDisplay(ModelData& data, sf::Font& font);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
sf::Font& font;
sf::RectangleShape playerOneDisplay sf::Vector2f(bruglesco::display_width, bruglesco::display_height) ;
sf::RectangleShape playerTwoDisplay sf::Vector2f(bruglesco::display_width, bruglesco::display_height) ;
unsigned playerOneScore 0 ;
sf::Text playerOne "Player One: " + std::to_string(playerOneScore), font ;
unsigned playerTwoScore 0 ;
sf::Text playerTwo "Player Two: " + std::to_string(playerTwoScore), font ;
;
#endif // !BRUGLESCO_MEMORY_HUDISPLAY_H
HUDisplay.cpp
#include "HUDisplay.h"
HUDisplay::HUDisplay(ModelData& data, sf::Font& font) :
data data ,
font font
playerOneDisplay.setPosition(bruglesco::display_x, bruglesco::player_one_display_y);
playerOneDisplay.setFillColor(sf::Color(120, 120, 120, 255));
playerTwoDisplay.setPosition(bruglesco::display_x, bruglesco::player_two_display_y);
playerTwoDisplay.setFillColor(sf::Color(120, 120, 120, 255));
playerOne.setPosition(bruglesco::display_x + bruglesco::display_offset, bruglesco::player_one_display_y);
playerOne.setCharacterSize(50);
playerOne.setFillColor(sf::Color::Red);
playerTwo.setPosition(bruglesco::display_x + bruglesco::display_offset, bruglesco::player_two_display_y);
playerTwo.setCharacterSize(50);
playerTwo.setFillColor(sf::Color::Black);
void HUDisplay::update()
if (data.playerOneTurn())
playerOneScore = data.getPlayer().getScore();
playerOne.setString("Player One: " + std::to_string(playerOneScore));
playerOne.setFillColor(sf::Color::Red);
playerTwo.setFillColor(sf::Color::Black);
else
playerTwoScore = data.getPlayer().getScore();
playerTwo.setString("Player Two: " + std::to_string(playerTwoScore));
playerOne.setFillColor(sf::Color::Black);
playerTwo.setFillColor(sf::Color::Red);
void HUDisplay::draw(sf::RenderWindow& window)
window.draw(playerOneDisplay);
window.draw(playerOne);
window.draw(playerTwoDisplay);
window.draw(playerTwo);
The trackMouse method here works the same as in the Menu. The input method is extended to registering clicks on card images. The logic is then passed over to the Model class which is maintained as a visitor to the View and passed in as a dependency. The positionCards function is able to position the cards dynamically based on the size of deck the player chose on the menu screen. The makeCards function creates the number of display objects based on deck size.
The matchFailDelay is an attempt at keeping the images displayed for a short period of time after a failed match. endFailDelay updates the Model so the display can update accordingly. moveMatched sets the position of matched cards according to the player who made the match and how many they've made. adjustTexture is done this way to consistently vary the images used and to swap back and forth between face up and face down.
GameScreen.h
#ifndef BRUGLESCO_MEMORY_GAMESCREEN_H
#define BRUGLESCO_MEMORY_GAMESCREEN_H
#include "Card.h"
#include "Expressions.h"
#include "HUDisplay.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
#include <vector>
class GameScreen
public:
GameScreen(ModelData& data, sf::Font& font);
void setGame();
void trackMouse(const sf::Vector2f& mousePos);
void input(const sf::Vector2f& mousePos);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
sf::Font& font;
sf::Texture cardMap;
sf::RectangleShape pauseButton sf::Vector2f(bruglesco::game_button_width, bruglesco::game_button_height) ;
sf::Text pauseString "Pause", font ;
sf::RectangleShape returnToMain sf::Vector2f(bruglesco::game_button_width, bruglesco::game_button_height) ;
sf::Text resetString "Return", font ;
HUDisplay hud data, font ;
sf::RectangleShape pauseForeground sf::Vector2f(static_cast<float>(bruglesco::screen_width), static_cast<float>(bruglesco::screen_height)) ;
sf::RectangleShape endGameForeground sf::Vector2f(bruglesco::win_width, bruglesco::win_height) ;
sf::Text winString "Winner", font ;
sf::Text playerWinString "", font ;
std::vector<sf::RectangleShape> deck;
bruglesco::gameMouseIn mouseIn bruglesco::gameMouseIn::none ;
bool paused false ;
std::vector<unsigned> imageIdentifiers;
unsigned delay 0 ;
void makeCards();
void positionCards();
void highlightButtons();
void matchFailDelay();
void endFailDelay();
void moveMatched();
void adjustTexture();
;
#endif // !BRUGLESCO_MEMORY_GAMESCREEN_H
GameScreen.cpp
#include "GameScreen.h"
GameScreen::GameScreen(ModelData& data, sf::Font& font) :
data data ,
font font
if (!cardMap.loadFromFile("spritesheet.png"))
bruglesco::debuggStream << "you aren't loading your textures"; // remove for release
pauseButton.setPosition(bruglesco::pause_x, 0);
pauseButton.setFillColor(sf::Color(120, 120, 120, 255));
pauseString.setPosition(bruglesco::pause_x + bruglesco::pause_offset, 0);
pauseString.setCharacterSize(50);
pauseString.setFillColor(sf::Color::Black);
returnToMain.setPosition(bruglesco::reset_x, 0);
returnToMain.setFillColor(sf::Color(120, 120, 120, 255));
resetString.setPosition(bruglesco::reset_x + bruglesco::reset_offset, 0);
resetString.setCharacterSize(50);
resetString.setFillColor(sf::Color::Black);
pauseForeground.setFillColor(sf::Color(120, 120, 120, 120));
endGameForeground.setPosition(bruglesco::win_x, bruglesco::win_y);
endGameForeground.setFillColor(sf::Color(90, 90, 90, 180));
winString.setPosition(bruglesco::winstring_x, bruglesco::winstring_y);
winString.setCharacterSize(250);
winString.setFillColor(sf::Color::Green);
playerWinString.setPosition(bruglesco::player_winstring_x, bruglesco::player_winstring_y);
playerWinString.setCharacterSize(250);
playerWinString.setFillColor(sf::Color::Green);
for (unsigned i = 0; i < bruglesco::max_pairs; ++i)
imageIdentifiers.push_back(i);
void GameScreen::setGame()
std::shuffle(std::begin(imageIdentifiers), std::end(imageIdentifiers), bruglesco::generator);
makeCards();
positionCards();
void GameScreen::trackMouse(const sf::Vector2f& mousePos)
if (pauseButton.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::gameMouseIn::pause;
else if (returnToMain.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::gameMouseIn::reset;
else
mouseIn = bruglesco::gameMouseIn::none;
void GameScreen::input(const sf::Vector2f& mousePos)
if (pauseButton.getGlobalBounds().contains(mousePos))
if (!data.isOver())
paused = !paused;
else if (!paused && returnToMain.getGlobalBounds().contains(mousePos))
data.quit();
else if (!paused && delay == 0)
for (std::pair<std::vector<Card>::iterator, std::vector<sf::RectangleShape>::iterator> card(data.getDeck().begin(), deck.begin());
card.first != data.getDeck().end() && card.second != deck.end(); ++card.first, ++card.second)
if (card.second->getGlobalBounds().contains(mousePos))
card.first->flip();
void GameScreen::update()
highlightButtons();
matchFailDelay();
moveMatched();
adjustTexture();
hud.update();
if (delay == 1)
endFailDelay();
if (delay > 0)
--delay;
if (data.isOver())
playerWinString.setString("Player " + std::to_string(data.getPlayer().getIdentity()));
void GameScreen::draw(sf::RenderWindow& window)
window.draw(pauseButton);
window.draw(pauseString);
window.draw(returnToMain);
window.draw(resetString);
hud.draw(window);
for (std::vector<sf::RectangleShape>::iterator card = deck.begin(); card != deck.end(); ++card)
window.draw(*card);
if (paused)
window.draw(pauseForeground);
if (data.isOver())
window.draw(endGameForeground);
window.draw(winString);
window.draw(playerWinString);
void GameScreen::makeCards()
deck.clear();
unsigned k = 0;
for (std::vector<Card>::iterator card = data.getDeck().begin(); card != data.getDeck().end(); ++card)
deck.push_back(sf::RectangleShape(sf::Vector2f(bruglesco::card_width, bruglesco::card_height)));
deck[k].setTexture(&cardMap);
deck[k].setTextureRect(sf::IntRect(2048, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
++k;
void GameScreen::positionCards()
unsigned rows = 0;
for (unsigned i = 2; i * i <= deck.size(); ++i)
if (deck.size() % i == 0)
rows = i;
unsigned columns = deck.size() / rows;
float padding_x = (bruglesco::canvas_width - columns * 128.f) / (columns + 1);
float padding_y = (bruglesco::canvas_height - rows * 128.f) / (rows + 1);
float x = padding_x;
float y = padding_y;
unsigned counter = 1;
for (std::vector<sf::RectangleShape>::iterator card = deck.begin(); card != deck.end(); ++card)
card->setPosition(x, y);
if (counter < columns)
++counter;
x += padding_x + bruglesco::card_width;
else
counter = 1;
x = padding_x;
y += padding_y + bruglesco::card_height;
void GameScreen::highlightButtons()
mouseIn == bruglesco::gameMouseIn::pause)
pauseButton.setFillColor(sf::Color(60, 60, 60, 255));
pauseString.setFillColor(sf::Color::Red);
else
pauseButton.setFillColor(sf::Color(120, 120, 120, 255));
pauseString.setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::gameMouseIn::reset)
returnToMain.setFillColor(sf::Color(60, 60, 60, 255));
resetString.setFillColor(sf::Color::Red);
else
returnToMain.setFillColor(sf::Color(120, 120, 120, 255));
resetString.setFillColor(sf::Color::Black);
void GameScreen::matchFailDelay()
if (delay == 0 && data.getFailedCards().size() == 2)
delay = 240;
void GameScreen::endFailDelay()
data.resetDeck();
void GameScreen::moveMatched()
if (data.getMatchedCards().size() == 2)
float x = bruglesco::player_card_x + bruglesco::player_card_x_offset * data.getPlayer().getScore();
float y;
if (data.playerOneTurn())
y = bruglesco::player_one_card_y;
else
y = bruglesco::player_two_card_y;
for (unsigned i = 0; i < data.getMatchedCards().size(); ++i)
deck[data.getMatchedCards()[i]].setPosition(x, y);
data.resetDeck();
void GameScreen::adjustTexture()
for (unsigned i = 0; i < data.getDeck().size(); ++i)
if (data.getDeck()[i].checkState() == bruglesco::CardState::unmatched)
deck[i].setTextureRect(sf::IntRect(2048, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
else
deck[i].setTextureRect(sf::IntRect(imageIdentifiers[data.getDeck()[i].getMatch()] * 128, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
for (unsigned i = 0; i < data.getFailedCards().size(); ++i)
deck[data.getFailedCards()[i]].setTextureRect(sf::IntRect(imageIdentifiers[data.getDeck()[data.getFailedCards()[i]].getMatch()] * 128, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
I tried to be a little more thorough in my description of my code this time around but that is not a skill I am great at yet. I hope that if you need any further explanation you will leave a comment and I will provide it.
If I have any particular concerns for the code it would be the GameScreen class. It just seems to be large enough to worry. Should I try to abstract it further? any suggestions how? Of course I want any suggestions on any aspect of the code.
c++ object-oriented game mvvm sfml
This is the 2nd half of my Memory game. The first half can be found here. The repository can be found on Github.
I am using SFML 2.4.2. In this project I focused on a few key aspects of design and development.
- Top-down Design
- DRY & SRP
- Encapsulation & Abstraction
In an effort to eliminate magic numbers I shoved all of my constexpr as well as my enums, a static logger and a static PRNG in a single header file. I don't know if this approach is best practice or not and would appreciate input. On one hand I could move the expressions to the appropriate headers where they are localized, but many of them are used repeatedly throughout the codebase. Should I localize the values that are used once and only keep a header file for the reused items (PRNG, Enums, etc...)?
note: in the previous answer it was recommended to me to use double over float. I chose not to do this as SFML takes floats for its parameters and the number of casts made the code unreadable. I could let the computer narrow implicitly but then I would need to turn of wX which I prefer to keep on. It would also lead to a significant number of warnings.
Expressions.h
#ifndef BRUGLESCO_MEMORY_EXPRESSIONS_H
#define BRUGLESCO_MEMORY_EXPRESSIONS_H
#include <fstream>
#include <random>
namespace bruglesco
static std::random_device rd;
static std::mt19937 generator(rd());
static std::ofstream debuggStream("DebugLog.txt"); // remove for release
constexpr unsigned screen_width = 1500u;
constexpr unsigned screen_height = 800u;
constexpr float menu_button_width = 256.f;
constexpr float menu_button_height = 256.f;
constexpr float play_button_x = 622.f;
constexpr float play_button_y = 96.f;
constexpr float six_button_x = 95.f;
constexpr float eight_button_x = 446.f;
constexpr float twelve_button_x = 797.f;
constexpr float sixteen_button_x = 1148.f;
constexpr float pair_button_y = 448.f;
constexpr float play_string_x_offset = 18.f;
constexpr float play_string_y_offset = 28.f;
constexpr float six_string_x_offset = 80.f;
constexpr float eight_string_x_offset = 40.f;
constexpr float twelve_string_x_offset = 15.f;
constexpr float sixteen_string_x_offset = 8.f;
constexpr float pair_string_x_offset = 40.f;
constexpr float pair_string_y_offset = 100.f;
constexpr float game_button_width = 128.f;
constexpr float game_button_height = 64.f;
constexpr float pause_x = 1244.f;
constexpr float reset_x = 1372.f;
constexpr float pause_offset = 12.f;
constexpr float reset_offset = 5.f;
constexpr float display_x = 1244.f;
constexpr float player_one_display_y = 64.f;
constexpr float player_two_display_y = 432.f;
constexpr float display_offset = 20.f;
constexpr float display_width = 256.f;
constexpr float display_height = 368.f;
constexpr float canvas_width = screen_width - display_width;
constexpr float canvas_height = static_cast<float>(screen_height);
constexpr float player_string_x = 1265.f;
constexpr float player_one_string_y = 72.f;
constexpr float player_two_string_y = 440.f;
constexpr float player_card_x = 1265.f;
constexpr float player_card_x_offset = 15.f;
constexpr float player_one_card_y = 120.f;
constexpr float player_two_card_y = 488.f;
constexpr float card_width = 128.f;
constexpr float card_height = 128.f;
constexpr unsigned max_pairs = 16u;
constexpr float win_width = screen_width - 300.f;
constexpr float win_height = screen_height - 150.f;
constexpr float win_x = 150.f;
constexpr float win_y = 75.f;
constexpr float winstring_x = 400.f;
constexpr float winstring_y = 100.f;
constexpr float player_winstring_x = winstring_x - 50.f;
constexpr float player_winstring_y = winstring_y + 200.f;
enum class DeckSize
six = 6,
eight = 8,
twelve = 12,
sixteen = 16
;
enum class CardState
unmatched,
checking,
matched
;
enum class menuMouseIn
none,
play,
six,
eight,
twelve,
sixteen
;
enum class gameMouseIn
none,
pause,
reset
;
enum class winState
none,
draw,
playerOne,
playerTwo
;
#endif // !BRUGLESCO_MEMORY_EXPRESSIONS_H
My main is intended to be small. Memory is the class that represents the entirety of the application.
Main.cpp
#include "Memory.h"
int main()
Memory memory;
memory.run();
The Memory class contains the ModelData and Viewport class. These classes represent the model logic and presentation logic respectively.
Memory.h
#ifndef BRUGLESCO_MEMORY_MEMORY_H
#define BRUGLESCO_MEMORY_MEMORY_H
#include "Expressions.h"
#include "ModelData.h"
#include "Viewport.h"
#include <SFMLGraphics.hpp>
class Memory
public:
void run();
private:
ModelData data;
Viewport view data ;
bool playing true ;
void input();
void update();
void draw();
;
#endif // !BRUGLESCO_MEMORY_MEMORY_H
The ModelData class was the main focus of the last post. In this one I'd like to focus on the Vieport
This class contains the window for rendering, as well as the MenuScreen and GameScreen classes. These are the only two screens in the program.
First the MenuScreen. It is a simple arrangement of buttons that are highlighted dynamically both on selection and mouseover. The trackMouse method is to check for mouseover, the input method responds to user input (in this case clicks), and the update method dynamically maintains your selected game size and highlights the buttons according to that selection and mouseover.
MenuScreen.h
#ifndef BRUGLESCO_MEMORY_MENUSCREEN_H
#define BRUGLESCO_MEMORY_MENUSCREEN_H
#include "Expressions.h"
#include "GameScreen.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
class MenuScreen
public:
MenuScreen(ModelData& data, GameScreen& game, sf::Font& font);
void trackMouse(const sf::Vector2f& mousePos);
void input(const sf::Vector2f& mousePos);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
GameScreen& game;
sf::Font& font;
sf::RectangleShape playButton sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text playString "Play", font ;
sf::RectangleShape sixPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text sixString "Six", font ;
sf::RectangleShape eightPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text eightString "Eight", font ;
sf::RectangleShape twelvePairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text twelveString "Twelve", font ;
sf::RectangleShape sixteenPairs sf::Vector2f(bruglesco::menu_button_width, bruglesco::menu_button_height) ;
sf::Text sixteenString "Sixteen", font ;
std::vector<sf::Text> pairString 4, "Pairs", font ;
bruglesco::menuMouseIn mouseIn bruglesco::menuMouseIn::none ;
bruglesco::DeckSize deckSize bruglesco::DeckSize::six ;
void highlightButton();
;
#endif // !BRUGLESCO_MEMORY_MENUSCREEN_H
MenuScreen.cpp
#include "MenuScreen.h"
MenuScreen::MenuScreen(ModelData& data, GameScreen& game, sf::Font& font) :
data data ,
game game ,
font font
playButton.setPosition(bruglesco::play_button_x, bruglesco::play_button_y);
playButton.setFillColor(sf::Color(120, 120, 120, 255));
playString.setPosition(bruglesco::play_button_x + bruglesco::play_string_x_offset, bruglesco::play_button_y + bruglesco::play_string_y_offset);
playString.setCharacterSize(150);
playString.setFillColor(sf::Color::Black);
sixPairs.setPosition(bruglesco::six_button_x, bruglesco::pair_button_y);
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setPosition(bruglesco::six_button_x + bruglesco::six_string_x_offset, bruglesco::pair_button_y);
sixString.setCharacterSize(100);
sixString.setFillColor(sf::Color::Red);
pairString[0].setPosition(bruglesco::six_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[0].setCharacterSize(100);
pairString[0].setFillColor(sf::Color::Red);
eightPairs.setPosition(bruglesco::eight_button_x, bruglesco::pair_button_y);
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setPosition(bruglesco::eight_button_x + bruglesco::eight_string_x_offset, bruglesco::pair_button_y);
eightString.setCharacterSize(100);
eightString.setFillColor(sf::Color::Black);
pairString[1].setPosition(bruglesco::eight_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[1].setCharacterSize(100);
pairString[1].setFillColor(sf::Color::Black);
twelvePairs.setPosition(bruglesco::twelve_button_x, bruglesco::pair_button_y);
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setPosition(bruglesco::twelve_button_x + bruglesco::twelve_string_x_offset, bruglesco::pair_button_y);
twelveString.setCharacterSize(100);
twelveString.setFillColor(sf::Color::Black);
pairString[2].setPosition(bruglesco::twelve_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[2].setCharacterSize(100);
pairString[2].setFillColor(sf::Color::Black);
sixteenPairs.setPosition(bruglesco::sixteen_button_x, bruglesco::pair_button_y);
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setPosition(bruglesco::sixteen_button_x + bruglesco::sixteen_string_x_offset, bruglesco::pair_button_y);
sixteenString.setCharacterSize(100);
sixteenString.setFillColor(sf::Color::Black);
pairString[3].setPosition(bruglesco::sixteen_button_x + bruglesco::pair_string_x_offset, bruglesco::pair_button_y + bruglesco::pair_string_y_offset);
pairString[3].setCharacterSize(100);
pairString[3].setFillColor(sf::Color::Black);
void MenuScreen::trackMouse(const sf::Vector2f& mousePos)
if (playButton.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::play;
else if (sixPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::six;
else if (eightPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::eight;
else if (twelvePairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::twelve;
else if (sixteenPairs.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::menuMouseIn::sixteen;
else
mouseIn = bruglesco::menuMouseIn::none;
void MenuScreen::input(const sf::Vector2f& mousePos)
if (playButton.getGlobalBounds().contains(mousePos))
data.setSize(deckSize);
data.play();
game.setGame();
mouseIn = bruglesco::menuMouseIn::none;
else if (sixPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::six;
data.setSize(deckSize);
else if (eightPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::eight;
data.setSize(deckSize);
else if (twelvePairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::twelve;
data.setSize(deckSize);
else if (sixteenPairs.getGlobalBounds().contains(mousePos))
deckSize = bruglesco::DeckSize::sixteen;
data.setSize(deckSize);
void MenuScreen::update()
highlightButton();
void MenuScreen::draw(sf::RenderWindow& window)
window.draw(playButton);
window.draw(playString);
window.draw(sixPairs);
window.draw(sixString);
window.draw(eightPairs);
window.draw(eightString);
window.draw(twelvePairs);
window.draw(twelveString);
window.draw(sixteenPairs);
window.draw(sixteenString);
for (std::vector<sf::Text>::iterator string = pairString.begin(); string != pairString.end(); ++string)
window.draw(*string);
void MenuScreen::highlightButton()
if (mouseIn == bruglesco::menuMouseIn::play)
playButton.setFillColor(sf::Color(60, 60, 60, 255));
playString.setFillColor(sf::Color::Red);
else
playButton.setFillColor(sf::Color(120, 120, 120, 255));
playString.setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::six)
sixPairs.setFillColor(sf::Color(60, 60, 60, 255));
sixString.setFillColor(sf::Color::Red);
pairString[0].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::six)
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setFillColor(sf::Color::Red);
pairString[0].setFillColor(sf::Color::Red);
else
sixPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixString.setFillColor(sf::Color::Black);
pairString[0].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::eight)
eightPairs.setFillColor(sf::Color(60, 60, 60, 255));
eightString.setFillColor(sf::Color::Red);
pairString[1].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::eight)
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setFillColor(sf::Color::Red);
pairString[1].setFillColor(sf::Color::Red);
else
eightPairs.setFillColor(sf::Color(120, 120, 120, 255));
eightString.setFillColor(sf::Color::Black);
pairString[1].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::twelve)
twelvePairs.setFillColor(sf::Color(60, 60, 60, 255));
twelveString.setFillColor(sf::Color::Red);
pairString[2].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::twelve)
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setFillColor(sf::Color::Red);
pairString[2].setFillColor(sf::Color::Red);
else
twelvePairs.setFillColor(sf::Color(120, 120, 120, 255));
twelveString.setFillColor(sf::Color::Black);
pairString[2].setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::menuMouseIn::sixteen)
sixteenPairs.setFillColor(sf::Color(60, 60, 60, 255));
sixteenString.setFillColor(sf::Color::Red);
pairString[3].setFillColor(sf::Color::Red);
else if (deckSize == bruglesco::DeckSize::sixteen)
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setFillColor(sf::Color::Red);
pairString[3].setFillColor(sf::Color::Red);
else
sixteenPairs.setFillColor(sf::Color(120, 120, 120, 255));
sixteenString.setFillColor(sf::Color::Black);
pairString[3].setFillColor(sf::Color::Black);
Next is the GameScreen class. Here is the meat of the presentation logic. The HUDisplay class is a subsection that updates the players score as strings to display them. It also maintains a space to draw matched cards in a stack in the players "hand".
HUDisplay.h
#ifndef BRUGLESCO_MEMORY_HUDISPLAY_H
#define BRUGLESCO_MEMORY_HUDISPLAY_H
#include "Expressions.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
#include <string>
class HUDisplay
public:
HUDisplay(ModelData& data, sf::Font& font);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
sf::Font& font;
sf::RectangleShape playerOneDisplay sf::Vector2f(bruglesco::display_width, bruglesco::display_height) ;
sf::RectangleShape playerTwoDisplay sf::Vector2f(bruglesco::display_width, bruglesco::display_height) ;
unsigned playerOneScore 0 ;
sf::Text playerOne "Player One: " + std::to_string(playerOneScore), font ;
unsigned playerTwoScore 0 ;
sf::Text playerTwo "Player Two: " + std::to_string(playerTwoScore), font ;
;
#endif // !BRUGLESCO_MEMORY_HUDISPLAY_H
HUDisplay.cpp
#include "HUDisplay.h"
HUDisplay::HUDisplay(ModelData& data, sf::Font& font) :
data data ,
font font
playerOneDisplay.setPosition(bruglesco::display_x, bruglesco::player_one_display_y);
playerOneDisplay.setFillColor(sf::Color(120, 120, 120, 255));
playerTwoDisplay.setPosition(bruglesco::display_x, bruglesco::player_two_display_y);
playerTwoDisplay.setFillColor(sf::Color(120, 120, 120, 255));
playerOne.setPosition(bruglesco::display_x + bruglesco::display_offset, bruglesco::player_one_display_y);
playerOne.setCharacterSize(50);
playerOne.setFillColor(sf::Color::Red);
playerTwo.setPosition(bruglesco::display_x + bruglesco::display_offset, bruglesco::player_two_display_y);
playerTwo.setCharacterSize(50);
playerTwo.setFillColor(sf::Color::Black);
void HUDisplay::update()
if (data.playerOneTurn())
playerOneScore = data.getPlayer().getScore();
playerOne.setString("Player One: " + std::to_string(playerOneScore));
playerOne.setFillColor(sf::Color::Red);
playerTwo.setFillColor(sf::Color::Black);
else
playerTwoScore = data.getPlayer().getScore();
playerTwo.setString("Player Two: " + std::to_string(playerTwoScore));
playerOne.setFillColor(sf::Color::Black);
playerTwo.setFillColor(sf::Color::Red);
void HUDisplay::draw(sf::RenderWindow& window)
window.draw(playerOneDisplay);
window.draw(playerOne);
window.draw(playerTwoDisplay);
window.draw(playerTwo);
The trackMouse method here works the same as in the Menu. The input method is extended to registering clicks on card images. The logic is then passed over to the Model class which is maintained as a visitor to the View and passed in as a dependency. The positionCards function is able to position the cards dynamically based on the size of deck the player chose on the menu screen. The makeCards function creates the number of display objects based on deck size.
The matchFailDelay is an attempt at keeping the images displayed for a short period of time after a failed match. endFailDelay updates the Model so the display can update accordingly. moveMatched sets the position of matched cards according to the player who made the match and how many they've made. adjustTexture is done this way to consistently vary the images used and to swap back and forth between face up and face down.
GameScreen.h
#ifndef BRUGLESCO_MEMORY_GAMESCREEN_H
#define BRUGLESCO_MEMORY_GAMESCREEN_H
#include "Card.h"
#include "Expressions.h"
#include "HUDisplay.h"
#include "ModelData.h"
#include <SFMLGraphics.hpp>
#include <vector>
class GameScreen
public:
GameScreen(ModelData& data, sf::Font& font);
void setGame();
void trackMouse(const sf::Vector2f& mousePos);
void input(const sf::Vector2f& mousePos);
void update();
void draw(sf::RenderWindow& window);
private:
ModelData& data;
sf::Font& font;
sf::Texture cardMap;
sf::RectangleShape pauseButton sf::Vector2f(bruglesco::game_button_width, bruglesco::game_button_height) ;
sf::Text pauseString "Pause", font ;
sf::RectangleShape returnToMain sf::Vector2f(bruglesco::game_button_width, bruglesco::game_button_height) ;
sf::Text resetString "Return", font ;
HUDisplay hud data, font ;
sf::RectangleShape pauseForeground sf::Vector2f(static_cast<float>(bruglesco::screen_width), static_cast<float>(bruglesco::screen_height)) ;
sf::RectangleShape endGameForeground sf::Vector2f(bruglesco::win_width, bruglesco::win_height) ;
sf::Text winString "Winner", font ;
sf::Text playerWinString "", font ;
std::vector<sf::RectangleShape> deck;
bruglesco::gameMouseIn mouseIn bruglesco::gameMouseIn::none ;
bool paused false ;
std::vector<unsigned> imageIdentifiers;
unsigned delay 0 ;
void makeCards();
void positionCards();
void highlightButtons();
void matchFailDelay();
void endFailDelay();
void moveMatched();
void adjustTexture();
;
#endif // !BRUGLESCO_MEMORY_GAMESCREEN_H
GameScreen.cpp
#include "GameScreen.h"
GameScreen::GameScreen(ModelData& data, sf::Font& font) :
data data ,
font font
if (!cardMap.loadFromFile("spritesheet.png"))
bruglesco::debuggStream << "you aren't loading your textures"; // remove for release
pauseButton.setPosition(bruglesco::pause_x, 0);
pauseButton.setFillColor(sf::Color(120, 120, 120, 255));
pauseString.setPosition(bruglesco::pause_x + bruglesco::pause_offset, 0);
pauseString.setCharacterSize(50);
pauseString.setFillColor(sf::Color::Black);
returnToMain.setPosition(bruglesco::reset_x, 0);
returnToMain.setFillColor(sf::Color(120, 120, 120, 255));
resetString.setPosition(bruglesco::reset_x + bruglesco::reset_offset, 0);
resetString.setCharacterSize(50);
resetString.setFillColor(sf::Color::Black);
pauseForeground.setFillColor(sf::Color(120, 120, 120, 120));
endGameForeground.setPosition(bruglesco::win_x, bruglesco::win_y);
endGameForeground.setFillColor(sf::Color(90, 90, 90, 180));
winString.setPosition(bruglesco::winstring_x, bruglesco::winstring_y);
winString.setCharacterSize(250);
winString.setFillColor(sf::Color::Green);
playerWinString.setPosition(bruglesco::player_winstring_x, bruglesco::player_winstring_y);
playerWinString.setCharacterSize(250);
playerWinString.setFillColor(sf::Color::Green);
for (unsigned i = 0; i < bruglesco::max_pairs; ++i)
imageIdentifiers.push_back(i);
void GameScreen::setGame()
std::shuffle(std::begin(imageIdentifiers), std::end(imageIdentifiers), bruglesco::generator);
makeCards();
positionCards();
void GameScreen::trackMouse(const sf::Vector2f& mousePos)
if (pauseButton.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::gameMouseIn::pause;
else if (returnToMain.getGlobalBounds().contains(mousePos))
mouseIn = bruglesco::gameMouseIn::reset;
else
mouseIn = bruglesco::gameMouseIn::none;
void GameScreen::input(const sf::Vector2f& mousePos)
if (pauseButton.getGlobalBounds().contains(mousePos))
if (!data.isOver())
paused = !paused;
else if (!paused && returnToMain.getGlobalBounds().contains(mousePos))
data.quit();
else if (!paused && delay == 0)
for (std::pair<std::vector<Card>::iterator, std::vector<sf::RectangleShape>::iterator> card(data.getDeck().begin(), deck.begin());
card.first != data.getDeck().end() && card.second != deck.end(); ++card.first, ++card.second)
if (card.second->getGlobalBounds().contains(mousePos))
card.first->flip();
void GameScreen::update()
highlightButtons();
matchFailDelay();
moveMatched();
adjustTexture();
hud.update();
if (delay == 1)
endFailDelay();
if (delay > 0)
--delay;
if (data.isOver())
playerWinString.setString("Player " + std::to_string(data.getPlayer().getIdentity()));
void GameScreen::draw(sf::RenderWindow& window)
window.draw(pauseButton);
window.draw(pauseString);
window.draw(returnToMain);
window.draw(resetString);
hud.draw(window);
for (std::vector<sf::RectangleShape>::iterator card = deck.begin(); card != deck.end(); ++card)
window.draw(*card);
if (paused)
window.draw(pauseForeground);
if (data.isOver())
window.draw(endGameForeground);
window.draw(winString);
window.draw(playerWinString);
void GameScreen::makeCards()
deck.clear();
unsigned k = 0;
for (std::vector<Card>::iterator card = data.getDeck().begin(); card != data.getDeck().end(); ++card)
deck.push_back(sf::RectangleShape(sf::Vector2f(bruglesco::card_width, bruglesco::card_height)));
deck[k].setTexture(&cardMap);
deck[k].setTextureRect(sf::IntRect(2048, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
++k;
void GameScreen::positionCards()
unsigned rows = 0;
for (unsigned i = 2; i * i <= deck.size(); ++i)
if (deck.size() % i == 0)
rows = i;
unsigned columns = deck.size() / rows;
float padding_x = (bruglesco::canvas_width - columns * 128.f) / (columns + 1);
float padding_y = (bruglesco::canvas_height - rows * 128.f) / (rows + 1);
float x = padding_x;
float y = padding_y;
unsigned counter = 1;
for (std::vector<sf::RectangleShape>::iterator card = deck.begin(); card != deck.end(); ++card)
card->setPosition(x, y);
if (counter < columns)
++counter;
x += padding_x + bruglesco::card_width;
else
counter = 1;
x = padding_x;
y += padding_y + bruglesco::card_height;
void GameScreen::highlightButtons()
mouseIn == bruglesco::gameMouseIn::pause)
pauseButton.setFillColor(sf::Color(60, 60, 60, 255));
pauseString.setFillColor(sf::Color::Red);
else
pauseButton.setFillColor(sf::Color(120, 120, 120, 255));
pauseString.setFillColor(sf::Color::Black);
if (mouseIn == bruglesco::gameMouseIn::reset)
returnToMain.setFillColor(sf::Color(60, 60, 60, 255));
resetString.setFillColor(sf::Color::Red);
else
returnToMain.setFillColor(sf::Color(120, 120, 120, 255));
resetString.setFillColor(sf::Color::Black);
void GameScreen::matchFailDelay()
if (delay == 0 && data.getFailedCards().size() == 2)
delay = 240;
void GameScreen::endFailDelay()
data.resetDeck();
void GameScreen::moveMatched()
if (data.getMatchedCards().size() == 2)
float x = bruglesco::player_card_x + bruglesco::player_card_x_offset * data.getPlayer().getScore();
float y;
if (data.playerOneTurn())
y = bruglesco::player_one_card_y;
else
y = bruglesco::player_two_card_y;
for (unsigned i = 0; i < data.getMatchedCards().size(); ++i)
deck[data.getMatchedCards()[i]].setPosition(x, y);
data.resetDeck();
void GameScreen::adjustTexture()
for (unsigned i = 0; i < data.getDeck().size(); ++i)
if (data.getDeck()[i].checkState() == bruglesco::CardState::unmatched)
deck[i].setTextureRect(sf::IntRect(2048, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
else
deck[i].setTextureRect(sf::IntRect(imageIdentifiers[data.getDeck()[i].getMatch()] * 128, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
for (unsigned i = 0; i < data.getFailedCards().size(); ++i)
deck[data.getFailedCards()[i]].setTextureRect(sf::IntRect(imageIdentifiers[data.getDeck()[data.getFailedCards()[i]].getMatch()] * 128, 0, static_cast<int>(bruglesco::card_width), static_cast<int>(bruglesco::card_height)));
I tried to be a little more thorough in my description of my code this time around but that is not a skill I am great at yet. I hope that if you need any further explanation you will leave a comment and I will provide it.
If I have any particular concerns for the code it would be the GameScreen class. It just seems to be large enough to worry. Should I try to abstract it further? any suggestions how? Of course I want any suggestions on any aspect of the code.
c++ object-oriented game mvvm sfml
edited May 25 at 17:24
asked May 25 at 15:52
bruglesco
9321518
9321518
add a comment |Â
add a comment |Â
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
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%2f195167%2fviewport-for-card-match-game%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