views:

168

answers:

7

Making an uno game for a project.

all 108 cards are located in a filled array named cardDeck[108]

the start function pulls 7 cards at random and fills the players hand by adding them to an array called playerHand[]

I need help on this please.

1) I don't know how to make the cards dissappear out of the cardDeck array when they are pulled into playerHand[].

2) I also don't know how to create something that will count the non-null values in the cardDeck array so it will display the total number of cards left in the deck after both players draw cards.

+1  A: 

you could assign the drawn cards a value/character that no card has. Thus telling you which cards have been drawn ...

Siddharth Sharma
+2  A: 

Just use a std::vector<card>. It's a dynamically resizable container which you can remove elements from.

sbi
I disagree. By using a structure with a value to show its play status, you can eliminate the need for insertions/deletions be they of the more intensive array variety, or the less intensive vector variety. You can reduce it to a simple assignment. You could use either a vector or an array of your structures. I think this is the optimal redesign...
Jason R. Mick
@Jason: I've seen as much from your discussion with John. JFTR, I'm with John on this.
sbi
+1  A: 

Keep an int (let's call it N) representing the number of cards left in the deck. To remove a card from the deck, pick a random number r between 0 and N (ie 0 <= r < N), swap the last card in the array with the r'th, decrement N and return the card that you just swapped to the end of the array.

This way, you never introduce NULLs into the array and so avoid your problem entirely. If you use an stl vector instead of an array this becomes even easier as the vector knows its size without you having to store it yourself.

Paul Hankin
+1  A: 

Consider this alternate implementation and logic.

  • Define CardCollection to be a class that 'has a'

    std::deque<boost::shared_ptr<Card> >

  • Define three CardCollections - one is the deck, the other two are player hands

  • Initialize the deck by push_back of all the Card objects (in random order) to its deque.

  • draw the Cards from the deck using pop_front() on its deque

  • move them to the corresponding
    player hand by push_back to that
    deque, erase in the deck deque
    (this solves 1)

  • 2) is solved by calling size() on the deck deque

  • when the hand ends, return Cards to the deck using push_back()

Steve Townsend
Why are you using shared_ptrs? That seems like you'd be copying a card out of the deck more than removing it. Since there's only one instance of each card, I would think a normal pointer or auto_ptr should do the trick here.
peachykeen
auto_ptr is not allowed in STL containers. Using shared_ptr to allow any container to include a ref to a single Card instance. If copying Cards is cheap, then just use vector<Card>.
Steve Townsend
Then why not a normal pointer? I can see why you might want to copy the pointers around, but you don't want more than one of each, I should think. Sharing just confuses me when combined with the nature of cards. :)
peachykeen
If I wanted to use raw pointers, which I don't, I would use boost::ptr_vector. However, boost:unique_ptr might better represent the transfer of Card from deck to player hand and back.
Steve Townsend
Cards are essentially values; for instance "Green 7". You can treat them like ints. `std::deque<Card>` is the logical container - cheap shuffling and cheap operations on both ends.
MSalters
@MSalters - yes. This also lends itself to the appealing design description "<deque> of Cards"
Steve Townsend
+12  A: 
#include <cstdlib>
#include <vector>
#include <algorithm>
using namespace std;

Make the deck a vector.

vector<Card> deck_;

Insert each card in to the deck.

deck_.push_back(Yellow0);
deck_.push_back(WildDraw4);

Shuffle the deck

srand((unsigned)time(0));
random_shuffle(deck_.begin(), deck_.end());

Take cards out of the deck & put them in the player's hand.

vector<Card> player_hand_;
for( size_t i = 0; i < 7; ++i )
{
  player_hand_.push_back(deck_[0]);
  deck_.erase(deck_.begin());
}

EDIT:

Per a request in the comments below, I'll explain why I believe there should be multiple vectors of stateless cards rather than a single vector of stateful cards.

