Build a Random Prize Competition Service: Part 1

I want to preface this article with two notes. The first is that I came up with this idea while I was poking around with random number generators and the differences between multiple ways available to us as JavaScript developers. Thus, I'm not advocating this functionality for your site: it's more that I thought that this could be a fun way of using what I learned. It's going to be quite long and a bit rambly, so feel free not to humor me.

The second is something I can't emphasize enough: if you are going to implement this functionality, know this is more of a proof of concept. As this functionality involves elements of chance and prize giving, you should ensure compliance with any laws or rules governing such things before offering this functionality to your customers. In other words, if you want to do something like this on your site then I would seek out advice from knowledgeable people on the subject.

Anyway, over the past few months I've been toying with an idea. I had seen a company that ran occasional prize draws where a user would pull down on a virtual handle, spin a virtual wheel, and then it would award whatever prize the wheel stopped on. While I'm not sure it has practical use for any of our customers, I've been thinking about whether such a thing could be replicated on an SCA site.

I think there are a lot of aspects that could make this a fun project, but one that may take a while to get right. Thus, what I'm thinking about doing is something quite simple in the backend (hopefully) but the interface might be a bit complicated.

Let's say you're a visitor, this is how it works:

  1. A site is running a competition
  2. You visit the page and you're presented with a wheel with different values in the segments – each one offers a different prize (eg free delivery, 10% off, free item, etc)
  3. You drag the wheel and it spins for a while
  4. When the wheel stops, whatever prize is being pointed to by the arrow is what you win

Although, as developers, we know that it's not this easy. For example, if we let the frontend client dictate what the prize is then we open ourselves up to issues where a clever but deceitful person could manipulate the result to get the one they want; we will, therefore, have to run the prize calculation on the backend.

Furthermore, we want to keep things fair to other shoppers so we may want to introduce restrictions around who is eligible to participate. We will need to have good random number generation so that rewards are distributed fairly and in a way that we expect. We should also think about eligibility: who we allow to participate and on what basis. It is not uncommon for businesses to reward loyal customers, so we may want to limit participation only to those customers. For example, customers might have to meet one or more of the following criteria:

  1. Registered for at least six months
  2. Orders totalling more than $100
  3. Ordered something within the last six months

