There is a total of 52!
, or 8e68
(an 8 with 68 zeros behind), unique possible ways of shuffling a deck of standard playing cards. Each time a deck is dealt, it is a new combination that has, most likely, never been seen before by anyone on Earth.
For one of my personal projects I had to generate a deck of 52 cards in a random order, with the following constraints:
- Each card should be returned by the API only when our user wants to reveal the card, not all the deck at once before, such that you cannot look into the network response to see the next cards,
- For that reason, it has to be asynchronous, a user requests a single card at a time, not the whole deck,
- Therefore, it has to be deterministic, since we get a single card at a time, querying a second card, should take it from the same deck as the first.
Those constraints would all be relatively standards if you want to generate a random deck for an online game for example, and the principles can be adapted for any kind of deck, wether it’s a standard 52 cards deck or your Magic the Gathering cards.
You can see an online demo of the system on my website, RandomDeck
In our case, we’re going to be working backwards, starting by building a deterministic and only later incorporating more secure principles. For the purpose of the example, I wrote the code in PHP and Angular, but the concepts are applicable to any programming language.
The seed of a solution
When you generate a random number, using the rand() function, the underlying random number generator will use what’s called a seed, an initial value that will determine the following numbers that will be generated.
The cool thing is that we can set that seed using the srand() function. If we set a hardcoded seed, like 42
, everytime we reload the page, we will get exactly the same sequence of random numbers (might depend based on the environment where you run the script.
srand(42);
echo rand(0, 100) . '<br>'; // Prints 38
echo rand(0, 100) . '<br>'; // Prints 32
echo rand(0, 100) . '<br>'; // Prints 94
If you remove the first line, you will get different results every time you reload the page, but with this line, you will always have the same numbers generated.
We can leverage this to get a deterministic and asynchronous sytem! What our frontend has to do is generate a random seed (or query the backend for one), and pass this same seed to the backend every time we want a new card, along with the number of the card we want to draw from 1 to 52.
const CARDS_LIST = [
'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8', 'S9', 'S10', 'S11', 'S12', 'S13',
'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9', 'H10', 'H11', 'H12', 'H13',
'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9', 'D10', 'D11', 'D12', 'D13',
'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'C10', 'C11', 'C12', 'C13'
];
function showCards($seed, $position)
{
// Set the seed to the one sent by the frontend
mt_srand($seed);
$cards = $CARDS_LIST;
// Generate the random order of the deck, always the same for the same seed
$order = array_map(function ($v) { return mt_rand(); }, range(1, count($cards)));
// "Shuffle" the deck using the order generated
array_multisort($order, $cards);
// Draw the card corresponding to the position set by the frontend
return $cards[$position - 1];
}
This will give you a fully functional system that allows you to draw any card from any position, without exposing the other cards of the deck!
Possible improvements
One improvement that I would suggest if you are concerned about building a more secured system would be to hide the seed.
What I mean is that at the moment, the frontend generates and is aware of the seed used to generate the deck, which means that a user could get this seed and simulate the system on their own computer, to have the result of the deck without dealing the cards using your API.
There are multiple ways to solve this issue, which are all relatively simple:
- You can get the seed from the frontend, and modify it slightly using an algorithm to then randomize the cards. For example you could multiply the seed by 2, or make more complex calculations.
- You can store the seeds in a database, and return the ID corresponding to the seed to the frontend, which means that only the backend is in control of the seed.
The second solution definitely requires more work, but it allows for more flexibility and potential extended functionalities in the future.