One of the ideals that OOP tries to approach is to model the real world as closely as possible. Now obviously a vector is not OOP, but the cards contained therein theoretically can be. And while it certainly can't be achieved perfectly in many cases, or should even be attempted to be perfectly achieved in as many cases, attempting to model software components after the real world IMO transcends any particular programming paradigm.

So consider the Uno cards. An Uno card has a couple of different properties. They have a rank (0-9) and they have a suit (red, blue, yellow, green). There are also a few cards that have special meaning and have neither suit nor rank (WildDraw4, Wild, Draw2, Skip, reverse).

So if we were to model an Uno card in C++, what would it look like? Uno cards don't change once they are printed. A yellow 2 will never be a green 5 or a Wild Draw 4, for example. If you take a green7 out of the box and hand it to your friend, it's still a green7. It's now in your friend's hand, but that's not something that's changed about the card. Rather, what's changed is your friend's hand. Instead of being empty, it now holds a Green 7 Uno card.

In the game of Uno, each player is dealt 7 cards. There is one card placed face-up that becomes the discard pile, and the remaining cards are placed face-down forming the draw pile. When you move a card from the draw pile to a player's hand, the card still hasn't changed even though it's location has. But don't get hung up on human language. "It's location" doesn't imply that the location of the card is a property of the card. But when you move the card, something has changed -- the states of the draw pile and the player's hand.

So we end up with a vector for the draw pile, another for the discard pile, and one vector for each player, representing their hand.

This design is easy to understand by a maintenance programmer, easy to extend, and easy to develop the first time. Because it is simple, it is also not especially prone to defects. After all, which is more likely to break down -- a skateboard or the Space Shuttle?

