Drawing multiple cards from a deck of cards

A quick how-to for something that sounds like it should be simple but is actually a bit fiddly in Pulpscript.

When drawing multiple cards from a deck of cards we never want to draw the same card twice - once a card has been drawn, it is no longer in the remaining deck.

Pulpscript doesn't have arrays so we can't just randomly pop an item from an array like we might do in other programming languages.

Instead we have to use Pulpscript's very basic random function and a bit of trickery to make sure we never pick any duplicates.

Here's an event that picks five random cards from a 52 card deck then simply logs the picks:

on drawFiveCards do
  // Card 1
  c1 = random 1,52
  s1 = c1
  
  // Card 2
  c2 = random 1,51
  if c2>=s1 then
    c2++
  end
  s2 = c2
  if s2<s1 then
    _ = s1
    s1 = s2
    s2 = _
  end
  
  // Card 3
  c3 = random 1,50
  if c3>=s1 then
    c3++
  end
  if c3>=s2 then
    c3++
  end
  s3 = c3
  if s3<s2 then
    _ = s2
    s2 = s3
    s3 = _
  end
  if s2<s1 then
    _ = s1
    s1 = s2
    s2 = _
  end
  
  // Card 4
  c4 = random 1,49
  if c4>=s1 then
    c4++
  end
  if c4>=s2 then
    c4++
  end
  if c4>=s3 then
    c4++
  end
  s4 = c4
  if s4<s3 then
    _ = s3
    s3 = s4
    s4 = _
  end
  if s3<s2 then
    _ = s2
    s2 = s3
    s3 = _
  end
  if s2<s1 then
    _ = s1
    s1 = s2
    s2 = _
  end
  
  // Card 5
  c5 = random 1,48
  if c5>=s1 then
    c5++
  end
  if c5>=s2 then
    c5++
  end
  if c5>=s3 then
    c5++
  end
  if c5>=s4 then
    c5++
  end
  
  // Log the values of all five cards
  log "c1: {c1}, c2: {c2}, c3: {c3}, c4: {c4}, c5: {c5}"
end

Each card in the deck is numbered 1-52. We can convert to suit and rank afterwards, don't worry about that for now.

The first pick is easy, we just do c1 = random 1,52.

For each subsequent pick we reduce the random max by 1 to match the number of cards remaining in the deck.

We then have to compare the new pick against the previous picks to ensure it isn't a duplicate. By incrementing the number if it is equal to or greater than each previous card we effectively correct the set of numbers that we randomly chose from.

That's easier to visualise with a deck of only 5 cards. The first pick random 1,5 is picking a number from the set [1,2,3,4,5]. The next pick random 1,4 then picks from the set [1,2,3,4], but if the first pick was 3 by incrementing our second pick for any value equal to or greater than 3 the effective set becomes [1,2,4,5].

For the third pick and beyond we have to compare against multiple previous picks so the logic only works if we compare the previous picks in ascending order. That's why for each card cN variable we also assign a sorted sN variable, and those sN variables get sorted after each pick.

The final pick is simpler because we don't need to resort the variables as there is no subsequent comparison to be made.

If you want to draw more than 5 cards you should be able to extend the same pattern - each subsequent pick adds an extra sort conditional and an extra comparison against the previous pick.

You can also change the size of the deck by changing the maximum value of the random function. The first pick should have a maximum matching the size of your deck, and each subsequent pick should reduce that maximum by one.

This same problem can be generalised to any situation where you want to make multiple random choices from a set without duplication.

Let me know if this was useful!

I feel like a lot of Pulp is just figuring out ways to deal with the fact that there are no arrays.

It definitely can be like that! It does keep things constrained and conceptually simpler unless you try to work around the limitations, at which point I think it's a fun challenge.