I was wondering if there's any good code libraries available in C# for this.
Basically I need the following:
A shuffled 52 card deck. Important that it's an unbiased random shuffle.
A way to compare 2 hands to see which one is higher (eg. 3 of a kind > a pair, AQT32 > AQ852)
I could write these myself, but I KNOW for a fact that this code exists out there numerous times and I'd like to save the effort of having to do it myself.
I'm going to run the following simulations:
Flat bet until the bankroll is exhausted or doubled up. Reset at that point and keep count. This is to figure the risk of ruin for different size bets in proportion to bankroll.
Have a tiered betting system. Same thing as above
Testing of flat bet / tiered betting systems on fixed session lengths. Record win/loss. Find the median/high/low/etc.
There's also other variables I'm going to test that are specific to this advantage play.
Anyone have any ideas where to get these libraries? Thanks!
Quote: WizardI would suggest a Mersenne Twister for the random numbers. I'm sure you can find code to score a poker hand out there. However, after that, I think you're on your own. If there is off the shelf code for testing betting systems, I'm not aware of it.
Not looking for something off the shelf for testing my betting system.
I'm ONLY looking for something off the shelf that generates a shuffled 52 card deck, and something off the shelf that compares 2 poker hands.
The rest of the code has to be specific to my system.
The shuffled 52 card deck would be of great convenience since that's the most complex part of it. I'll be auditing the shuffle by keeping track of the quantity of each of the 52 cards dealt and making sure there isn't a huge deviation from the expected appearances.
Both of the things I need seem to be pretty basic functions that tons of different apps would use..
Here's how I would prepare for a simulation that uses a 52-card deck:
Random RNG = new Random();
int[] Deck = new int[52];
for (int x = 0; x < 52; x++)
{
Deck[x] = x;
}
That initializes the deck array with one of each card (0 = 2 of Clubs, 1 = 3 of Clubs, ..., 50 = Kings of Spades, 51 = Ace of Spades). The RNG object (an instance of the System.Random class) should be static or at least a member of the parent class, and instantiated only once so that it may be reused. (Creating or re-creating a System.Random class every time a new random number is needed will produce flawed results.)
To shuffle the deck, I do this:
for (int x = 0; x < 52; x++)
{
int r = RNG.Next(52);
if (r != x)
{
int tmp = Deck[x];
Deck[x] = Deck[r];
Deck[r] = tmp;
}
}
While the code speaks for itself, what it does is loop from the beginning of the deck to the end, and for each iteration, it picks a random position in the deck and swaps the current card with the card in that randomly-chosen position. If both positions point to the same location within the array, which will happen from time to time, there is no need to swap them, so it bypasses the swap in those cases. You can reuse the shuffling routine on an already-shuffled deck; there is no need to re-initialize the deck array every time you need to shuffle.
There is a way to generate "secure" random numbers in C# using the RNGCryptoServiceProvider class. I don't do it because it's significantly slower and not worth the trouble for a simulation, in my opinion.
Quote: JBI don't know if this will help, or if it will be too simplistic for your needs, but I use the built-in System.Random class when running simulations.
Here's how I would prepare for a simulation that uses a 52-card deck:Random RNG = new Random();
int[] Deck = new int[52];
for (int x = 0; x < 52; x++)
{
Deck[x] = x;
}
That initializes the deck array with one of each card (0 = 2 of Clubs, 1 = 3 of Clubs, ..., 50 = Kings of Spades, 51 = Ace of Spades). The RNG object (an instance of the System.Random class) should be static or at least a member of the parent class, and instantiated only once so that it may be reused. (Creating or re-creating a System.Random class every time a new random number is needed will produce flawed results.)
To shuffle the deck, I do this:for (int x = 0; x < 52; x++)
{
int r = RNG.Next(52);
if (r != x)
{
int tmp = Deck[x];
Deck[x] = Deck[r];
Deck[r] = tmp;
}
}
While the code speaks for itself, what it does is loop from the beginning of the deck to the end, and for each iteration, it picks a random position in the deck and swaps the current card with the card in that randomly-chosen position. If both positions point to the same location within the array, which will happen from time to time, there is no need to swap them, so it bypasses the swap in those cases. You can reuse the shuffling routine on an already-shuffled deck; there is no need to re-initialize the deck array every time you need to shuffle.
There is a way to generate "secure" random numbers in C# using the RNGCryptoServiceProvider class. I don't do it because it's significantly slower and not worth the trouble for a simulation, in my opinion.
That would be a very simplistic approach, however, because I am going to be putting real money down based on the results of the simulation, it is very important to me that it's an unbiased shuffle. I don't need anything as fancy as what they'd use in an online casino or video poker machine, but I definitely want something that's sure to not be biased.
One approach I was considering is to use the the default RNG % 52, BUT, cut it off at (int)(Max # from RNG / 52) * 52. Check if the # is >= to that and rerun if it is, to avoid the bias that certain cards are slightly more probable to be drawn. I need to fix my machine w/ visual studio on it, but I'll probably audit whatever routine I use to check that it's not biasing certain "cards"
Quote: f2dThat would be a very simplistic approach, however, because I am going to be putting real money down based on the results of the simulation, it is very important to me that it's an unbiased shuffle. I don't need anything as fancy as what they'd use in an online casino or video poker machine, but I definitely want something that's sure to not be biased.
One approach I was considering is to use the the default RNG % 52, BUT, cut it off at (int)(Max # from RNG / 52) * 52. Check if the # is >= to that and rerun if it is, to avoid the bias that certain cards are slightly more probable to be drawn. I need to fix my machine w/ visual studio on it, but I'll probably audit whatever routine I use to check that it's not biasing certain "cards"
Using the .Next() method of the Random class eliminates bias, and always returns a number within the range you want. For example, above I use RNG.Next(52) which returns an unbiased random number which is greater than or equal to zero and and less than 52 (0 ... 51).
You can test this by creating a Random object and repeatedly calling its .Next() method with a parameter of 52. Count how many times each result comes up. I'm sure you'll find that the results aren't biased.
If you do consider them to be biased, then you may want to consider using the RNGCryptoServiceProvider class. It isn't terribly complicated, but it is noticeably slower than the Random class. If you use it, the proper way to eliminate bias is this: each time you need a new number, repeatedly retrieve random bytes from it until you get one that falls within the range you need. (If you get a byte in the range 52 ... 255 then discard it and get another byte. Repeat until you get one from 0 ... 51.) Then use this byte in the shuffling procedure.
PS It would be simply programs that crunch the numbers, graphics not needed!
Then I can do a simple mod to determine rank and a divide for suit
Demango, learn c#
Once you get the hang of it, it's relatively easy to figure out how to simulate game results
I'm only being overly careful of any errors or biases because I'll lose money if I'm making mistakes
You want to use a Fisher-Yates shuffle : http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
It's almost the same, but you only select a random card from the unselected portion of the deck ((=> x)) There's a discussion on the problem in that wiki article, but simply put going over the whole deck can results in n^n possible swaps, but a deck can only have n! possibly combinations. n! is not a factor of n^n, so the n^n result must have some bias.
Just ran the algorithm for 3 card deck.
Combinations 132 and 213 are 25% more likely than the others.
Quote: thecesspitJust ran the algorithm for 3 card deck.
Combinations 132 and 213 are 25% more likely than the others.
I just ran a simulation of 1 billion trials shuffling a 3-card deck using my method and the Fisher-Yates method, and the results were effectively the same:
Permutation | My Method | Fisher-Yates Method |
---|---|---|
123 | 166790034 (16.7%) | 166800546 (16.7%) |
132 | 166810586 (16.7%) | 166802104 (16.7%) |
213 | 166807272 (16.7%) | 166814580 (16.7%) |
231 | 166816233 (16.7%) | 166809240 (16.7%) |
312 | 166810793 (16.7%) | 166809549 (16.7%) |
321 | 166817160 (16.7%) | 166816059 (16.7%) |
Could I trouble you to post the code that you used which showed a bias?
Here is the code I used for a C# console application:
using System;
using System.Threading;
namespace ShuffleTesting
{
class Program
{
static object Locker = new object();
static Random RNG = new Random();
static int[] Deck1 = { 1, 2, 3 };
static int[] Deck2 = { 1, 2, 3 };
static long[] MyMethod = new long[322];
static long[] FyMethod = new long[322];
static long Trials = 0;
static void Main(string[] args)
{
Thread t1 = new Thread(RunSimulation);
Thread t2 = new Thread(Monitor);
t1.Start();
t2.Start();
t2.Join();
}
static void RunSimulation()
{
while (true)
{
// MY METHOD
for (int x = 0; x < 3; x++)
{
int r = RNG.Next(3);
int t = Deck1[x];
Deck1[x] = Deck1[r];
Deck1[r] = t;
}
// FISHER-YATES METHOD
for (int x = 2; x > 0; x--)
{
int r = RNG.Next(x + 1);
int t = Deck2[x];
Deck2[x] = Deck2[r];
Deck2[r] = t;
}
// UPDATE RESULTS
lock (Locker)
{
MyMethod[(Deck1[0] * 100) + (Deck1[1] * 10) + Deck1[2]]++;
FyMethod[(Deck2[0] * 100) + (Deck2[1] * 10) + Deck2[2]]++;
Trials++;
}
}
}
static void Monitor()
{
while (true)
{
lock (Locker)
{
Console.Clear();
Console.WriteLine("PERM\tMY METHOD\tFY METHOD");
Console.WriteLine();
Console.WriteLine("123\t" + MyMethod[123] + "\t" + FyMethod[123]);
Console.WriteLine("132\t" + MyMethod[132] + "\t" + FyMethod[132]);
Console.WriteLine("213\t" + MyMethod[213] + "\t" + FyMethod[213]);
Console.WriteLine("231\t" + MyMethod[231] + "\t" + FyMethod[231]);
Console.WriteLine("312\t" + MyMethod[312] + "\t" + FyMethod[312]);
Console.WriteLine("321\t" + MyMethod[321] + "\t" + FyMethod[321]);
Console.WriteLine();
Console.WriteLine("TRIALS\t" + Trials);
}
Thread.Sleep(1000);
}
}
}
}
I can't work out why your getting the results you are though, so that's a mystery to me.
With a 3-card deck, where each card is called 0, 1, or 2, there are 6 possible states: 012, 021, 102, 120, 201, and 210. With my shuffle routine there are 3 swaps, each with 3 possible positions to swap with. So that's a total of 6*(33) = 162 possible outcomes (because the initial state can be any of the 6 possibilities). Using a spreadsheet to list all 162 possibilities, and tallying the number of times each state is achieved after the shuffling routine, all 6 states have 27 ways to arrive at them.
However, I think I know why you came up with a biased shuffle: if you only look at one initial state, then the shuffle is biased. The bias only disappears when the algorithm is repeatedly applied to an already-shuffled deck.
In light of that, I agree that the Fisher-Yates method is better, if for no other reason than to make sure that the very first shuffle is unbiased.
Quote: JBUsing that technique, I still come up with an unbiased result... sort of.
With a 3-card deck, where each card is called 0, 1, or 2, there are 6 possible states: 012, 021, 102, 120, 201, and 210. With my shuffle routine there are 3 swaps, each with 3 possible positions to swap with. So that's a total of 6*(33) = 162 possible outcomes (because the initial state can be any of the 6 possibilities). Using a spreadsheet to list all 162 possibilities, and tallying the number of times each state is achieved after the shuffling routine, all 6 states have 27 ways to arrive at them.
However, I think I know why you came up with a biased shuffle: if you only look at one initial state, then the shuffle is biased. The bias only disappears when the algorithm is repeatedly applied to an already-shuffled deck.
In light of that, I agree that the Fisher-Yates method is better, if for no other reason than to make sure that the very first shuffle is unbiased.
That's it then, I started with 012. That was bugging me. I hadn't realised that the next shuffle was dependent on the previous shuffle, but of course that's what your code says.
Quote: thecesspitThat's it then, I started with 012. That was bugging me. I hadn't realised that the next shuffle was dependent on the previous shuffle, but of course that's what your code says. Could this explain why there seems to be a slightly large range of results on your method than thr FY method?
I actually never knew before now that the method I have always used was dependent upon the input. I will be using the Fisher-Yates method in all future projects from now on, since it produces an unbiased shuffle regardless of the input, which is how a shuffle routine should work.
Listing all the Fisher-Yates possibilities for the same 3-card deck, there are 6*3! = 36 total possible starting states and swaps, so the reason my old method had more was because, as you initially stated, there are nn possible swaps instead of n! swaps.
In any event, I love this kind of stuff and learning new things, so I thank you for pointing me towards that Wikipedia article!
To the original poster -- do NOT use the shuffle routine I listed earlier. Use the following code instead:
for (int x = 51; x > 0; x--)
{
int r = RNG.Next(x + 1);
int t = Deck[x];
Deck[x] = Deck[r];
Deck[r] = t;
}
http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html
Is where I came across this first. It's got a very nice set of diagrams describing the problem.
Or it might have been a daily WTF (www.thedailywtf.com).
SPOILER (exercise ansers)
The answers are (a) 2^(n-1), (b) t(n), given by the recurrence t(0)=t(1)=1 and t(n)=t(n-1)+(n-1)*t(n-2), which is greater than sqrt(n!) (in fact, t(n) is on the order of n^(n/2)*e^(-n/2+sqrt(n)-1/4)/sqrt(2)), (c) the nth Catalan number, choose(2*n,n)/(n+1). (a) is the least likely result and (b) is the most likely result for n>=18.
You can see that this is quite a difference as n becomes large, certainly for n=52. However, this is just the worse case. For many statistical purposes, what might matter is what fraction of the permutations fall within a certain delta of 1/n! probability. This could be a very high fraction for large n for all I know.
Knuth gives two citations about this:
D. B. Robbins and E.D. Bolker. Aequationes Mathematicae 22 (1981) 268-292.
D. Goldstein and D. Moews. Aequationes Mathematicae 65 (2003) 3-30.
The first gives all the results above except that case (b) is the most likely for n>=18, which is proven by the second paper.
namespace __Card_Sim
{
class Program
{
static void Main(string[] args)
{
Deck deck = new Deck();
Audit audit = new Audit();
for (int i = 0; i < 1000000000; i++)
{
audit.Add(i % 10, deck.Deal());
if (i % 10 == 0)
{
deck.Shuffle();
}
}
audit.PrintReport();
}
}
}
namespace __Card_Sim
{
public class Card
{
public int Rank; // 1 = A, 2 = 2 ... , 10 = 10, 11 = J, 12 = Q, 13 = K
public string Suit;
public Card(int id)
{
if (id < 0 || id > 51)
{
throw new Exception("Card out of range");
}
Rank = (id % 13) + 1;
int suitnumber = id / 13;
switch (suitnumber)
{
case 0: Suit = "c"; break;
case 1: Suit = "d"; break;
case 2: Suit = "h"; break;
case 3: Suit = "s"; break;
}
}
}
public class Deck
{
private Random RNG;
private List<int> used;
public Deck()
{
RNG = new Random(DateTime.Now.Millisecond);
used = new List<int>();
}
public Card Deal()
{
int card = RNG.Next(52);
while (used.Contains(card))
{
card = RNG.Next(52);
}
used.Add(card);
return new Card(card);
}
public void Shuffle()
{
used = new List<int>();
}
}
}
namespace __Card_Sim
{
public class Audit
{
private Dictionary<int, Dictionary<int,int>> tracker;
private Dictionary<int, int> counter;
public Audit()
{
tracker = new Dictionary<int, Dictionary<int, int>>();
counter = new Dictionary<int, int>();
}
public void Add(int index, Card card)
{
if (!tracker.ContainsKey(index))
{
tracker.Add(index, new Dictionary<int, int>());
counter.Add(index, 0);
for (int i = 1; i <= 52; i++)
{
tracker[index].Add(i, 0);
}
}
int cardnumber = card.Rank;
switch (card.Suit)
{
case "d": cardnumber += 13; break;
case "h": cardnumber += 26; break;
case "s": cardnumber += 39; break;
}
tracker[index][cardnumber]++;
counter[index]++;
}
public void PrintReport()
{
StreamWriter io = new StreamWriter(@"F:\cardsim\audit.txt");
int expected = 0;
foreach (int key in tracker.Keys)
{
io.WriteLine("Card #: " + key + " Dealt " + counter[key] +" times");
expected = counter[key] / 52;
io.WriteLine("Expected QTY: " + expected);
for (int i = 1; i <= 52; i++)
{
int deviation = tracker[key] - expected;
io.WriteLine(i + ": " + tracker[key] + " | " + deviation);
}
}
io.Close();
}
}
}
Card #: 0 Dealt 100000000 times
Expected QTY: 1923076
1: 1921809 | -1267
2: 1922714 | -362
3: 1922426 | -650
4: 1922665 | -411
5: 1921151 | -1925
6: 1922167 | -909
7: 1925444 | 2368
8: 1919322 | -3754
9: 1925711 | 2635
10: 1924225 | 1149
11: 1924166 | 1090
12: 1922701 | -375
13: 1925841 | 2765
14: 1922545 | -531
15: 1923025 | -51
16: 1920850 | -2226
17: 1922051 | -1025
18: 1923827 | 751
19: 1923776 | 700
20: 1922821 | -255
21: 1924667 | 1591
22: 1923497 | 421
23: 1923274 | 198
24: 1921156 | -1920
25: 1921790 | -1286
26: 1921527 | -1549
27: 1924063 | 987
28: 1925072 | 1996
29: 1925121 | 2045
30: 1924036 | 960
31: 1923261 | 185
32: 1923746 | 670
33: 1922322 | -754
34: 1921416 | -1660
35: 1921296 | -1780
36: 1924855 | 1779
37: 1922212 | -864
38: 1922360 | -716
39: 1921755 | -1321
40: 1921139 | -1937
41: 1924727 | 1651
42: 1923450 | 374
43: 1924421 | 1345
44: 1923808 | 732
45: 1923742 | 666
46: 1923421 | 345
47: 1921837 | -1239
48: 1923219 | 143
49: 1922713 | -363
50: 1921995 | -1081
51: 1923718 | 642
52: 1925147 | 2071
Card #: 1 Dealt 100000000 times
Expected QTY: 1923076
1: 1923341 | 265
2: 1923234 | 158
3: 1921045 | -2031
4: 1924586 | 1510
5: 1920615 | -2461
6: 1924027 | 951
7: 1921339 | -1737
8: 1922386 | -690
9: 1925344 | 2268
10: 1921439 | -1637
11: 1921876 | -1200
12: 1923853 | 777
13: 1920918 | -2158
14: 1923036 | -40
15: 1923323 | 247
16: 1923637 | 561
17: 1924229 | 1153
18: 1922729 | -347
19: 1925038 | 1962
20: 1921873 | -1203
21: 1922179 | -897
22: 1924048 | 972
23: 1923134 | 58
24: 1921062 | -2014
25: 1923374 | 298
26: 1925727 | 2651
27: 1921715 | -1361
28: 1923085 | 9
29: 1924584 | 1508
30: 1925981 | 2905
31: 1924412 | 1336
32: 1920227 | -2849
33: 1923237 | 161
34: 1920903 | -2173
35: 1921266 | -1810
36: 1922280 | -796
37: 1921789 | -1287
38: 1922105 | -971
39: 1923183 | 107
40: 1926270 | 3194
41: 1923261 | 185
42: 1921876 | -1200
43: 1923282 | 206
44: 1922404 | -672
45: 1922971 | -105
46: 1925385 | 2309
47: 1925092 | 2016
48: 1925056 | 1980
49: 1922316 | -760
50: 1924749 | 1673
51: 1924444 | 1368
52: 1920735 | -2341
Card #: 2 Dealt 100000000 times
Expected QTY: 1923076
1: 1924480 | 1404
2: 1922877 | -199
3: 1922924 | -152
4: 1924614 | 1538
5: 1922656 | -420
6: 1924473 | 1397
7: 1921439 | -1637
8: 1923788 | 712
9: 1921794 | -1282
10: 1921969 | -1107
11: 1923463 | 387
12: 1925390 | 2314
13: 1921738 | -1338
14: 1922341 | -735
15: 1922463 | -613
16: 1924291 | 1215
17: 1923850 | 774
18: 1923063 | -13
19: 1923360 | 284
20: 1923557 | 481
21: 1923276 | 200
22: 1920976 | -2100
23: 1923813 | 737
24: 1925449 | 2373
25: 1922807 | -269
26: 1922911 | -165
27: 1921912 | -1164
28: 1921012 | -2064
29: 1920657 | -2419
30: 1924042 | 966
31: 1923902 | 826
32: 1921777 | -1299
33: 1923070 | -6
34: 1924272 | 1196
35: 1924145 | 1069
36: 1922928 | -148
37: 1922828 | -248
38: 1921403 | -1673
39: 1924408 | 1332
40: 1922046 | -1030
41: 1924066 | 990
42: 1922936 | -140
43: 1919771 | -3305
44: 1922022 | -1054
45: 1923393 | 317
46: 1923622 | 546
47: 1924821 | 1745
48: 1923603 | 527
49: 1921521 | -1555
50: 1924052 | 976
51: 1924424 | 1348
52: 1923605 | 529
Card #: 3 Dealt 100000000 times
Expected QTY: 1923076
1: 1923851 | 775
2: 1920547 | -2529
3: 1922869 | -207
4: 1918737 | -4339
5: 1923211 | 135
6: 1923518 | 442
7: 1921612 | -1464
8: 1923637 | 561
9: 1923339 | 263
10: 1924055 | 979
11: 1923047 | -29
12: 1925146 | 2070
13: 1923349 | 273
14: 1922563 | -513
15: 1924065 | 989
16: 1923979 | 903
17: 1921856 | -1220
18: 1922677 | -399
19: 1923640 | 564
20: 1920150 | -2926
21: 1926017 | 2941
22: 1922330 | -746
23: 1925196 | 2120
24: 1923874 | 798
25: 1921442 | -1634
26: 1924326 | 1250
27: 1921269 | -1807
28: 1923761 | 685
29: 1924997 | 1921
30: 1922144 | -932
31: 1923192 | 116
32: 1921708 | -1368
33: 1921312 | -1764
34: 1922658 | -418
35: 1923507 | 431
36: 1924024 | 948
37: 1923033 | -43
38: 1920828 | -2248
39: 1922260 | -816
40: 1922440 | -636
41: 1922232 | -844
42: 1925092 | 2016
43: 1925373 | 2297
44: 1922987 | -89
45: 1924222 | 1146
46: 1923118 | 42
47: 1921759 | -1317
48: 1922397 | -679
49: 1925143 | 2067
50: 1924553 | 1477
51: 1923594 | 518
52: 1923364 | 288
Card #: 4 Dealt 100000000 times
Expected QTY: 1923076
1: 1923958 | 882
2: 1923148 | 72
3: 1920819 | -2257
4: 1925680 | 2604
5: 1920577 | -2499
6: 1920542 | -2534
7: 1922711 | -365
8: 1921913 | -1163
9: 1923627 | 551
10: 1925570 | 2494
11: 1923767 | 691
12: 1921991 | -1085
13: 1921951 | -1125
14: 1922381 | -695
15: 1920553 | -2523
16: 1923995 | 919
17: 1923225 | 149
18: 1925070 | 1994
19: 1923260 | 184
20: 1922546 | -530
21: 1924350 | 1274
22: 1921498 | -1578
23: 1923777 | 701
24: 1924833 | 1757
25: 1922125 | -951
26: 1923325 | 249
27: 1921769 | -1307
28: 1922741 | -335
29: 1924331 | 1255
30: 1924034 | 958
31: 1922512 | -564
32: 1923596 | 520
33: 1921167 | -1909
34: 1922301 | -775
35: 1921309 | -1767
36: 1925002 | 1926
37: 1924124 | 1048
38: 1927153 | 4077
39: 1921099 | -1977
40: 1925011 | 1935
41: 1924354 | 1278
42: 1924893 | 1817
43: 1920951 | -2125
44: 1923111 | 35
45: 1923919 | 843
46: 1922905 | -171
47: 1922248 | -828
48: 1923044 | -32
49: 1923228 | 152
50: 1924410 | 1334
51: 1921104 | -1972
52: 1922492 | -584
Card #: 5 Dealt 100000000 times
Expected QTY: 1923076
1: 1926100 | 3024
2: 1921109 | -1967
3: 1925099 | 2023
4: 1921603 | -1473
5: 1923183 | 107
6: 1922733 | -343
7: 1922602 | -474
8: 1924436 | 1360
9: 1921352 | -1724
10: 1925474 | 2398
11: 1922063 | -1013
12: 1920668 | -2408
13: 1921137 | -1939
14: 1922176 | -900
15: 1924439 | 1363
16: 1923449 | 373
17: 1923227 | 151
18: 1923853 | 777
19: 1925263 | 2187
20: 1920467 | -2609
21: 1925224 | 2148
22: 1923518 | 442
23: 1925123 | 2047
24: 1921498 | -1578
25: 1925558 | 2482
26: 1921181 | -1895
27: 1924365 | 1289
28: 1924417 | 1341
29: 1924036 | 960
30: 1921690 | -1386
31: 1922695 | -381
32: 1920467 | -2609
33: 1923507 | 431
34: 1921700 | -1376
35: 1923871 | 795
36: 1924478 | 1402
37: 1922624 | -452
38: 1923592 | 516
39: 1924328 | 1252
40: 1922589 | -487
41: 1922064 | -1012
42: 1922160 | -916
43: 1922560 | -516
44: 1922834 | -242
45: 1923196 | 120
46: 1922616 | -460
47: 1923494 | 418
48: 1922758 | -318
49: 1921012 | -2064
50: 1921764 | -1312
51: 1923511 | 435
52: 1925137 | 2061
Card #: 6 Dealt 100000000 times
Expected QTY: 1923076
1: 1921831 | -1245
2: 1922857 | -219
3: 1922856 | -220
4: 1922466 | -610
5: 1925398 | 2322
6: 1922860 | -216
7: 1924255 | 1179
8: 1924596 | 1520
9: 1919873 | -3203
10: 1923437 | 361
11: 1922808 | -268
12: 1924355 | 1279
13: 1922213 | -863
14: 1922560 | -516
15: 1921984 | -1092
16: 1923328 | 252
17: 1923491 | 415
18: 1924071 | 995
19: 1924010 | 934
20: 1922449 | -627
21: 1922850 | -226
22: 1923327 | 251
23: 1923800 | 724
24: 1923098 | 22
25: 1922994 | -82
26: 1922098 | -978
27: 1922436 | -640
28: 1922013 | -1063
29: 1922986 | -90
30: 1921313 | -1763
31: 1925599 | 2523
32: 1923368 | 292
33: 1922058 | -1018
34: 1922421 | -655
35: 1922857 | -219
36: 1920892 | -2184
37: 1924713 | 1637
38: 1922482 | -594
39: 1925280 | 2204
40: 1922378 | -698
41: 1923196 | 120
42: 1921231 | -1845
43: 1923265 | 189
44: 1921420 | -1656
45: 1922493 | -583
46: 1922016 | -1060
47: 1923516 | 440
48: 1921509 | -1567
49: 1926004 | 2928
50: 1924056 | 980
51: 1926340 | 3264
52: 1924293 | 1217
Card #: 7 Dealt 100000000 times
Expected QTY: 1923076
1: 1923293 | 217
2: 1924819 | 1743
3: 1922343 | -733
4: 1924487 | 1411
5: 1924411 | 1335
6: 1922054 | -1022
7: 1925105 | 2029
8: 1921019 | -2057
9: 1924648 | 1572
10: 1923671 | 595
11: 1923470 | 394
12: 1922322 | -754
13: 1921857 | -1219
14: 1923545 | 469
15: 1922452 | -624
16: 1921027 | -2049
17: 1924640 | 1564
18: 1921407 | -1669
19: 1926962 | 3886
20: 1922390 | -686
21: 1924519 | 1443
22: 1921753 | -1323
23: 1922285 | -791
24: 1921834 | -1242
25: 1922918 | -158
26: 1922741 | -335
27: 1922069 | -1007
28: 1923879 | 803
29: 1922081 | -995
30: 1925141 | 2065
31: 1922966 | -110
32: 1922394 | -682
33: 1923509 | 433
34: 1920783 | -2293
35: 1923193 | 117
36: 1921579 | -1497
37: 1922895 | -181
38: 1921956 | -1120
39: 1923343 | 267
40: 1923493 | 417
41: 1925275 | 2199
42: 1922458 | -618
43: 1923559 | 483
44: 1922879 | -197
45: 1925279 | 2203
46: 1922336 | -740
47: 1923919 | 843
48: 1921883 | -1193
49: 1923747 | 671
50: 1922799 | -277
51: 1921798 | -1278
52: 1922815 | -261
Card #: 8 Dealt 100000000 times
Expected QTY: 1923076
1: 1923655 | 579
2: 1922290 | -786
3: 1921696 | -1380
4: 1924888 | 1812
5: 1923662 | 586
6: 1923410 | 334
7: 1921318 | -1758
8: 1921116 | -1960
9: 1920591 | -2485
10: 1923643 | 567
11: 1923510 | 434
12: 1924156 | 1080
13: 1923501 | 425
14: 1921688 | -1388
15: 1923238 | 162
16: 1922093 | -983
17: 1925768 | 2692
18: 1922801 | -275
19: 1925420 | 2344
20: 1921404 | -1672
21: 1923645 | 569
22: 1924109 | 1033
23: 1922363 | -713
24: 1922523 | -553
25: 1921436 | -1640
26: 1921394 | -1682
27: 1924970 | 1894
28: 1922623 | -453
29: 1920701 | -2375
30: 1921139 | -1937
31: 1923480 | 404
32: 1921588 | -1488
33: 1924365 | 1289
34: 1924207 | 1131
35: 1922980 | -96
36: 1923737 | 661
37: 1925116 | 2040
38: 1922840 | -236
39: 1922608 | -468
40: 1925618 | 2542
41: 1924762 | 1686
42: 1922738 | -338
43: 1923349 | 273
44: 1923730 | 654
45: 1920747 | -2329
46: 1922274 | -802
47: 1923468 | 392
48: 1924253 | 1177
49: 1924515 | 1439
50: 1923893 | 817
51: 1921658 | -1418
52: 1923323 | 247
Card #: 9 Dealt 100000000 times
Expected QTY: 1923076
1: 1919928 | -3148
2: 1922600 | -476
3: 1922757 | -319
4: 1923106 | 30
5: 1922317 | -759
6: 1923304 | 228
7: 1921632 | -1444
8: 1923684 | 608
9: 1923492 | 416
10: 1924294 | 1218
11: 1923360 | 284
12: 1923459 | 383
13: 1923339 | 263
14: 1926541 | 3465
15: 1922345 | -731
16: 1922469 | -607
17: 1921791 | -1285
18: 1923462 | 386
19: 1923627 | 551
20: 1923268 | 192
21: 1923492 | 416
22: 1922407 | -669
23: 1922840 | -236
24: 1921895 | -1181
25: 1925006 | 1930
26: 1922381 | -695
27: 1922923 | -153
28: 1923971 | 895
29: 1922671 | -405
30: 1923298 | 222
31: 1922188 | -888
32: 1922821 | -255
33: 1924223 | 1147
34: 1923096 | 20
35: 1924126 | 1050
36: 1922840 | -236
37: 1924789 | 1713
38: 1922065 | -1011
39: 1924896 | 1820
40: 1922691 | -385
41: 1921042 | -2034
42: 1924557 | 1481
43: 1923844 | 768
44: 1922798 | -278
45: 1923890 | 814
46: 1921310 | -1766
47: 1923527 | 451
48: 1922982 | -94
49: 1922956 | -120
50: 1922142 | -934
51: 1922958 | -118
52: 1922600 | -476
Because of the simplistic way of checking for removal, efficiency of this goes WAY down the further you go into the deck.
It also doesn't check if the deck's used up. If you try to get another card after all 52 are gone it will go into an infinite loop. But since I made this for me to use.. that's not my problem.
I did it this way because I will be using no more then 50% of the deck
Testing for a billion cards took just a few minutes.