Reputation: 242
I am creating a card game using .NET Standard and Monogame. I have created and tested the core of the game in a dll (PlayingCards.Common.Dll).
I now need to add a GUI layer and am having problems with the design.
For example, my card class lives in PlayingCards.Common.Dll. The GUI needs to use this player card but add a texture, position and possibly an update.
I am aware that I can use the decorator pattern and create a GuiCard that holds a Card and the extra functionality needed for the GUI however when my dealer class deals cards to users (As this is done in the core) they will only receive a Card and not a GuiCard.
It is also the Player class that holds a hand which contains x amount of cards so when drawing in the Gui I need to somehow draw all cards for all players....
Classes of interest:
public class Card
{
...
public Card(Suit suit, Value value)
{
Suit = suit;
Value = value;
...
}
}
public class Dealer
{
private readonly Deck _deck = new Deck();
...
private Card DealCard() => _deck.Draw();
}
//This is in GUI.DLL
public class CardEntity
{
...
private readonly Position _position;
private readonly Card _card = new Card(Suit.Spades, Value.King);
public CardEntity(GraphicsDevice graphicsDevice, Card card)
{
_position = new Position();
...
}
public void Draw(SpriteBatch spriteBatch)
{
var topLeftOfSprite = new Vector2(_position.X, _position.Y);
var sourceRectangle = new Rectangle
{
X = XPosOnSpriteSheet,
Y = YPosOnSpriteSheet,
Height = CardTextureHeight,
Width = CardTextureWidth - Offset
};
spriteBatch.Draw(_cardsSheetTexture, topLeftOfSprite, sourceRectangle, XnaColor.White);
}
}
Thank you.
Upvotes: 1
Views: 200
Reputation: 242
After reading about and testing multiple design patterns this weekend I have got this working using the Visitor Pattern.
My Card and Hand implement IVisitable and an Accept Method.
My visitor then visits all cards and holds all the logic to Draw the cards. This means I can separate the concerns of any object and the draw-able component completely and is nicely SOLID. This will also separate the update logic in the future as I will simply create a new UpdateVisitor.
The visitor code below, if anyone is interested:
using System.Collections.Generic;
...
namespace ...
{
public class CardDrawingVisitor : Visitor
{
private readonly SpriteBatch _spriteBatch;
private readonly PngHandler _cardPng;
private readonly Stack<Card> _result = new Stack<Card>();
public CardDrawingVisitor(GraphicsDevice graphicsDevice, SpriteBatch spriteBatch)
{
_spriteBatch = spriteBatch;
_cardPng = new PngHandler(graphicsDevice, "cards");
}
public override void Visit(Card card)
{
_result.Push(card);
}
public void Draw()
{
var cardNumber = 0;
foreach (var card in _result)
{
_cardPng.Draw(_spriteBatch, card, cardNumber++, 0);
}
}
}
}
Upvotes: 0
Reputation: 9957
There are many possible answers to design problems. It might be worth refining your question to be more specific if you want a good answer.
However, I did spot one thing that can be answered clearly.
I am aware that I can use the decorator pattern and create a GuiCard that holds a Card and the extra functionality needed for the GUI however when my dealer class deals cards to users (As this is done in the core) they will only receive a Card and not a GuiCard.
The thing you're missing from the decorator pattern is deriving from a base class. In most examples they use either an abstract base class or an interface.
So in your particular case the classes should look more like this:
public abstract class Card
{
public Card(Suit suit, Value value)
{
Suit = suit;
Value = value;
}
public abstract Suit Suit { get; }
public abstract Value Value { get; }
}
public class CardEntity : Card
{
private readonly Position _position;
private readonly Card _card;
public CardEntity(GraphicsDevice graphicsDevice, Card card)
{
_position = new Position();
_card = card;
}
public Suit Suit => _card.Suit;
public Value Value => _card.Value;
public void Draw(SpriteBatch spriteBatch)
{
var topLeftOfSprite = new Vector2(_position.X, _position.Y);
var sourceRectangle = new Rectangle
{
X = XPosOnSpriteSheet,
Y = YPosOnSpriteSheet,
Height = CardTextureHeight,
Width = CardTextureWidth - Offset
};
spriteBatch.Draw(_cardsSheetTexture, topLeftOfSprite, sourceRectangle, XnaColor.White);
}
}
Keep in mind, this will solve the problem by using the decorator pattern but it still might not be the right design choice. The decorator pattern comes with an overhead that ultimately forces you to implement considerably more code.
If you're looking to explore more design patterns that might be useful I read a great book a while back called Game Programming Patterns. The author even provides a web version for free :)
Upvotes: 1