We can look at how we might implement these rules. As for the requirements for the functionality might work, I'm thinking something like this:

  1. CSS/HTML wheel spinner interface
  2. Once spun, a call is made to the backend
  3. The user is checked to see if they've played before (if yes, error is returned "You've already played!")
  4. If no, a number between, say, 1 and 10,000 is created
  5. This number is then checked against a pre-determined list of prizes
  6. Update the user's profile with what they drew and the prize they got (eg 7123 - 10% off)
  7. Depending on the prize, a handler is called to return the information on this prize (eg, if it's a promotion then add the shopper to the customer group that enables them access to this promotion)
  8. This information is then returned to the frontend
  9. The spinner is sent a value to land to on and the animation slows to a rate that ensures it stops on that value
  10. The customer is given the information about their prize
  11. That information is retrievable from their account at any time

I think the significant thing here is that the frontend spinner is a ruse — where it will land is pre-determined by the random number generated by the backend. Initial iterations of this functionality should omit the spinner itself, but we can use much simpler functionality in the meantime.

In fact, a lot of the functionality that we introduce to the module at the start will provide excellent examples for any future modules you want to produce. Let's get started shall we?

Prepare the Module Structure

There's two clear parts to this implementation: the spinner/RNG functionality, and the actual instance of this functionality. In other words, we're separating the spinner from the competition — invoking the former when the latter is created. To start with, we will put everything into one module and then look at separating it later.

In your customizations directory, create a folder called RNG@1.0.0 with folders for JavaScript, Sass, SuiteScript and Templates. Then create an ns.package.json file with the following in it:

{
  "gulp": {
    "javascript": [
      "JavaScript/*"
    ]
  , "sass": [
      "Sass/*"
    ]
  , "ssp-libraries": [
      "SuiteScript/*.js"
    ]
  , "templates": [
      "Templates/*"
    ]
  }
}

Let's create the entry point file. Go to JavaScript and create RNG.js with the following:

define('RNG'
, [

  ]
, function
  (

  )
{
  'use strict'

  console.log('RNG.js loaded');
});

Next, we think about what we're going to be doing with this functionality. If we want complex user details then we would need to add this functionality to the account application; however, if we're just going to check whether the user is logged in or not, then we can reliably use the shopping application.

Open up your distro.json file and add the module to the list. Then add the module to the dependencies lists for the shopping application's JavaScript and CSS.

Save and deploy. We can now get started.

Build a Router, View and Template

Before we can do anything we need to do some 'hello world' type stuff and get a route set up that we can visit. Once we have that we need to load a view that then shows a template. This is pretty basic stuff so let's get through it straight away.

In JavaScript, create RNG.Router.js:

define('RNG.Router'
, [
    'Backbone'
  , 'RNG.View'
  ]
, function
  (
    Backbone
  , View
  )
{
  'use strict';

  return Backbone.Router.extend ({
    routes:
    {
      'competition': 'showComp'
    }

  , initialize: function (application)
    {
      this.application = application;
    }

  , showComp: function ()
    {
      var view = new View
      ({
        application: this.application
      });

      view.showContent();
    }
  })
});

Nice and simple. Then create RNG.View.js:

define('RNG.View'
, [
    'Backbone'

  , 'rng.tpl'
  ]
, function
  (
    Backbone

  , rng_tpl
  )
{
  'use strict';

  return Backbone.View.extend({
    template: rng_tpl
  })
});

Load then show the template. Speaking of which, create rng.tpl:

<p>RNG template loaded!</p>

That's all you need so far. Save the files and run gulp local — you should should be able to visit the local version of this page (#competition) and see your page load!

Basic Backend

At the moment, what we have is purely frontend. Let's create a basic backend that plugs into our module and sends data from the backend to the frontend. We're going to add some new files and modify some existing ones.

We know that one of the things we want to do is check if the user is logged in, so let's do that part as part of our skeleton.

Backend Model and Service Controller

As we know, the backend model and service controller handle CRUD operations for NetSuite-hosted data. For now, we're going to add a simple get request.

In SuiteScript, create RNG.Model.js:

define('RNG.Model'
, [
    'Models.Init'
  , 'SC.Model'
  ]
, function
  (
    CommerceAPI
  , SCModel
  )
{
  'use strict'

  return SCModel.extend({

    name: 'RNG'

  , get: function ()
    {
      var prizeData = {};

      prizeData.isLoggedIn = CommerceAPI.session.isLoggedIn2();

      return prizeData
    }
  });
});

So when a get is performed, we query the commerce API and pull out data about the user's current session, specifically their login status.

From here, we need to tell the app how to fire this method — for that we need the service controller.

In SuiteScript create RNG.ServiceController.js and in it put:

define('RNG.ServiceController'
, [
    'ServiceController'
  , 'Application'
  , 'RNG.Model'
  ]
, function
  (
    ServiceController
  , Application
  , Model
  )
{
  'use strict';

  return ServiceController.extend({
    name: 'RNG.ServiceController'

  , get: function ()
    {
      return Model.get();
    }
  });
});

This is very simple: when a get is called... do a get on the model.

Now to enable this functionality in the application, we need to first update ns.package.json so it contains this object:

, "autogenerated-services": {
    "RNG.Service.ss": "RNG.ServiceController"
  }

This tells the application that we want it to generate RNG.Service.ss, rather than us doing it manually.

Next we need to update distro.json. Pop down to the ssp-libraries object and then add "RNG.ServiceController" to the dependencies object.

Now we need to look at connecting this to the frontend of our module.

The Frontend Model, Router, View and Template

First we start by connecting the backend model and frontend model.

In JavaScript, create RNG.Model.js:

define('RNG.Model'
, [
    'Backbone'
  , 'Utils'
  ]
, function
  (
    Backbone
  , Utils
  )
{
  'use strict';

  return Backbone.Model.extend({
    urlRoot: Utils.getAbsoluteUrl('services/RNG.Service.ss')
  });
});

Next we need to update the router so that it's aware of the model. We also need the router to fetch the data from the model, and then run showContent() when it's loaded.

In RNG.Router.js, add the model as a dependency and then modify the showComp function so that it is this:

, showComp: function ()
  {
    var model = new Model()
    , self = this;

    model.fetch().done(function ()
    {
      var view = new View
      ({
        application: self.application
      , model: model
      });

      view.showContent();
    });
  }

This is a standard (non-collection) model.

Now we think about surfacing the data from our get request onto the frontend. For that, we need to add it to the context object in the view.

In RNG.View.js, add the following:

, getContext: function ()
  {
    return {
      isLoggedIn: this.model.get('isLoggedIn')
    }
  }

Just some simple surfacing stuff. Easy.

Finally, change the contents of rng.tpl to:

<p>Login status: {{isLoggedIn}}</p>

Now, we changed some backend files and the distro file, so we need to push this up. When that's done, visit your page and you should see this:

Of course, if you are logged in then it might return true. Try it out: log in and then refresh the page.

Exploring Random Number Generators

So we know that the most basic part of this is a random number generator. The concept behind is that when the user generates a number, it is then matched to various ranges of numbers to determine what prize it correlates to.

For the purposes of my tutorial, I'm going to suggest that there are three groups of prizes with the following chances:

  1. $50 off your order (5%)
  2. $25 off your order (10%)
  3. $10 off your order (85%)

Thus, when we generate random numbers, we need to align them with number ranges representing these chances. For now, let's look at how we might generate random numbers.

The most obvious pivot for this functionality is the in-built JavaScript number generator included in all browsers: Math.random(). This method will generate a random number. However, if you look up the history of this function you'll see that it's plagued by people telling you how it's not cryptographically secure. While we're not going to be using it for cryptography, we should still be wary as we're dealing with prizes.

There is, however, a different issue that we need to think about: how random are its random numbers? Well, it turns out that across different JavaScript engines, the results are varied. This is bad: what we want from a random number generator is one that has good probability distribution; in other words, if I flip a coin there's a 50% chance of it landing on other side — if I do it enough times, I expect this to be the pattern. Turns this isn't the case with Math.random(). For a long but interesting write-up on why, read what the CTO of Betable, Mike Malone, has to say.

In short: if you want something to, say, choose a random color for your background or have a bit of fun, Math.random() is fine. For everything else, we need something more sophisticated.

Cryptographic Random Numbers

There is another native way to generate random numbers though: the web cryptography API.

This API, which is not fully supported by browsers, contains a very useful method that allows us to generate cryptographically secure random numbers. To do this, we rely on two things: something called a typed array and the .getRandomValues() method to fill it with values.

If you're unfamiliar with typed arrays, they are not a new concept but they are new to JavaScript. Typed arrays enable JavaScript to be much better at handling raw binary data, which is super useful for things like networking and fancy HTML canvas elements. It's also really good for things we, as ecommerce developers, won't get to use like WebGL (graphics rendering).

I don't want to get too technical about this stuff (search for it if you like) but it essentially means that we can create fixed-length arrays of 8, 16 and 32 bits. Thus we can say, "I have an 8-bit array — select random bits for each of these" therefore creating an integer between 0 and 255.

Shall we test this out? Yes! Open your developer console on any page on your site (we're going to use Underscore) and type in:

var array = new Uint8Array(5)

Typing in array will return the array: note how it's an array of five 0s — each one represents one 8-bit byte.

Now, if we want to generate actual random numbers, we can do:

window.crypto.getRandomValues(array);

This returns the same array but this time each one of the values has been replaced with a randomly generated integer between 0 and 255. This is neat.

If you're curious as to how 'random' these actually were, and if I could find a way to 'test' it. So I wrote some code; first I transferred the above code into a function:

var randomNumber = function(gen)
{
  var array = new Uint8Array(gen);
  return window.crypto.getRandomValues(array);
}

If you want to try this out, you can do randomNumber(1)[0] to return one random number.

Now, create another function:

var checkRandomness = function (gen)
{
  var test = _.flatten(randomNumber(gen)).sort(function(a, b){return a-b})
  , counts = {}
  , avgs = gen/255;
  
  test.forEach(function(x) {counts[x] = (counts[x] || 0)+1; });
  console.log('Expect about ' + avgs + ' per number')
  return counts;
}

So what we're doing is passing the function the number of numbers generate (gen) and then running our first function that many times. It'll take the results, flatten them out to a normal array (using Underscore) and then sort them in order. Then we'll do a simple counting exercise, creating an object out of this array where it goes through and counts and each time it spots a number. Finally, it returns this object with a quick note how often we should expect each number to come up. Now, let's test it.

Do: checkRandomness(65000) to generate 65000 (!) random numbers and count them. You should get something like this:

Expand the object and take a look. It says there should be about 255 of each, give or take. Of course, it won't be perfectly distributed — that's the nature of randomness. Also, this isn't really a big enough set of values to truly test randomness, but you get the idea.

So with this, we can start to look at how we might turn this into a workable solution. Thus, you can now add a third and final function to our repertoire:

var getPrize = function()
{
  var draw = randomNumber(1)[0]
  , prize = ''

  if (draw<=(255*0.05))
  {
    prize = '$50 off your order!';
  }
  else if (draw>(255*0.05) && draw<=(255*0.15))
  {
    prize = '$25 off your order!';
  }
  else if (draw>(255*0.15))
  {
    prize = '$10 off your order!';
  }

  return {'Draw': draw, 'Prize': prize}
}

Now, run getPrize() a few times and see your results. Most of the time you should get $10 prizes, followed by a smattering of $25 ones and then, very rarely, some $50 ones.

Can We Implement This?

It seems like we've found what we should be using, so why not try to implement it?

If you copy and paste our two functions into RNG.Model.js and log a random number then your return statement should look something like this:

return SCModel.extend({

  name: 'RNG'

, genRandom: function ()
  {
    var array = new Uint8Array(1);
    return window.crypto.getRandomValues(array);
  }

, getPrize: function()
  {
    var draw = this.genRandom(1)[0]
    , prize = ''

    if (draw<(255*0.05))
    {
      prize = '$50 off your order!';
    }
    else if (draw>=(255*0.05) && draw<(255*0.15))
    {
      prize = '$25 off your order!';
    }
    else if (draw>=(255*0.15))
    {
      prize = '$10 off your order!';
    }

    return {'Draw': draw, 'Prize': prize}
  }
, get: function ()
  {
    var prizeData = {};

    prizeData.isLoggedIn = CommerceAPI.session.isLoggedIn2();
    console.log(this.genRandom());
    return prizeData
  }
});

If you save and deploy this and then hit the competition page again, you'll see that it errors. Huh? Check the execution log for the SSP application and you'll see a clue why.

But we know that Uint8Array() doesn't need to be defined — it's a native part of JavaScript, right? Well, if you look at when typed arrays where added to the ECMAScript specifications, we see that it appears in ECMAScript 2015, a relatively modern specification (June 2015) that's implemented in all good, modern browsers. However, not, seemingly, in the JavaScript engine that runs in NetSuite. Bummer.

Oh dear. One for the trash heap. Right, before we move on, tidy up by undoing all the changes we just made to RNG.Model.js. Save and re-deploy.

Third-Party RNG with Mersenne-Twister

OK, so at this point we know that Math.random() is a sub-optimal random number generator, and that our use of the native crypto API is so fresh that the NetSuite backend hasn't caught up to it yet. What can we do?

I looked at the possibility of shimming a polyfill for this functionality but it seemed a) complicated and b) to miss the point. After all, we're using the typed arrays and crypto API because we thought they were readily available tools that did the job, not because they were the only tools for the job.

