Software simulator and unexpected results

#1
Dear all,

let me briefly introduce myself. My name is Fabio, I'm 27 years old, PhD student in Artificial Intelligence and I recently started to study a few books about blackjack.

What I'm trying to do now is to write a simple blackjack software simulator, in order to test the effects coming from using different counting systems, different way of calculating the true count, etc.

The first "beta" version of my simulator is ready. But I'm getting an unexpected result. It looks like that just applying the basic strategy, without any card counting (betting always the same amount), after 10,000 runs of 1,000 hands each (averaged together) the software is able to score a (very small) profit. According to the literature I didn't expect this result at all. I spent a couple of days debugging my code, but I can't really find any issues. Therefore what I'd like to ask you is if, under the following conditions, is it normal for the player to have a positive edge.

These conditions are:
- 6 decks used;
- dealer stands on S17;
- penetration rate (I guess this is not that important if counting is not used): 75%;
- unlimited splits;
- split aces are considered like every other split hand (you can double, split again or hit as many cards you want);
- double allowed on any two starting cards and after any split;
- no surrender;
- no insurance;
- natural pays 3-to-2 (before paying, the dealer checks for blackjack if he has a T-valued or an A card exposed).

Thanks in advance to anybody who would like to spend few minutes helping me... :)
 

johndoe

Well-Known Member
#2
Well, there's clearly a bug somewhere, but I'm not sure what we can do to help without seeing your code. With the rules you state you should be in the 0.16% ballpark house edge. (Not sure off-hand what unlimited splits give you.)

The number of rounds you're simulating is on the small size; go to 1 billion at least. 10M is ok for testing but not accurate enough to draw real conclusions.
 
#3
johndoe said:
Well, there's clearly a bug somewhere, but I'm not sure what we can do to help without seeing your code. With the rules you state you should be in the 0.16% ballpark house edge. (Not sure off-hand what unlimited splits give you.)

The number of rounds you're simulating is on the small size; go to 1 billion at least. 10M is ok for testing but not accurate enough to draw real conclusions.
Thank you very much for your prompt reply, John.

What was important for me was to have a confirmation that the result I'm getting are clearly wrong. I didn't want to flood with C++ code a blackjack forum (particularly with my first post :p), but if you really insist I can quickly prepare a pseudo-code scheme of the main() of my application and post it here... :)

PS: do you really think that 10,000 runs are not enough? Of course it's not the best possible approximation ever, but my feeling, up to date, is that the graphs of the average results gathered from these simulation are quite smooth even with "just" 10,000 runs. Is it typical for blackjack simulators to work on such a higher magnitude?
 
#5
Hi guys,

as I announced before, I post here the pseudo-code of the main function of my simulator.

Code:
place the bet
deal the first cards
if the dealer has an ace exposed offer insurance
check if player has been dealt a natural
	yes:
		check if the dealer has been dealt a natural:
			yes:
				check if the insurance has been bought:
					yes:
						pay insurance
				push
			no:
				pay blackjack 3-to-2
	no:
		check if the dealer has been dealt a natural:
			yes:
				check if the insurance has been bought:
					yes:
						pay insurance		
				hand ends
			no:
				play the hand regularly
I'm not an expert of the game, so my guess is that I'm doing something wrong implementing the game order itself. Debugging the code, in fact, I can see that the basic strategy works perfectly, both for soft and hard hands, as well as the splits and the doubles look alright.

I'm not really sure about how I manage the blackjacks. Is it ok to check for the dealer's blackjack before the hand starts even if the player doesn't have a natural? Or this is typically done only when an ace is exposed and therefore the insurance has been previously offered (i didn't mention the insurance before because at the moment I've set the simulator for buying it with probability 0.0)? I'm pointing out the management of naturals because modifying my simulator for paying them 2-to-1 instead than 3-to-2 the results become much more similar to what I was expecting.

It's really weird when you're looking for a bug that you can't find... :-/
 

johndoe

Well-Known Member
#6
The logic looks right to me. Though usually it's the dealer that checks for BJ first. But if you have one player it doesn't matter.

