I read this thread with interest as a developer myself (not online gaming software!).
A point which I think binary128 maybe tried to make but didn't make really clear is that programming a fair game of video poker should be trivial to a good developer. I am thinking it would be less than 50 lines of code, and take no more than a few hours to program and write a test for it. Same for just about every card game at a casino, plus roulette, craps etc. With video poker the operator has the advantage over the player due to the paytable. If they want to make the game more profitable they can just change the paytable to suit.
Programming a deterministic game that has a pre-set RTP would be far more complicated and difficult to balance over time. Someone mentioned throwing away 3 of a kind to see if you are re-dealt that as a way of telling. But what about if the player does something hugely unexpected that makes the preset hand impossible to complete. Say the game has decided that it will deal a royal flush, so it deals 3 or 4 to a royal plus some low unsuited trash and expects the player to hold the suited royal cards. But instead the player discards the royal cards and keeps the others. What does it do then? It can't possibly deal a royal now. Maybe it spits out a four of a kind or straight flush if possible? Then later it has to rebalance that because it is now one short of the expected royal count. I know these types of machines do exist but to me it seems like a dumb way of developing a game that would just cost way more money in development and testing time. It would be better, quicker and cheaper to make a random game and just change the paytable to make the game more profitable. I can only assume that these sort of games are developed to "smooth" the payouts to players so that the operator does not have to pay out several big winners in a short space of time.
A question for binary128, jstrike and any other online gaming developer reading. How do you seed your random number generator? Of course I don't expect detailed specifics as this is the exploitable part of any online gambling game. I mean at a high level. Do you use hardware based generation, or is it all done in software? And have you ever considered publishing the source code to your shuffling and/or dealing algorithm (not the RNG) in the interests of transparency?
You're right on about it being a lot harder to write a "deterministic" game than a random one. And the idea of the machine having to make up the difference for hands where it couldn't make the expected payout was something that went through my mind too, along with how you'd synchronize that across lots of machines without going into a race condition.
Highly technical: You're not quite right about VP taking only 50 lines of code...I mean, just parsing a poker hand takes about 60 lines (at least my version does). Actually dealing out hands randomly and then replacing the discards is trivial, the hard part is running all the input validation, account balancing and database i/o you need if you're taking input from a web facing application, recording a result, verifying that the player hasn't done anything with any other machine in the casino at the same time that would put them into a negative balance, checking whether the player's connected, making graceful disconnects, keeping it stateless in case someone does lose connection, and doing that with dozens of players at the same time on the same server while avoiding deadlock. If you just ran a single-threaded socket server in JVM it wouldn't be a problem, I guess, it's just that we allow players to sit at 6 tables at the same time, and some of those can be multiplayer blackjack tables too, and all of it reads/writes to the same db. So... doing things the hard way, our most basic Jacks or Better game is about 250 lines of backend code mostly dealing with transactions, connection state checks, action validation, parsing the paytable, gracefully handling rollbacks. And that doesn't include the bet acceptance code or the shuffling code or the poker hand parser.
Our RNG is seeded like this:
1. Take an md5 hash of the microtime, i.e. a 32-character hexadecimal number.
2. Use the current (old) seed to select a number between 0 and 24.
3. Use that number to substring a 32-bit (8 character) chunk the md5 hash.
4. Convert that substring to an unsigned integer, which is the new seed.
Basically in PHP it boils down to this:
PHP:
$s = (hexdec(substr(md5((double)microtime()*1000003),mt_rand(0,24),8)) & 0x7fffffff) * round(mt_rand(0,1)-.5);
So the seed itself is a function of the microtime, the last seed, and where the RNG was in the last period when the reseeding is triggered. The reseeding gets triggered when any of the distributed satellite servers asks for a new tranche of random numbers to refill its buffer. Each one keeps a running pool of anywhere from 10,000 to 100,000 integers in volatile memory. When that pool dips below length N, the satellite server requests a new tranche from the RNG, which reseeds and spits out a set of numbers based on the size of the satellite server's request. The first 32 bits of the new tranche are used by the satellite server to determine what level it's going to request its next refill at and how many numbers it'll request next time, so neither the size of the requests nor the refill size are constant, and both are determined by the central RNG's numbers themselves. After that, the rest of the numbers are put onto the bottom of the pool. During this process the games on the satellite are continuously pulling 32-bit numbers off the top of the pool. This is how we keep a continuous flow of random numbers to the player even though the RNG may be far away.
Finally, when we shuffle, we pull a 32-bit number at the top of the pool to create a local mersenne twister sequence, which is the basis for a Fisher-Yates shuffle where the initial array is a new (sorted) deck or 6-deck shoe. The local twister sequence is determined by what's at the top of the pool and is predictable if you know what's in the pool, it's just a way to scale the numbers...we could just as easily use any other evenly scaling algorithm to get a number between 1 and 52 from the pool and it would still be as random as what the pool itself was holding from the RNG, but an MT sequence happens to be an efficient way of doing it. We have no way to view the pool while it's running, it only exists as an array in RAM, but we log these numbers as they come off of it. So there's no actual randomness being generated by the satellite server. Each one's a slave to the central RNG, the numbers used for the decks are predestined from the moment it fills the pool, and all it's doing is showing the results. (Although who gets a lucky hand is obviously up to the order people are playing in, what seats they're in, and more of a philosophical question). Here's the FY shuffle we run:
PHP:
function _fy_shuffle(&$array) {
$total = count($array);
if (RNG_POOL) {
//pulls a 32-bit unsigned integer off the top of the pool.
$seed = $this->_fetchSeedFromPool();
}
if (!$seed) {
//if connection to the central RNG is lost and the pool is below the fill line,
//we use the game server to generate a local random seed based on the same algorithm that fills the pool.
$seed = $this->_getFailoverSeed();
}
mt_srand($seed);
for ($i = 0; $i<$total; $i++) {
$j = @mt_rand(0, $i);
$temp = $array[$i];
$array[$i] = $array[$j];
$array[$j] = $temp;
}
}
That gets done 3 times, cutting the deck once between each shuffle at a point chosen by the last number in the MT sequence. The deck or shoe is then inserted into the database and used as the basis for the next dealt hand (or kept running for multiple hands of blackjack).
The output has proved to be extremely random by measuring the results... for decks and shoes, we evaluate that two ways (1) how many decks end up with suited cards in the first 2, 3, 4, 5 or 6 cards at the top of the deck, versus the probability of that happening, and (2) how many times each card ended up as the first card in a deck. For example, out of 29,010 shuffles since we opened, there have been 57 single decks with exactly 5 suited cards at the top of them. The probability of that happening on a given deck is (12/51)*(11/50)*(10/49)*(9/48) = 0.1981%, which means over 29,010 shuffles it should happen about 57.46 times. We're right on target.
So there you have it... a complete explanation of how our RNG is seeded and how the decks are shuffled. I know I'm probably not supposed to put this "out there", but I don't see a point in keeping it a secret. As far as I know, it's unbreakable and the results are fair. If someone sees flaws in it, let me know. I'm always looking to improve the system.