I returned to the Malone article and re-read it. It's there I realized how significant this quote was, when he talked about the pseudo-random number generator in the V8 JavaScript engine:

"I would have gone with mersenne twister since it is what everyone else uses (python, ruby, etc)." This short critique, left by Dean McNamee, is the only substantive feedback on the code review of V8's PRNG when it was first committed on June 15, 2009. Dean's recommendation is the same one I'll eventually get around to making in this post.

Indeed, in his concluding remarks he reiterates how widely regarded the Mersenne Twister PRNG algorithm is. Indeed, if you look at the Wikipedia page, you'll see that it is a widely used random number generator. While it is not cryptographically secure, it's worth iterating that this isn't necessarily important. What's important is that we get good random numbers.

Testing Mersenne-Twister

It did not take me long to find a JavaScript version of this algorithm. For this, I'm going to use this one: https://gist.github.com/banksean/300494. Adding this to our project is straightforward.

Save a copy of it to your JavaScript folder. Then, in your distro.json file, update the amdConfig object for the shopping JavaScript with:

"MersenneTwister": "mersenne-twister"

This makes the application aware of the file. Now we just need to add it as a dependency to a file; for testing purposes, let's add MersenneTwister as a dependency to RNG.View.js.

Save and deploy.

If you head back to the competition page and refresh, you can now call random numbers from the generator using the console:

