0

so Im supposed to make a game an assignment for class.

Essentially, I decided to re-create a heads up Poker game, running different functions such as int deal(a, b, x, y) where a and b are the hero's cards, and x and y are the villan's.

This function in particular, has me a bit stumped. Effectively, I loop through the array deck, and assign random numbers to a, b, x and y. Then, I will translate each assigned value into a real, unique card, and return that to int main().

The part that I am stuck at is "as each card is selected, it is deleted from the array. It seems that there is no easy way in C to simply remove an element from deck array.

int deal (a, b, x, y)
{

    int deck[52] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
                    31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52};
    int heroFirst;
    int heroSecond;

    int villainFirst;
    int villainSecond;

    srand(0);

}

Any thoughts?

displayName
  • 155
  • 1
  • 9
  • 1
    You can't "delete" an element from an array, as arrays have a fixed size that can not be changed. You could modify the value of the element so that it becomes something which should not be possible (like `0` or `-1`?) and check for that. – Some programmer dude Jul 31 '17 at 18:21
  • 1
    That's an XY problem. Don't use a screwdriver for a nail! An array is not well suited for the problem in any language (some call lists "arrays", though). – too honest for this site Jul 31 '17 at 18:36
  • 2
    Note [`srand()` — why call it only once?](http://stackoverflow.com/questions/7343833/srand-why-call-it-only-once/) – Jonathan Leffler Jul 31 '17 at 19:27

4 Answers4

4

You don't have to delete anything.

Shuffle your deck array (using a Fisher-Yates shuffle or similar algorithm). Deal each card from the "top" of the deck:

int top = 0;

card1 = deck[top++];
card2 = deck[top++];
card3 = deck[top++];
...

etc. The top variable is the index of the next available card in the deck.

The general outline of your code will be something like

#define DECKSIZE 52
#define HANDSIZE  5

int main( void )
{
  int deck[DECKSIZE] = { ... }; // initial deck;
  size_t top = 0;               // points to next available card

  shuffle( deck, DECKSIZE ); 

  int hero[HANDSIZE] = {0};   // 0 means no card has been drawn for
  int villan[HANDSIZE] = {0}; // that element.  

  if ( deal( hero, HANDSIZE, deck, DECKSIZE, &top ) &&
       deal( villan, HANDSIZE, deck, DECKSIZE, &top ) )
  {
    /** 
     * do stuff with hero and villan hands
     */
  }
  else
  {
    /**
     * Not enough cards available in deck for two hands.
     */
  }
};

Your deal function would look something like

int deal( int *hand, size_t handsize, int *deck, size_t decksize, size_t *top )
{
  size_t i;
  for ( i = 0; i < handsize && *top < decksize; i++ )
    hand[i] = deck[(*top)++];

  return i == handsize;
}

This function will return 0 if we run out of cards in deck before we've dealt the hand, in which case you'll need to do...something. Good luck!

If you want to deal a partial hand (such as to replace 3 cards), you'd do something like

 if ( deal( &hero[2], 3, deck, DECKSIZE, &top) )
   ...

This call will overwrite hero[2] through hero[4] with three new cards drawn from deck. With each call to deal, top will be advanced to point to the next available card in the deck.

You can write a discard function that returns cards to the deck. It means keeping a separate bottom variable and updating that:

int discard( int card, int *deck, size_t decksize, size_t *top, size_t *bottom )
{
  int result = *bottom < *top && *bottom < decksize;
  if ( result )
    deck[(*bottom)++] = card;

  return result;
}

Obviously, bottom should be strictly less than the deck size and strictly less than top on a discard; otherwise, we haven't managed our deck or hands properly. With a little work, you could make your array "circular", such that top and bottom "wrap around" as necessary. If you exhaust the deck, you can reshuffle (minus the cards in hand, which will be the deck entries between bottom and top) and reset top and bottom as necessary.

Play with this on paper for a little while, and it should become obvious.

EDIT

Addressing questions here:

At which point do you assign deck[5] to a card, for instance

That happens in the deal function, in the for loop. The first time we call deal, we tell it to deal to the hero hand:

deal( hero, HANDSIZE, deck, DECKSIZE, &top ) 

At this point, top is 0. Assuming we're dealing 5 cards at a time, the first call to deal effectively does this:

Loop iteration 0: hero[0] = deck[0] 
Loop iteration 1: hero[1] = deck[1]
Loop iteration 2: hero[2] = deck[2]
Loop iteration 3: hero[3] = deck[3]
Loop iteration 4: hero[4] = deck[4]

When the function returns, the top variable has been updated to 5. The next time we call deal, we tell it to deal to the villain hand:

deal( villain, HANDSIZE, deck, DECKSIZE, &top ) 

Again, assuming we're dealing 5 cards at a time, the loop effectively does this:

Loop iteration 0: villain[0] = deck[5];
Loop iteration 1: villain[1] = deck[6];
Loop iteration 2: villain[2] = deck[7];
Loop iteration 3: villain[3] = deck[8];
Loop iteration 4: villain[4] = deck[9];

After the second call to deal, top has been updated to 10.

Each time you call deal with the top variable, it will start dealing from deck at the position specified by top, and each time through the loop it will add 1 to top. The loop will exit if one of two conditions is true:

  1. i == handsize - we've dealt all the cards necessary for this hand
  2. *top == decksize - we've reached the end of the array, nor more cards may be dealt.

So, suppose you've dealt a number of hands, and there are only 3 cards left in the deck - if you try to deal 5 more cards, the loop will exit before you've dealt all 5 cards, and we'll return a 0 to indicate that no more cards are left in the deck.

at which point is the desk shuffled randomly?

You would call a shuffle function to do that before the first call to deal:

int deck[DECKSIZE] = { ... };
...
shuffle( deck, DECKSIZE );
...
if ( deal( hero, HANDSIZE, deck, DECKSIZE, &top ) &&
     deal( villain, HANDSIZE, deck, DECKSIZE, &top ) )
{
  ...
}

A simplistic (and not terribly good) implementation of the shuffle function would be:

void shuffle( int *deck, size_t decksize )
{
  for ( size_t i = 0; i < decksize; i++ )
  {
    int r = rand() % (decksize - i)  
    int tmp = deck[i+r];
    deck[i+r] = deck[i];
    deck[i] = tmp;
  }
}

Basically, what this does is swap each deck[i] with a randomly chosen element from deck[i] through deck[decksize-1] (meaning the element may remain in place). Assume we have 5 cards. The first time through the loop, i points to the first card. We pick an offset from i at random and call it r:

i --> 1
      2
      3
      4 <-- r
      5

We then swap the contents of deck[i] and deck[i+r], and then advance i:

      4
i --> 2
      3
      1 
      5

We pick another r at random from the remaining elements:

      4
i --> 2
      3
      1 
      5 <-- r

and do another swap and advance i:

      4
      5
i --> 3
      1 
      2 

Lather, rinse, repeat - by the end of the loop, the array is more-or-less randomly shuffled.

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • Thank you for this. Im still learning C, everything you've written out borders along the lines of "hey! I've seen that before... just not the way it's being used," like with pointers for example. Ill re-read your comment a bunch of times, I'l need that, but can you elaborate a little more on the part where you're dealing from the top of the deck? At which point do you assign deck[5] to a card, for instance (slash, at which point is the desk shuffled randomly?) – displayName Jul 31 '17 at 19:32
2

No, there is no way in C to delete an element from an array. Indeed, as far as I know there is no way to delete an element from an array in C++, C# or Java either.

Now, that being the case, you have a few options. You can use a sentinel value in your code to mark an element as absent. You can resize your array to actually shrink it by one and relocate all elements backwards one place whenever an element is deleted.

In your example, either 0 or -1 should serve fine as sentinel values.

Tanveer Badar
  • 5,438
  • 2
  • 27
  • 32
  • You are right. Arrays are simply not well suited for the problem in any language. – too honest for this site Jul 31 '17 at 18:38
  • Actually there are a lot of languages that have easy methods to insert, delete, etc members of an array. `lisp` , `prolog`, etc. – user3629249 Aug 01 '17 at 12:53
  • @user3629249 Indeed, that is why I restricted my answer the languages I know about. Even javascript, if I recall correctly, lets you insert and delete elements from the middle of an array. But then, what javascript pretends is an array is really a hash table. – Tanveer Badar Aug 01 '17 at 12:55
0

First at all, make the array deck static. Next, introduce another static variable initially equal to the lenght of array=52.

Now when a card at a random position is dealt, it can be swapped with the last card in the deck, indexed with arraysize-1, and the arraysize being decreased.

Making these two variables static allows the function to maintain a state between calls. Of course better techniques would encapsulate these variables in a struct (allowing eg multiple games / resetting the deck).

In short, the problem you're really trying to solve isn't one of deleting an entry in an array.. Rather, it is a more encompassing issue: How to perform repeated random selection from a fixed domain without repeating selected values. The method described above is one way to do that. An example of the latter technique using a 20-slot array is below (the value 20 was chosen for short output lines (easy to read). It could just as easily be 52, 1000, etc.):

Code

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define DECK_SIZE  20

typedef struct Deck
{
    int cards[DECK_SIZE];
    size_t size;
} Deck;

Deck init_deck()
{
    // no shuffle is needed. each selection in draw_card will
    //  pick a random location in the remaining cards.
    Deck deck;
    for (int i=0; i<DECK_SIZE; ++i)
        deck.cards[i] = i+1;
    deck.size = DECK_SIZE;
    return deck;
}

int draw_card(Deck *deck)
{
    // reset when we run out of cards.
    if (deck->size == 0)
    {
        printf("- reset \n");
        deck->size = DECK_SIZE;
    }

    // generate random location in remaining cards.
    //  note: susceptible to modulo bias
    size_t idx = rand() % deck->size;

    // decrement size to consume card and index where
    //  the card will be swap-stored.
    --deck->size;

    // swap with last card in remaining cards
    int tmp = deck->cards[deck->size];
    deck->cards[deck->size] = deck->cards[idx];
    deck->cards[idx] = tmp;

    // return the drawn card
    return deck->cards[deck->size];
}

int main()
{
    Deck deck = init_deck();

    srand((unsigned)time(NULL));

    // draw 300 times. this should reset 15 times, and
    //  each time a new set of 1..20 should result
    for (int i=0; i<300; ++i)
        printf("%d ", draw_card(&deck));

    fputc('\n', stdout);
}

Output (obviously varies)

7 16 3 20 9 13 6 4 1 12 18 10 14 2 8 17 11 5 15 19 - reset 
6 20 14 16 11 2 10 13 4 12 18 5 3 7 19 9 17 8 15 1 - reset 
14 1 8 15 13 2 19 16 11 17 5 18 9 12 7 6 3 20 4 10 - reset 
18 17 12 2 15 19 1 4 14 10 20 16 9 5 11 13 6 8 3 7 - reset 
4 18 5 1 19 16 8 10 9 14 13 17 12 20 7 2 15 6 11 3 - reset 
14 16 18 1 5 10 17 3 19 9 8 2 7 13 12 20 4 15 11 6 - reset 
16 15 12 13 6 1 17 10 9 7 11 20 8 19 2 18 3 4 14 5 - reset 
7 1 8 16 17 5 2 12 13 6 18 20 9 11 14 19 15 3 4 10 - reset 
20 13 4 18 7 17 12 15 5 14 2 16 11 3 9 10 1 19 8 6 - reset 
5 19 4 17 18 13 8 2 12 7 9 1 11 10 3 14 6 15 16 20 - reset 
3 5 10 7 1 15 19 13 16 12 9 8 6 20 4 11 17 18 14 2 - reset 
11 14 4 7 15 9 16 18 8 13 12 5 10 19 2 6 20 1 3 17 - reset 
10 18 2 4 12 20 14 11 16 13 3 9 8 6 5 7 17 1 15 19 - reset 
19 12 20 11 13 9 5 1 10 15 7 2 17 6 3 4 8 14 16 18 - reset 
10 3 19 4 6 14 18 11 1 7 9 16 8 13 17 20 2 5 15 12 

Notive that on each line, there are twenty selections, and each number in 1..20 appears exactly once per line. How this technique serves you is up to you. A worthy challenge will be how to enumerate the hands held by existing players when a reset (often called a deck-flip) happens. That's an interesting problem to solve, and not one that is as intuitive as it may seem.

Hope it helps.

WhozCraig
  • 65,258
  • 11
  • 75
  • 141
Aki Suihkonen
  • 19,144
  • 1
  • 36
  • 57
  • This is a popular algorithm for remedial tasks such as the OPs, the key to making it work probably worth mentioning: The card selection is done modulo the *remaining* deck size (arraysize). Note: this can introduce [modulo bias in random selection](https://stackoverflow.com/questions/10984974/why-do-people-say-there-is-modulo-bias-when-using-a-random-number-generator), so don't do it in mission-critical code. Regardless, preference to using sentinel values is that there is never a need to repeat random selection when the current selection falls on an already-used entry (i.e. a sentinel). – WhozCraig Jul 31 '17 at 18:34
-2

There are a couple of solutions you could use to solve your problem:

First, is using the vector class, especially the insert and remove functions. here's a good explanation on how it works: C++ Vectors

Second, which is also my favorite is to use numbers or booleans as indicators. for example, declare a simple boolean array of the same length as your deck. the initial value of all elements would be true. for each card, you'd like to remove from the deck simply change its value to false to indicate it's gone.

Sina Mansour L.
  • 418
  • 4
  • 8