In the most common games, the dealer checks for BJ before anything is played (if an A or 10 is showing), and if he has a natural, the hand ends right there.

But why would making the payout 2-1 give you better results? Doing that would make even more profit for the player, which is even more wrong. You should see a loss, not a profit.
 

Sonny

Well-Known Member
#7
Make sure that when you split aces and get a ten that the program does not pay 3:2 for that hand. That mistake would decrease the house edge by a little over 0.22%, which would give your player a slight advantage.

-Sonny-
 
#8
johndoe said:
The logic looks right to me. Though usually it's the dealer that checks for BJ first. But if you have one player it doesn't matter.

In the most common games, the dealer checks for BJ before anything is played (if an A or 10 is showing), and if he has a natural, the hand ends right there.

But why would making the payout 2-1 give you better results? Doing that would make even more profit for the player, which is even more wrong. You should see a loss, not a profit.
Thanks for the explanation. It looks like you don't believe that modifying my code in that way (checking for dealer's BJ before the player can act) would be enough to make my code working, but I'll give it a try anyway... :)

You're right on the last point. I was wrong in my previous post, since on that test I modified the pay-out from 3-to-2 to 1-to-1 and not to 2-to-1 as mentioned before.
 
#9
Sonny said:
Make sure that when you split aces and get a ten that the program does not pay 3:2 for that hand. That mistake would decrease the house edge by a little over 0.22%, which would give your player a slight advantage.

-Sonny-
Thanks for your input, Sonny. Unfortunately I think that this is not the case, since I do all the checks for the naturals before actually "playing" the hand (i.e. hit/stand/double/split/etc). But I'll give it a look.
 
#10
In case someone could find a mistake that I can't really see, I'm attaching to this post one of the history files generated by my software. I've to say that I'm really getting crazy trying to figure out what is going on!

PS: of course I don't pretend anybody to help me, but every bit of help would be greatly appreciated!

PPS: I'll publish on line the source code for the simulator (as I've always done for all my softwares) whenever it will be working. So, maybe the efforts we're spending on it will be helpful for somebody in the future... :)
 

Attachments

Canceler

Well-Known Member
#11
Somebody else look at this and see if you think the player won too much here:

Hand # 170
Stack (before betting): 5090
Bet amount: 5
Player's Hand #1: 8s 8d
Player's total Hand #1: 16
Dealer exposed card: 8s
Hand #1: The player splits
Hand #1: 8s Th
Hand #2: 8d 8d
Hand #1: The player stands
Hand #2: The player splits
Hand #2: 8d 4h
Hand #3: 8d 8c
Hand #2: The player hits
Player's Hand #2: 8d 4h 3c
Player's total Hand #2: 15
Hand #2: The player hits
Player's Hand #2: 8d 4h 3c 7h
Player's total Hand #2: 22
Hand #3: The player splits
Hand #3: 8d Qh
Hand #4: 8c Js
Hand #3: The player stands
Hand #4: The player stands
Dealer's hand: 8s 9d
Dealer's total: 17
The dealer stands
The player wins Hand #1 (10)
The player wins Hand #2 (10)
The player wins Hand #3 (10)
The player wins Hand #4 (10)

Hand # 171
Stack (before betting): 5110
 

Sonny

Well-Known Member
#12
Good eye Canceler! The player somehow won $10 with a busted bet on hand #2. He should have lost $5 on hand #2 and won $5 on each of the other 3 hands. His bankroll should be 5100, not 5110, right?

-Sonny-
 
#15
Here we are! :)

Having fixed the bug you've correctly pointed out, now my simulator returns results that, if not 100% correct, for sure look much better than before. I attach to this post the graphs resulting from 10,000 iterations of 1,000 hands played with and without using a counting system respectively. In the first case, the counting system is the Hi/Lo and the betting amounts are scaled according to a "conservative" interpretation of the Kelly Criterion (conservative as defined by Scoblete in his "Best Blackjack" book).

I can also see why you were saying that 10,000 runs are not enough to obtain smooth data.

I'll spend some time to clean up and rearrange the code, tonight, then I'll post here the first working version of my simulator. Hope you will appreciate that.