// An array of random integers
new MersenneTwister();

// A single random floating point number
var m = new MersenneTwister();
console.log(m.random());

Great stuff. In fact, why don't we go further? Type in:

var a = new MersenneTwister().mt
  , b = [];

a.forEach(function (x)
{
  b.push(x/4294967295)
})

b.sort(function(a, b){return a-b})

And you can see your numbers.

Just one point worth highlighting: why do we divide the number by 4294967295? Put simply, this number is the biggest integer that can be expressed in 32 bits, which, incidentally, is also the largest number that could be generated by the algorithm. Thus, we are dividing the randomly generated number by this one to get a value between 0 and 1. This isn't necessary but will make working with it easier.

Let's take it further and create a pseudo-solution to analyse the results:

var a = new MersenneTwister().mt
  , b = []
  , c = {'$50':0, '$25':0, '$10':0};

a.forEach(function (x)
{
  var d = x/4294967295;
  b.push(d)
  if (d<=0.05) {c.$50++}
  else if (d>0.05 && d<=0.15) {c.$25++}
  else (c.$10++)
})

console.log('$50: ' + c.$50 + ' (' + c.$50/b.length*100 + '%)');
console.log('$25: ' + c.$25 + ' (' + c.$25/b.length*100 + '%)');
console.log('$10: ' + c.$10 + ' (' + c.$10/b.length*100 + '%)');

