3
\$\begingroup\$

I'm writing a card game (Dominion) as a pet project. I'm new to C++ but not programming.

A player has a deck, containing the hand and cards in play (tableau). Outside the player, there are piles of cards to buy from (supply piles). I want to display these objects (the hand, tableau and supply piles) on the screen. I had chosen to represent the hand and tableau using std::vector<Card> and the supply piles as std::vector<SupplyPile>. Card and SupplyPile implement an interface for displaying contents to the screen

class IInfo {
public:
  virtual std::string Info() const = 0;
  virtual std::string ToString() const = 0;
};

I have a class I'm calling View that will take on displaying things to the screen.

class View {
public:
  View(const std::vector<IInfo*>& items, int window_starty, int window_startx);
  virtual ~View() { }

  const IInfo& CurrentItem() const;
  const int CurrentIndex() const;
  bool IsEmpty() const;
  void ItemDown();
  void ItemUp();
  void SetActive();
  void SetInactive();
  virtual void Update();
protected:
  virtual WINDOW* InitializeWindow(int lines,
    int cols,
    int starty,
    int startx);

  virtual ITEM** MakeMenuItems();
private:
  // Some constants

  const std::vector<IInfo*>& items_;

  MENU *menu_;
  WINDOW *window_;
};

I now know that C++ doesn't support covariance in templates, so I can't create instances of View with the supply pile, hand, and tableau. How can I redesign the system to maximize DRY? The View class operates on its container member in a very simple way, ripe for abstraction.

Examples of things I want to do

std::vector<Card> hand = player.hand();
std::vector<Card> tableau = player.tableau();
std::vector<SupplyPile> supply_piles = game.supply_piles();

View hand_view = new View(hand, starty, startx);
View tableau_view = new View(tableau, starty, startx);
View supply_piles_view = new View(supply_piles, starty, startx);

One answer suggested making View into a template, and it almost worked until I remembered that I want to track which View is active at a given time:

View active_ = hand_view;
// Later
active_ = supply_piles_view;

I can't do this with View<Card> and View<SupplyPile>.

Other code

class Card : public IInfo {
public:
  Card(std::string name,
    int cost,
    int initial_supply,
    std::string text,
    std::string type);
  ~Card();

  void Play();
  std::string Info() const;
  std::string ToString() const;

  int cost() const;
  int initial_supply() const;
  std::string name() const;
  std::string text() const;
private:
  int cost_;
  int initial_supply_;

  std::string name_;
  std::string text_;
  std::string type_;

  std::string set_;
};

class SupplyPile : public IInfo {

public:
    SupplyPile(const Card& card, int initial_count);
    SupplyPile(const SupplyPile& other);
    virtual ~SupplyPile();
    virtual bool operator==(const SupplyPile& other) const;

    bool BuyOrGain();
    std::string Info() const;
    std::string ToString() const;

    const Card& card() const;
    int count() const;
    std::string name() const;
private:
    const Card& card_;

    int count_;
};
\$\endgroup\$
2
  • \$\begingroup\$ Your question isn't fully clear to me. If you create a std::vector<IInfo*> x; then you can do push_back(new Card(...), push_back(new SupplyPile(...)). Templates aren't covariant, no, so if you have template <typename T> class Foo then Foo<A> and Foo<B> are totally separate types for any A and B (even if B derives from A), but I don't see where that's an issue in any of your code. \$\endgroup\$
    – Yuushi
    Commented Feb 9, 2013 at 4:56
  • \$\begingroup\$ Updated with a snippet of code I wanted to write but can't. \$\endgroup\$ Commented Feb 11, 2013 at 19:39

2 Answers 2

1
\$\begingroup\$

Is there any reason why View itself can't be a template?

template <typename T> class View {
public:
  View(const std::vector<T>& items, int window_starty, int window_startx);
  virtual ~View() { }

  const T& CurrentItem() const;
  // ...
  const std::vector<T>& items_;
  // ...
};

View<Card> hand_view = new View<Card>(hand, starty, startx);
\$\endgroup\$
4
  • \$\begingroup\$ This had occurred to me, but I'm not sure if it will work. There is one method that needs to call ToString() on an element of items_. If I can do that, then I should be able to convert to a template. Perhaps a cast is in order? \$\endgroup\$ Commented Feb 12, 2013 at 2:19
  • \$\begingroup\$ Not a problem. items_[0].ToString() etc will work fine. \$\endgroup\$ Commented Feb 12, 2013 at 9:26
  • \$\begingroup\$ Still not there. I edited the original question to explain the new problem: I want to be able to track which View is active at a given moment. \$\endgroup\$ Commented Feb 12, 2013 at 20:01
  • 1
    \$\begingroup\$ You could derive View<T> from a base class/ interface, so you could maintain a reference to that instead. Or even just have an enumerated constant to represent it. At this point though, you're straying even further from the site remit... \$\endgroup\$ Commented Feb 13, 2013 at 11:22
0
\$\begingroup\$

I ended up doing something I don't much like, but it works.

I redesigned View to work with disposable copies of a new interface, IViewable. IViewable will be the link between the UI and the core logic. Every time a UI update is needed, copies of the items to display will be passed as std::vector<IViewable*>.

The new View ctor is:

  View(std::vector<IViewable*> *initial_items, int window_starty, int window_startx)

Updates need a new set of items to display each time, rather than using a reference to the originals like I wanted:

  void Update(std::vector<IViewable*> *items);
\$\endgroup\$

Your Answer

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

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