-1

I have a small struct -

struct Card {
        public string suit;
        public string value;
}

Which I then use to initialize an array of cards

Card[] deck = new Card[52];

In my Main(), I call

Deck myDeck = new Deck();

Which correlates to the constructor

public Deck() {         
            int cardNum = 0;
            for (int i = 0; i < numSuits; i++) {
                for (int a = 0; a < numValues; a++) {
                    deck[cardNum].suit = suits[i];
                    deck[cardNum].value = values[a];
                    Console.WriteLine("The card at position " + (cardNum + 1) + " is the " + deck[cardNum].value + " of " + deck[cardNum].suit);
                    cardNum++;

                }
            }

        }

... thus creating a deck with 52 cards, which as confirmed by my Console.WriteLine(), populates the deck correctly.

My issue is I have 2 other methods, public void Shuffle() and public string Deal() which, as their names suggest, shuffle the deck and deal the top card respectively, however I do not know how to pass the deck.suit and deck.value values into said methods.

I have tried initializing the Card[] array inside the constructor. All of these functions are under the same namespace and class.

I would also like to keep the constructor and two methods in the code and not use anything else, even though I'm sure there are many other, potentially easier ways to do this.

Thanks in advance!

Andrew Gray
  • 3,593
  • 3
  • 35
  • 62
J. Raymond
  • 21
  • 5

3 Answers3

1

Have a play with this:

void Main()
{
    var deck = new Deck();
    deck.Shuffle();
    var cards = deck.Deal(1);
    foreach (var card in cards)
    {
        Console.WriteLine($"{card.Value} of {card.Suit}");
    }
}

public struct Card
{
    public Card(string suit, string value)
    {
        this.Suit = suit;
        this.Value = value;
    }
    public readonly string Suit;
    public readonly string Value;
}

public class Deck
{
    private const int _numSuits = 4;
    private const int _numValues = 13;

    private string[] _suits = new [] { "Clubs", "Diamonds", "Hearts", "Spades", };
    private string[] _values = new []
    {
        "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King",
    };

    private Card[] _deck = new Card[52];

    public Deck()
    {
        _deck =
            _suits
                .SelectMany(s => _values, (s, v) => new Card(s, v))
                .ToArray();
    }

    private Random _random = new Random();
    public void Shuffle()
    {
        _deck = _deck.OrderBy(_ => _random.Next()).ToArray();
    }

    public Card[] Deal(int take)
    {
        var cards = _deck.Take(take).ToArray();
        _deck = _deck.Skip(take).ToArray();
        return cards;
    }
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • 1
    You're allocating a `Random` object each time you allocate a deck. That can lead to the problems shown in [Random encounter not so random](https://stackoverflow.com/q/2727538). For alternatives see https://codeblog.jonskeet.uk/2009/11/04/revisiting-randomness/ and [Is C# Random Number Generator thread safe?](https://stackoverflow.com/q/3049467). – dbc Oct 22 '18 at 04:09
  • 1
    @dbc - Yes, you're right. Please note I declared it as a field, rather than as a variable within the `Shuffle` method for exactly the reason you raised. I should have declared it as `[ThreadStatic] private static Random _random = new Random();` to be even safer. – Enigmativity Oct 22 '18 at 04:24
  • Thanks very much Enigmativity, this works a treat. A few follow up questions for you, if I could: 1. Is there a way to make it such that when deck.Shuffle() is called, any previously dealt cards are added back into the deck (i.e. deck.Shuffle() shuffles all 52 cards, regardless of how many have been dealt). 2. Is there a way to keep Deal() returning a string? I've tried modifying to give me a string variable from _deck.Take().ToArray(), but so far no luck! – J. Raymond Oct 22 '18 at 06:36
  • @J.Raymond - You would typically create a new `Deck` when you want all the cards back. And what `string` should `Deal` return? – Enigmativity Oct 22 '18 at 07:02
0

Cards are a bit more universal. They are not use only in Decks. They are also used in Hands. And with some games that have open cards even in another structures whose name I do not know (sorry, I only know Poker from Star Trek TNG).

From personal experience I know it can be pretty hard to make a proper class for "Hand" or "Deck". In the end both just seem to end up being little more then containers for a Card[]. So it might be better/easier to use a Card[]. And just have a bunch of static functions that initialize a full a Deck of Card, Shuffle a Deck, DrawFrom a Deck and take a Card[] as one of the arguments or return Card instances. Keep in mind that Arrays are inherently handeled over by reference, wich really helps here.

As for the act of drawing random cards without repitition, I personally call that the "Lottery Problem". And I made this example code to deal with it:

//Create an array. Fill it with Sequential Numbers.
int[] input = new int[20];

for(int i = 0; i < numbers.lenght; i++)
  input[i] = i;

/*Initialise the instances you will need*/
//Use the array to create a list
List<int> DrawableNumbers = new List<int>(input);
List<int> DrawnNumbers = new List<int>();

//Generate a Random Number Generator
Random rng = new Random();

/*Draw 6 from the group*/
while(DrawnNumbers.Count < 6){
  //Get a random Index to move from DrawableNumbers to DrawnNumbers
  int temp = Random.NextInt(DrawableNumbers.Count);
  DrawnNumbers.Add(DrawableNumbers[i]);
  DrawableNumbers.Remove(temp);
}

The only difference with shuffling a deck, is that you do not stop until you run out of cards.

Christopher
  • 9,634
  • 2
  • 17
  • 31
0

Unfortunately i cannot yet comment:

Random rnd=new Random();
Card[] randomOrder = deck.OrderBy(x => rnd.Next()).ToArray();    
//TODO: you could iterate over the random order and fill your Stack 

that should shuffle your array randomly. Deal should be simple, you could pick a random index of your array and return the card. If that card should be removed after it has been dealt either use a list or better, when you shuffle you can fill create a StackT> and fill it, then you can Pop the next card every time you call your Deal function.

@enigmativity beat me to it, and his solution allows to take multiple cards at once. The question is whats better practice using a stack and emptying it slowly or going with .Take and new arrays?-

Tomek
  • 323
  • 1
  • 4
  • 19
  • Returning an array of cards for `Deal()` allows the deck to run out without resorting to throwing exceptions or using nullable `Card`s. And it helps with the metaphor of actually dealing cards - many games deal more than one card at a time. – Enigmativity Oct 22 '18 at 04:08
  • @Enigmativity correct, I already voted for your solution since you have the choice to deal 1 or multiple cards per deal which is ideal solution. I based it off Texas Hold 'Em where you deal only a single card every time. – Tomek Oct 22 '18 at 04:41