?

Log in

No account? Create an account

Previous Entry | Next Entry

"Random" Notes

I carpool with a friend in a nearby building to the climbing gym. To decide who drives, we play email roshambo. The HTML code for the Rock/Paper/Scissors choice is presented below:

<SELECT name="p_throw">
<OPTION value="r">Rock</OPTION>
<OPTION value="p">Paper</OPTION>
<OPTION value="s">Scissors</OPTION>
</SELECT>

We've done this for years, and we each end up driving about 50% of the time, with a few short streaks breaking up the routine. I decided it was too much effort to have to actually choose rock, paper or scissors if I didn't want to. It'd be nice for the website to randomly suggest one for me.

So I added the following PHP code:

$suggest = rand(0, 2);
<SELECT name="p_throw">
<OPTION <? if ($suggest == 0) echo "SELECTED "; ?>value="r">Rock</OPTION>
<OPTION <? if ($suggest == 1) echo "SELECTED "; ?>value="p">Paper</OPTION>
<OPTION <? if ($suggest == 2) echo "SELECTED "; ?>value="s">Scissors</OPTION>
</SELECT>

There, now the web page will suggest a random throw when it loads. How convenient!

Except that my friend started destroying me in our challenges. From December 2009 through March 2010, he began winning over 75% of our challenges, and I had to keep driving the carpool. This was costing me money!

It turns out that while I was using the suggested random throw, he had a different strategy. He considered his suggested throw, and made the throw that would beat it.

So there was a correlation between the random throw suggested for me, and the one suggested for him! Even though our suggestions are generated from different pages (mine from index.php because I start the challenge, and his from throw.php because he responds to a challenge), the random number generator runs on the same server.

It's just not random enough, and it exposed an exploit that was costing me money!

There's a simple fix for this, use mt_rand() instead of rand(). So I made the following change:

$suggest = mt_rand(0, 2);
Although I made the change to mt_rand(), the fact that the pseudo-random numbers were being made by the same physical generator bothered me. I decided that instead of generating the suggested throw on the server, it'd be best to generate the suggestion at the client computer, in Javascript. So I wrote the following code and deployed that:

function Set_suggested_throw()
{
var randomnumber = Math.floor( Math.random() * 3 )
document.rpsform.p_throw.selectedIndex=randomnumber
}
</head>
<body onLoad="Set_suggested_throw()">
That's better. Now his random suggestion is generated on an entirely different machine than mine...

Except it bothered me that both random numbers were seeded in a similar fashion, and there'd often be a constant offset between the localtime and uptimes of both machines. This wouldn't do. I needed better randomness. Luckly, there's a site for that.

Great! I'll have my Javascript make a quick call to random.org to make the suggestion.

function Set_suggested_throw()
{
var xmlhttp = null;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
if ( typeof xmlhttp.overrideMimeType != 'undefined') {
xmlhttp.overrideMimeType('text/xml');
}
} else if (window.ActiveXObject) {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// Only if quota not exceeded. See http://www.random.org/clients/
xmlhttp.open( 'GET',
'/rnd_org/integers/?num=1&min=0&max=2&col=1&base=10&format=plain&rnd=new',
true );
xmlhttp.send( null );
xmlhttp.onreadystatechange = function() {
if ( this.readyState == 4 && this.status == 200 ) {
document.contactform.p_throw.selectedIndex=this.responseText
}
}

}
Making a call to random.org from a page generated at rps.dlma.com runs afoul of the Same Origin Policy. My server is a Linux server, so all I have to do is add a ProxyPass to my httpd.conf and restart it.

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

ProxyPass /rnd_org http://www.random.org

A downside is that the proxy content can be cached, so I'd have to be sure to disable that.

Even worse is that my server is a shared server running at DreamHost, and I don't have access to httpd.conf. And one can't specify a ProxyPass in a .htaccess file either.

So the work-around is to have the PHP code make the call to random.org. Great! Just code that sucker up, ensure that we don't spam random.org by checking our quotas, and falling back on the Javascript implementation if we do go over quota. I won't show you all that, just the PHP snippet.

$ctx = stream_context_create( array( 'http' => array( 'timeout' => 5 ) ) );
$url = "http://www.random.org/integers/?num=1&min=0&max=2&col=1&base=10&format=plain";
$result=file_get_contents( $url, 0, $ctx );
if ( !is_bool( $result ) || $result != false ) {
// set $suggest }
Great! The next time we played Email Roshambo, I won! Phew! As he drove me to the gym, I asked him how he'd been since I last saw him.

He said, "Oh, I've had better Fridays. I was RIFfed. We won't be carpooling anymore."

Comments

( 2 comments — Leave a comment )
davidd
Apr. 4th, 2010 09:55 am (UTC)
That's really sad about your buddy... but it makes for a hilarious stinger punchline!

I find the difficulty you encountered trying to ensure true randomness interesting. In fact, a case might be made that your little example demonstrates that, in order to create an apparently random occurrence, "intelligent design" is required. There you go, pushing that arch-conservative religious agenda again. You didn't think we'd notice your sly propaganda, did you?

Oh, and... happy Easter!



Edited at 2010-04-04 09:56 am (UTC)
dblume
Apr. 4th, 2010 11:47 pm (UTC)
Happy Easter to you, too!

But "intelligent design?" All my adventure did was reveal some real-world pitfalls with pseudo-random number generation and potential internet resource caching.

Pseudo-random number generation is "good enough" for a lot of applications. I'd thought that my email RPS game would certainly have fallen into that category, so I was surprised when it didn't.
( 2 comments — Leave a comment )