John Dibling
this fixed both problems thank you!
Nick Gibson
You're welcome. Please consider upvoting and/or accepting responses that help you.
John Dibling
John I think in this case a structure of the card type and play value (in play, in hand, discarded) would do nicely. There's no reason I can think of to be shifting memory in a vector when the number of total cards is static at all times. (See my answer below)
Jason R. Mick
@Jason: Well, OP specifically asked for advice on how to "make the cards dissappear out of the cardDeck array." Moreover, you're right that there is always 108 cards in play. But as with a real-life Uno game, there aren't always 108 cards in "the deck" if you consider the draw-pile to be "the deck," which it seems like OP does.
John Dibling
Thats why I define an enum of values for the play status (in hand, in deck, or discarded). To make any sort of change, it's just a simple assignment op in that case, versus vector deletion/appending. Can't you see the merit to that? (Note both our implementations are considered redesigns, he asked how to do it with arrays... which would be possible, just less efficient.)
Jason R. Mick
Again the problem here is a set number of objects (cards) with a finite number of statuses (in play, in hand, discarded, etc...). You can use two approaches here -- either maintain collections of cards with each status (expensive operationally) or simply maintain a single collection of your game cards, which has a status indicator attached to each card (out of play cards can be labeled as discarded). This approach (my answer below) is more efficient and would be a better fit for his project.
Jason R. Mick
I do indeed see what you're saying. Yes, you're design has merit, and it's a clever design. I can certianly see how utilizing this design would be best in certain problem domains. But in this case (and, indeed, most cases) the added sophistication of your design brings more complexity to the overall application than a simpler approach. Your code might indeed be faster. But then again, it might not with full optimizations. And even if it is, we're probably talking about 8 *nanoseconds* vs. 12 nanoseconds. For an Uno game. Remember the old adage about premature optimization? It applies.
John Dibling
John Dibling
I disagree. I think your design is the more complex one. Granted I add two new elements (an enum and a struct), but I remove two variables -- two of the three collections (a single collection variable "cards" remains). And I think my approach is more intuitive. So in terms of elements we're breaking even. But in terms of operations, it only takes one op to change a card's status with my approach, but with yours it takes two ops (an append and erase). Hence I would argue your design introduces greater complexity. Why would you pick a less efficient design that is also more complex?
Jason R. Mick
@John. What's hard to maintain, non-extensible, or non-robust about my design? To add an extra play state in some future uno variation, with my approach you can simply add an extra enum value. For your approach you have to add an maintain an entire extra vector object. That is a less-extensible, less robust design, imho.
Jason R. Mick
@Jason: I don't care about the downvote, but if it's yours I'd like to suggest you read up on premature optimization. Here's one place to start: http://c2.com/cgi/wiki?PrematureOptimization
John Dibling
@Jason: Let's just agree to disagree then.
John Dibling
@John. I appreciate your approach as being a workable solution. But please view my discussion below. I really think your approach is a less intuitive, more complex solution. I guess if you disagree, we have to agree to disagree. If you post an edit with a similar discussion on why you think a multi-container approach trumps a single container approach (i.e. why you think its less complex, more intuitive etc.) I will gladly change my downvote to an upvote, though. :)
Jason R. Mick
@Jason: Coming from the table with the cards in your hand, the _more intuitive_ approach is certainly the one where the cards actually change places. When you're sitting on the table playing cards, you don't clip little markers to the cards in the deck lying on the table, to mark who currently owns which card, you physically move the cards around - which is exactly what the multi-container approach does. As John said, your approach _might_ be faster, but it's certainly less intuitive and more complex than actually passing around objects.
sbi
But in your brain, you do assign markers. The card is "in the draw pile" is "in the discard pile" or is "in your hand". Just like with a car you could be "parked at home" "parked at work" "driving" "etc". So my approach is intuitive. I think if you asked me where my car was I would think "my car is in state parked at work", rather than think "my car is a member of the collection parked at work"
Jason R. Mick
@Jason: I've posted the edit you've requested. Not because I want you to reverse the downvote (which bothers me not in the slightest), but because I truly believe you're thinking about this in the wrong way. In short, "parked at home" isn't a property of your car. Instead, "has your car" is a property of the parking lot at your apartment.
John Dibling
what do you mean "Vector isn't OOP"? a deck of cards is a real world object i would like to model. std::vector does a great job of abstractly modeling a real world collection like a deck of cards.
TokenMacGuy
@TokenMacGuy: Sure, I agree. That doesn't make it OOP. Vectors are template-based, are not run-time polymorphic, do not use derivation, and employ early (compile-time) binding. The STL is not OOP in the traditional sense, even by a stretch. By the way, just because something isn't OOP doesn't mean it's bad or wrong!
John Dibling
@John: Well maybe you and I have different Ideas of what OOP is, then. Did you know you can write procedural code in SmallTalk? OOP is a methodology, not a language feature. It just so happens that the polymorphism presented by std::vector is compile time, not run time.
TokenMacGuy
@Token: And it just so happens that the paradigm of using _compile-time polymorphism_ (a wide-spread term) is named _Generic programming_, to tell it from _Object-oriented Programming_. The whole of STL (which, BTW, is a part of the std lib, and not the whole of it) _is not OO at all_. If it was OO, each container would have to have a `sort()` function, a `find_if()`, a `replace()`, and whatnot. However, these are all free functions (in general, anyway, there are concessions to performance). Stepanov made a hell of a giant step when he came up with this paradigm.
sbi
@Token, @sbi: And in fact, SStepanov himself has said explicitly that the STL is not OOP. "STL is not object oriented." link: http://www.stlport.org/resources/StepanovUSA.html
John Dibling
A: 