Run it numerous times to see the results. It should be largely consistent with the percentages we expect. Great! So, can we finally get it to work on our site?

Implementing Mersenne-Twister by Making It AMD-Compatible

We've dealt with a lot of third-party JavaScript files in our time and implementing them usually involves a bit of shimming and a bit of tinkering. Today I'm going to suggest something along these lines; put simply, let's convert it to AMD.

At the moment, we have a file that RequireJS calls an "older, traditional 'browser globals' script" and the traditional way of integrating it into our application would be to shim it.

I know from my own personal experience and the experience of those who have contacted me, that getting third-party JS files integrated into your system can be headache. To wit, I propose, that you simply start treating these files like no other and modify them so that they conform to the same standards all other modules live by.

Start by creating a MersenneTwister directory in your customizations directory. Take the algorithm file from RNG > JavaScript directory and put it into the new one; rename it to MersenneTwister.js.

Open the file and then replace the code (but leave the copyright notices at the top) with:

define('MersenneTwister', [], function ()
{

  'use strict';

  var N = 624
    , M = 397
    , MATRIX_A = 0x9908b0df   /* constant vector a */
    , UPPER_MASK = 0x80000000 /* most significant w-r bits */
    , LOWER_MASK = 0x7fffffff /* least significant r bits */

    , mt = new Array(N) /* the array for the state vector */
    , mti=N+1; /* mti==N+1 means mt[N] is not initialized */

  return {
   /* initializes mt[N] with a seed */
   init_genrand: function(s) {
      mt[0] = s >>> 0;
      for (mti=1; mti>> 30);
        mt[mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
        + mti;
        /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
        /* In the previous versions, MSBs of the seed affect   */
        /* only MSBs of the array mt[].                        */
        /* 2002/01/09 modified by Makoto Matsumoto             */
        mt[mti] >>>= 0;
        /* for >32 bit machines */
      }
    }

    /* initialize by an array with array-length */
    /* init_key is the array for initializing keys */
    /* key_length is its length */
    /* slight change for C++, 2004/2/26 */
  , init_by_array: function(init_key, key_length) {
      var i, j, k;
      this.init_genrand(19650218);
      i=1; j=0;
      k = (N>key_length ? N : key_length);
      for (; k; k--) {
        var s = mt[i-1] ^ (mt[i-1] >>> 30)
        mt[i] = (mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
          + init_key[j] + j; /* non linear */
        mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
        i++; j++;
        if (i>=N) { mt[0] = mt[N-1]; i=1; }
        if (j>=key_length) j=0;
      }
      for (k=N-1; k; k--) {
        var s = mt[i-1] ^ (mt[i-1] >>> 30);
        mt[i] = (mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
          - i; /* non linear */
        mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
        i++;
        if (i>=N) { mt[0] = mt[N-1]; i=1; }
      }

      mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
    }

    /* generates a random number on [0,0xffffffff]-interval */
  , genrand_int32: function() {
      var y;
      var mag01 = new Array(0x0, MATRIX_A);
      /* mag01[x] = x * MATRIX_A  for x=0,1 */

      if (mti >= N) { /* generate N words at one time */
        var kk;

        if (mti == N+1)   /* if init_genrand() has not been called, */
          this.init_genrand(5489); /* a default initial seed is used */

        for (kk=0;kk>> 1) ^ mag01[y & 0x1];
        }
        for (;kk>> 1) ^ mag01[y & 0x1];
        }
        y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
        mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];

        mti = 0;
      }

      y = mt[mti++];

      /* Tempering */
      y ^= (y >>> 11);
      y ^= (y << 7) & 0x9d2c5680;
      y ^= (y << 15) & 0xefc60000;
      y ^= (y >>> 18);

      return y >>> 0;
    }

    /* generates a random number on [0,0x7fffffff]-interval */
  , genrand_int31: function() {
      return (this.genrand_int32()>>>1);
    }

    /* generates a random number on [0,1]-real-interval */
  , genrand_real1: function() {
      return this.genrand_int32()*(1.0/4294967295.0);
      /* divided by 2^32-1 */
    }

    /* generates a random number on [0,1)-real-interval */
  , random: function() {
      return this.genrand_int32()*(1.0/4294967296.0);
      /* divided by 2^32 */
    }

    /* generates a random number on (0,1)-real-interval */
  , genrand_real3: function() {
      return (this.genrand_int32() + 0.5)*(1.0/4294967296.0);
      /* divided by 2^32 */
    }

    /* generates a random number on [0,1) with 53-bit resolution*/
  , genrand_res53: function() {
      var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6;
      return(a*67108864.0+b)*(1.0/9007199254740992.0);
    }

  /* These real versions are due to Isaku Wada, 2002/01/09 added */

  , randomValues: function () {
      return mt
    }
  }
});

