views:

565

answers:

3

Context: A card game; I want to deal out cards from a deck to each player in the game, in a clean way.

This is what I had in mind:

public static CardGame.IGame DealAll(this CardGame.IGame objThis, CardGame.Card[] cards)
    {
        if (objThis.Players.Length > 0)
        {
            for (int i = 0; i < cards.Length; i++)
            {
                objThis.Deck.MoveTo(cards[i], objThis.CurrentPlayer.Hand);

                objThis.AdvancePlayer();
            }
        }

        return objThis;
    }

public static Card[] MoveTo(this Card[] objThis, Card card, Card[] cards)
    {
        List<Card> lstCards = cards.ToList();
        List<Card> lstThis = objThis.ToList();

        lstThis.Remove(card);
        lstCards.Add(card);

        objThis = lstThis.ToArray();
        cards = lstCards.ToArray();

        return cards;
    }

Surely you can see the reference problems. Using the ref keyword leads to some not-so-nice looking code, but it may be unavoidable. Any suggestions?

I would prefer a solution that is flexible enough to handle other "card-passing" situations (a player playing a card to the pile, moving cards from the pile to a "trash" deck, etc.).

+1  A: 

Well, one simple way is not to use arrays in the first place. Use lists from the start and you don't need to reallocated etc - just remove from the deck and add to the hand. You might want to consider using a Queue<T> for the deck though.

The more functional way would be to use immutable collections and ref parameters, but that's not terribly practical without some good immutable collection classes behind you. (They're available, but not built into the framework.)

Why are you passing the array of cards into the method though? Shouldn't it just deal everything from the deck? At that point it's easier to write:

foreach (Card card in deck)
{
    CurrentPlayer.Hand.Add(card);
    AdvancePlayer();
}
deck.Clear();

(I'm not sure why you're using extension methods here, btw. This looks like something which would be more appropriate as an instance method.)

Jon Skeet
I hadn't heard of the Queue class but I'll check it out. I neglected to mention this but I want something flexible enough to use in other "card-passing" situations (playing a card, moving cards to the "trash" deck, etc.). Extension methods seemed suitable.
I think you'll end up with more convoluted code this way. I would seriously suggest writing it in the simplest way first, and then consider reuse when you've actually *got* similar code in multiple places.
Jon Skeet
FWIW, I agree with Jon. Extension methods should be used when you need to extend a class's behavior and you don't control it. But you don't need to extend the behavior of collections; you just want to do things that *involve* collections.
John Feminella
+3  A: 

This is a bad case for Arrays, I think, which usually aren't designed to be repeatedly added to and removed from. Also, I would not make this an extension method, since it has no relevance outside of a few selected places in your application.

Consider just sticking with a List instead and having a class method which is responsible for doing the moving.

public class CardDealer {
...
  private List<Card> _deck;

  // Put the card [c] into [hand], and remove it from the deck.
  public void Deal(List<Card> hand, Card c) {
    _deck.Remove(c);
    hand.Add(c);
  }
}

Commenters have suggested a deck of cards may better modeled as a Queue, which is a legitimate point depending on whether you can only take cards from the top of the deck or not. If that is indeed the case, consider this:

public class CardDealer {
...
  private Queue<Card> _deck;

  // Put the top card of the deck into the specified hand.
  public void Deal(List<Card> hand) {
    // Deck is a Queue now. No need to specify which card to take.
    Card c = _deck.Dequeue(); 
    hand.Add(c);
  }
}
John Feminella
Would the "ref" keyword be required in this example? Also I neglected to mention this but I would prefer something that I can re-use for other "card-passing" situations.
There's no need for a ref keyword here. You're not changing what the list refers to, only its contents.
John Feminella
Exactly the way I was thinking. +1. @unknown, This is a little complicated because reading from a deck is like a queue whereas hands are more random-access. John's example treats the deck as random-access, but you can assign c = _deck.Last() or whatever to make it act like a queue.
strager
A: 

Maybe something like this?

interface ICardPile
{
    ICollection<Card> Cards
    {
        get;
    }
}

interface IOrderedCardPile : ICardPile // FIXME Better name.
{
}

class Deck : ICardPile
{
    private Stack<Card> _cards = new Stack<Card>();

    ICollection<Card> Cards
    {
        get
        {
            return _cards;
        }
    }

    public Deck()
    {
        // TODO Fill deck.
    }

    public void DealCardsTo(IEnumerable<ICardPile> piles, int cardCount)
    {
        for(int i = 0; i < cardCount; ++i)
        {
            foreach(var pile in piles)
                Cards.MoveSomeTo(piles, 1);
        }
    }
}

class Hand : IOrderedCardPile
{
    private HashSet<Card> _cards = new HashSet<Card>();

    ICollection<Card> Cards
    {
        get
        {
            return _cards;
        }
    }
}


// Extension methods
static void MoveSomeTo(this ICardPile pile, ICardPile other, int count)
{
    // Removes cards from the end of pile and puts them at the end of other.

    foreach(Card card in pile.Cards.Reverse().Take(count))
    {
        other.Add(card);
    }

    pile.Cards = pile.Cards.Take(count);
}

static void MoveCardTo(this IOrderedCardPile pile, ICardPile other, Card card)
{
    // Removes card from pile and puts it at the end of other.
    pile.Remove(card);
    other.Add(card);
}


// Examples
Deck deck;
DiscardPile discard;
var hands = new Hand[4];

deck.DealCardsTo(hands, 7);

// Discard all aces.
forach(var hand in hands)
{
    foreach(var card in hand.Cards.Where(card => card.Number == Card.SomeEnum.Ace))
        hand.MoveCardTo(discard, card);
}
strager