I would recommend something like this (it's a bit more complicated, but should work):

Add all cards to a deque (push back)

(optionally) Shuffle (randomly sort) the collection

Loop 7 times
{
    For each player
    {
       Removing the first card from the collection and add it to the player's hand
    }
}

You should end up with hands of 7 cards and everything left on the deck is very easy to count or work with (it's all left in the deque).

Now, I would recommend using vectors for the player's hands instead of arrays, although the difference here is pretty small, since you have a known number of elements. Vectors might be easier to work with. If you haven't already, I'd also make a Card class and keep the deck and hands as deque<Card*> and vector<Card*>, which will make moving cards a breeze (copy the pointer, which is one instructions) and shuffling will be pretty fast too on small data types.

peachykeen
+1  A: 

I assume you're using some sort of custom type for the cards. So lets call this t_card_kind.

You could do some sort of array shift to "delete" the card from the players hand. But this doesn't make much sense.

What I would suggest is defining an enum like

namespace uno { enum play_status { DRAW_PILE,IN_HAND,DISCARD }; };

That way you can simply declare a new type-defined structure

typedef struct s {
   t_card_kind card;
   uno::play_status status;
} t_game_card;

... and lastly declare an array such as

t_game_card my_cards[108];

or a vector such as: vector my_cards[108];

and then populate it.

To change a card's status simply access the member.

e.g.

my_cards[i].status = uno::IN_HAND;

This design will execute much quicker and is more elegant.

To solve your second question just implement a method like

unsigned int count_status(const t_game_card * my_card, 
                          uno::play_status status_kind) {
   const unsigned int DECK_SIZE = 108; //size of a standard uno deck
   unsigned int matches;
   for (unsigned int counter=0; counter < DECK_SIZE;counter++) {
      if (t_game_card[counter].play_status==status_kind) {
         matches++;
      }
   }
   return matches;
}

...Or just switch to vectors and use the erase() method.


EDIT 1
Based on my above discussion with John there is debates over this single container design versus a multi-container design. Here is my opinion on the topic:

I believe this design of using a single vector of cards with state info attached is more robust and easier maintain than a multi-vector based design. Here is why.

In your resulting code you would have something like:

namespace uno { enum play_status { DRAW_PILE,IN_HAND,DISCARD }; };    
typedef struct s { t_card_kind card; uno::play_status status; } t_game_card;    
static vector<t_game_card> my_cards[108];

Where a multi-vector based approach would have:

static t_game_card draw_pile[108];
static t_game_card in_hand;
static t_game_card discards;

...still three items, though mine is a bit longer line length wise.

As previously stated, to change a state with my approach you would simply use something like:

my_cards[i]=uno::IN_HAND;

whereas with a vector-based method you would have to do something like:

in_hand.append(draw_pile[i]);
draw_pile.erase(draw_pile.begin()+i);

This is longer, more computationally intensive, uses more memory, and in my opinion less intuitive.

Now let us consider extensibility. Let's say you come up with a uno variant that includes a "BONUS" pile. With my approach this is simple -- just add a value to the enum:

namespace uno { enum play_status { DRAW_PILE,IN_HAND,DISCARD,BONUS }; };

with the vector approach, you'll need to create and maintain an entire extra vector:

    static t_game_card draw_pile[108];
    static t_game_card in_hand;
    static t_game_card discards;
    static t_game_card bonus;

We now have 4 (!) variables with the vector based approach versus a single, intuitive variable with my approach.

Maintaining multiple containers for separate states takes up more memory, is computationally more expensive, and is less intuitive versus using a single container (vector, array, etc.) to hold structures with state info attached.

Jason R. Mick
+1 for a novel design, although I do think this is needlessly complex for the given problem.
John Dibling
Why do you use a macro for a constant? Please don't show such techniques to novices.
sbi
Alright sbi, I've modified the design to use a constant instead.
Jason R. Mick
How do you go about sorting the cards in hand?
Dennis Zickefoose
Also, which card do you draw next?
Dennis Zickefoose
If you needed to sort them, that could be added as an additional element in your structure ie. unsigned int position_in_hand; Similarly you could store the index of the last drawn hand and last discarded card. This would add two variables to the design, so I suppose the design is slightly more complex in this respect.
Jason R. Mick
Actually write out code to sort cards in hand. Its not just slightly more complex. Its a mess.
Dennis Zickefoose