Thanks a lot, guys!
 

Attachments

sagefr0g

Well-Known Member
#16
fabietto said:
.....

PPS: I'll publish on line the source code for the simulator (as I've always done for all my softwares) whenever it will be working. So, maybe the efforts we're spending on it will be helpful for somebody in the future... :)
i for one would like to see the code when your ready to post it.
 
#17
sagefr0g said:
i for one would like to see the code when your ready to post it.
Hi Sage,

thanks for your interest. Please find the sources attached to this message. Unfortunately I haven't found the time tonight to clean up the code, which is a little bit messy. I'll keep working on it during next days, implementing new functions and making it more user-friendly. I think that as it is it might be anyway a good starting point.

Up to date I've tested it only on Mac OS X 10.6, compiled using g++ 4.2.1. Since it doesn't rely on any particular library or OS-related function, in principle it should compile smoothly on any platform.

Before compiling, all the parameters you can play around with are stored inside the defaultParameters.h file.

Any kind of feedback or advice about possible improvements would be greatly appreciated.
 

Attachments

k_c

Well-Known Member
#18
fabietto said:
Dear all,

let me briefly introduce myself. My name is Fabio, I'm 27 years old, PhD student in Artificial Intelligence and I recently started to study a few books about blackjack.

What I'm trying to do now is to write a simple blackjack software simulator, in order to test the effects coming from using different counting systems, different way of calculating the true count, etc.

The first "beta" version of my simulator is ready. But I'm getting an unexpected result. It looks like that just applying the basic strategy, without any card counting (betting always the same amount), after 10,000 runs of 1,000 hands each (averaged together) the software is able to score a (very small) profit. According to the literature I didn't expect this result at all. I spent a couple of days debugging my code, but I can't really find any issues. Therefore what I'd like to ask you is if, under the following conditions, is it normal for the player to have a positive edge.

These conditions are:
- 6 decks used;
- dealer stands on S17;
- penetration rate (I guess this is not that important if counting is not used): 75%;
- unlimited splits;
- split aces are considered like every other split hand (you can double, split again or hit as many cards you want);
- double allowed on any two starting cards and after any split;
- no surrender;
- no insurance;
- natural pays 3-to-2 (before paying, the dealer checks for blackjack if he has a T-valued or an A card exposed).

Thanks in advance to anybody who would like to spend few minutes helping me... :)
It could possibly be in your shuffle.

I recently wrote a simulator in C++ with the following shuffle algorithm:

Code:
void shuffle(Card * const deck, const short &totCards)
{
	for (short i = 0; i < totCards; i++)
	{
		int j = rand() % totCards;
		Card temp = deck[i];
		deck[i] = deck[j];
		deck[j] = temp;
	}
}
totCards is number of cards, so for 6 decks totCards=312.

rand() in C++ generates a random number between 0 and RAND_MAX. For the compiler I use RAND_MAX=32767. Therefore there are 32768 possible random numbers.

The problem with the above algorithm is that 32768 is never evenly divisible by totCards. When modulus division is done and applied to the initial order the probability that the first cards in the shuffled shoe will be the inital cards in the original order is increased. Using 6 decks as an example, if the cards are initially ordered so that cards 1-24 are aces, cards 25-48 are 2s, etc. that means the chance of an ace being dealt on the early rounds is higher than it should be. Random numbers (0 to 32759) % 312 are enough to cover each rank equally but random numbers (32760 to 32767) % 312 only cover cards 0 to 7 so there is a bias toward these cards appearing on top in the shuffle. In the initial shuffle cards 0 to 7 are all aces.

If shuffling is continued after initial shuffle using the resultant order as the new starting point then I think the bias becomes negligible but if the shoe it reordered several times the bias will be more apparent.

In my code I plan to use
int j = int(double(rand())/(RAND_MAX+1)*totCards+1)
to get a random number between 1 and totCards using the C++ rand() function. I think this will eliminate the bias.

I'm not sure if this applies to your problem but it's something to consider.
 

London Colin

Well-Known Member
#19
A can of worms