There's a lot to take in here so I'll summarize the changes I made:

  1. We added a standard define statement to create the namespace
  2. We removed the constructor and moved the crucial variables outside of its function, making them their own variables
  3. We changed these variables' names, eg this.N is now N, here and throughout the code
  4. We added a standard return statement
  5. We changed all of the prototyping functions to methods
  6. We added a method that returns the array of numbers we generated, which will be useful for testing

Save the file. It's now AMD-compatible.

Create an ns.package.json with the following:

{
  "gulp": {
    "javascript": [
      "MersenneTwister.js"
    ]
  , "ssp-libraries": [
      "MersenneTwister.js"
    ]
  }
, "jshint": "false"
}

This will include the file in the JavaScript for both the frontend and the backend. Technically, we won't need the file in the frontend, so we should remove this later when we're done with testing it.

Then, in distro.json, remove the shim in amdConfig and then add the module like you would normally: as an entry in the modules object and as a dependency of the shopping JavaScript file.

I know we've been modifying the distro file a lot so, just to reiterate, at this point you should have the following references:

  • RNG: as a module, as a dependency of shopping.js, its service controller as a dependency of ssp-libraries, and as a dependency of shopping.css
  • MersenneTwister: as a module, and as a dependency of shopping.js

Now for some testing. In SuiteScript > RNG.Model.js, add MersenneTwister as a dependency and then replace the return statement with the following:

return SCModel.extend({
  name: 'RNG'

, genRandom: function ()
  {
    var seed = new Date().getTime();
    MersenneTwister.init_genrand(seed);

    var random = MersenneTwister.random();
    console.log(random);
    return random;
  }

, get: function ()
  {
    var prizeData = {};

    prizeData.isLoggedIn = CommerceAPI.session.isLoggedIn2();
    prizeData.prize = this.genRandom();
    return prizeData
  }
});

So the new genRandom function starts by creating a seed (more on this later), which it then uses to prepare the array of values. After that, we make our call for a random integer, which we dutifully send to our execution log. Finally, we added a little bit into the get call so that the call to the whole thing is made.

Save. Deploy. Refresh.

Now if you check the execution log, you should see your random number appear! Hurray!

Randomness, Seeding and Entropy

So you'll notice one thing that I snuck in to the genRandom function: we created a seed using the time.

Random numbers need seeds because they define the initial starting point for random number generators. If you use the same seed every time you start a RNG process, then you'll end up with the same results. The seed, by the way, is always stored as the first value in the array after its initialized. Thus, if you ever need to examine some results, you can use the randomValues(); method and see what's been returned.

But what this also means is that we also need good seeds. You might be thinking, "But wait a minute, haven't we just moved the problem a little bit further along? Don't we now we need random seeds?", to which the answer is no. We are explicitly not doing cryptography here, so as long as we have a good algorithm and we don't use the seed value as the actual random number, the numbers generated will have good probability distribution.

Now this stuff doesn't particularly matter to us because we don't let users enter their own seed before generating a random number. As we only use the time there is a minor threat of manipulation if a user could work out the exact point in milliseconds would create the most desirable result, but there are so many real life variables in the way that make this infeasible (eg latency, CPU cycle time, etc). Again, if we were doing cryptography this would be unacceptable, but we're not.

If you are worried, you can add in additional entropy to the seed that transforms your number some more. Just pick stuff that's readily available and changes or is different from user to user. And then keep that information secret so that no one can use it to figure out how to crack into it. Remember, the seed just needs to be constantly changing: re-using one will end up in the same value being generated which, over time, may result in poor probability distribution.

Closing Remarks

Woah. Wait a minute there, Steve, I hear you say. We're not done yet. Yes, well, for now we are. Delving into random number generation took up far more time than I thought it would because it was so interesting and it was something I wanted to get right. As such, I've not had a chance to finalize the latter parts of this project. That must come in a part two.

So for now, let's look back: we created a skeleton backend module which did the basics of sending data back to the frontend. We then moved onto RNG, which is where we were like Alice and got lost in a weird world.

We learnt that Math.random() is really only good for trivial things. We learnt that there's a fancy new crypto API but that the NetSuite servers, at the time of writing, don't support it. Then, finally, we settled on a third-party implementation of Mersenne-Twister algorithm. We tested it out, we liked it, we implemented it on our backend.

We're leaving things at a point where a random number can be generated and returned, which means that we should now move on and focus on what it is exactly we're going to be doing.

If you struggled to follow along or want to compare code, you can download my source here: RNGpart1.zip.

For part two, see Build a Random Prize Competition Service: Part 2.