There can be issues both with random number generation, and with the shuffle algorithm. I found a couple of helpful articles on Wikipedia when I was looking into this a while ago ...
http://en.wikipedia.org/wiki/Shuffling#Randomization
http://en.wikipedia.org/wiki/Fisher–Yates_shuffle


k_c said:
It could possibly be in your shuffle.

I recently wrote a simulator in C++ with the following shuffle algorithm:

Code:
void shuffle(Card * const deck, const short &totCards)
{
	for (short i = 0; i < totCards; i++)
	{
		int j = rand() % totCards;
		Card temp = deck[i];
		deck[i] = deck[j];
		deck[j] = temp;
	}
}
totCards is number of cards, so for 6 decks totCards=312.
I think there is a problem with that algorithm. The destination of the swap has to be restricted to the locations not yet passed through, but including the current location. (See the Wikipedia references.)


k_c said:
In my code I plan to use
int j = int(double(rand())/(RAND_MAX+1)*totCards+1)
to get a random number between 1 and totCards using the C++ rand() function. I think this will eliminate the bias.
Will that eliminate the bias, though? I've seen code like that recommended because it uses the high bits of the integer returned by rand(), whereas a modulo operation uses the low bits. (And some implementations of rand() are apparently not very random in the low bits.)

Regarding bias though, I convinced myself that it must still be there, since we are still mapping from each of 32768 equally likely inputs to 'totCards' outputs, meaning some outputs must be more likely than others. I decided that the only solution was to discard the unwanted return values from rand() and call it again. (But I confess my thinking was getting a little fuzzy on this point.)

So this is what I came up with ..
Code:
// Return a random int in the range 0 .. n-1
inline int randint(int n)
{
    // To avoid 'modulo bias', limit the usable random ints to a multiple of n.
    int rmax_plus1 = RAND_MAX - RAND_MAX%n ;
    int r;
    do
    {
        r = rand();
    } while (r>=rmax_plus1);

    // So we now have r in one of the ranges 0..n-1, 0..2n-1, 0..3n-1, etc.


    // The answer could now be computed as r % n.
    // However, some implementations of rand() return results in which
    // the low-order bits are much less random than the high-order bits.
    // Using the % operator would propagate this poor randomness into the result
    // of this function.

    // So instead multiply n by a float which is >=0 and <1.
    // The integer result is then in the range 0..n-1
    return int( n * (float(r)/float(rmax_plus1)) );
}
Incidentally, the C++ standard library has a random_shuffle() function which can be used. By default it handles the random number generation internally, but you can supply your own, such as the one above.

E.g., I call it like this ...

Code:
    random_shuffle(cards,cards+52*NumDecks(),randint);
Or alternatively, my own implementation of the shuffle (or rather Eric Farmer's, since his code was the starting point) ...
Code:
    for (int card = 52*NumDecks(); card > 1; card--)
    {
        swap(card-1, randint(card));
    }

    void swap(int card1, int card2) 
    {
        CardType temp = cards[card1];
        cards[card1] = cards[card2];
        cards[card2] = temp;
    }
There's probably something available in the Boost libraries - http://www.boost.org - that provides both a better PRNG and all the associated algorithmic stuff to go with it. That might be worth looking into.
 

k_c

Well-Known Member
#20
London Colin said:
I think there is a problem with that algorithm. The destination of the swap has to be restricted to the locations not yet passed through, but including the current location. (See the Wikipedia references.)
I think the algorithm is OK. The assumption, of course, is that random numbers are generated by rand() in C++.

The other problem is that even if it is guaranteed that the numbers are random, if the number of possible random numbers is not a multiple of 52 then a bias will exist. For example if we wanted to generate a random number in the range 0 to 51 and we are given a guaranteed random number from the range 0 to 77 instead of 0 to 32767:

Define r() = random number 0 to 77

num = r() % 52 is always a number 0-51

Here the bias is more obvious though. Since r() is in the range 0-77, num is twice as likely to be 0-25 when compared to 26-51. The problem would be fixed if we could somehow redefine r() to be a guaranteed random number in the range 0-51, but that would solve the problem without having to do anything else. :laugh:
 
Top