Ensuring a Random Number is not Re-picked

Jan 31, 2018

This one might be for the javascript experts in the community, but open to all.

I think someone (and it might have been Owen Holt) posted how you can use javascript to create a random variable and in that same coding ensure any of the x numbers is not re-picked.  I may be imagining this though.  (If this does exist can someone point us to the discussion thread)

With the Storyline 360 and the random variable option, does someone have a method they have come up with to ensure the same random number does not get picked more than once?

27 Replies
Tracy Parish

Update:  this might be the discussion thread using Javascript to remove those that have already been selected.

https://community.articulate.com/discussions/building-better-courses/trivia-game-using-variables-and-javascript-to-remove-answered-questions-from-the-deck


Still wondering about using the built in random feature though.  (I can think of way to do it if I'm do a small set of random numbers, but what about between  something like 1 and 1500000)

 

OWEN HOLT

You've got the right thread for my JavaScript solution. It does have a heavy dependence on variables so the bigger the number range, the more variables required. 

Stephanie Harnett just published a thread where she used the new random number variable but also controlled it such that previously selected numbers were not used. Her thread only included the published file so I am not sure how she was controlling the valid selections.  Link to Stephanie's thread

OWEN HOLT

I streamlined my JS for you with a slightly more elegant solution that only uses 3 variables.

WHAT YOU NEED:

  1. A variable to store a single text array formatted like this: 1,2,3,4,5,6,7,8,9,10,11.... through your top number. Separate values with a comma only; NOT a comma + space. In my example, the variable is named Text_Array and goes from 1 to 100.
  2. A variable to receive your Random Number. This can be either a text or numeric variable depending on what you want to do with it. In my example, it is a text variable named Random.  My example doesn't do anything other than display the random number chosen, but obviously you would want to evaluate the value and then do something based on it like go to layer, play media, jump to slide, etc.
  3. A variable to track how many items are left in your list. You will use this to track when you have used all of the numbers to do something different.
  4. Something to trigger your JavaScript. I'm using a button.  Here is the JavaScript code commented out so you can see what it is doing:


    //get the StoryLine player
    var player=GetPlayer();

    //get Storyline variable value as a string
    var textArray=player.GetVar("Text_Array");

    //Convert string to a numeric array
    numArray=textArray.split(",").map(Number);

    //Get a random number from the array and send it to StoryLine
    var randNum = numArray[Math.floor(Math.random() * numArray.length)];
    player.SetVar("Random",randNum);

    //Remove the random number from your array and get the array's length
    numArray.splice(numArray.indexOf(randNum), 1);
    var itemsLeft=numArray.length;

    //Convert array to a string and send it back to SL along with the array's length
    textArray=numArray.map(String).toString();
    player.SetVar("Items_Left", itemsLeft);
    player.SetVar("Text_Array", textArray);

See it in action here

Example is SL360
Attached file is SL2

OWEN HOLT

Alternative SL360 file that adds some code to make setting the range easier. 
On slide one, designate the high end of your range. JavaScript will create the array, convert it to a string, and send it to your variable so you don't have to type in 1,2,3,4,..... as the default value for the variable.  Note, the input is optional as you could hard code the upper limit if it is known and not going to be variable.  

Here is the additional JS on slide one:

// get the SL player
var player=GetPlayer();

//Optional: only use this if you are not hard coding the upper range limit.
//The value is supplied by a numeric entry box tied to a SL variable called HighRange
var highLimit=player.GetVar("HighRange");

//Create and populate the array
//If you are hard coding the upper limit, replace "highLimit" with the numeric value of your upper limit.
var textArray = [];
    for (var i = 1; i <= highLimit; i++) {
    textArray.push(i);
    };

//Get the length of the array
var itemsLeft = textArray.length;

//Convert the array to a string and send the variable values to SL
textArray=textArray.map(String).toString();
player.SetVar("Text_Array", textArray);
player.SetVar("Items_Left", itemsLeft);

See it working here.
SL2 version provided below.

Bridget Brown

Owen - I have a knowledge check dice game with only 6 numbers.  Currently I am using the Random number variable, but of course the learner roll 1-5, answer questions 1-5 and have to roll 10 more times to get question #6.

If I use your java - do I take out the random number variable?   I am also not clear how, when the array picks a number I plug that number into a text variable (%diceroll%!) so that I can trigger a jump to a slide/question.

Thanks.

OWEN HOLT

Correct, you would not need to use StoryLine's random number if you are using JavaScript.

Once you generate a random number using JS, you send it back to SL using the setvariable code.
In this example -> player.SetVar("Text_Array", textArray);

player.SetVar is the command.
"TextArray" is the name of the StoryLine variable being used.
textArray is the JS variable that has the value you are sending back to StoryLine.

Scott Wiley

I just stumbled on this thread and am enjoying the sharing of JS coding ideas. Continuing in the spirit of sharing, I have a couple of prototypes I did a while back that touches on multiple posts here.

They were both done in Storyline 2.

  • One deals with a dice rolling mechanism.
    • A slider is used to select the number of dice to roll.
    • Each of the 5 possible dice variables are randomized to one of 6 numbers.
    • The number of each die is added together to come up with the total.
  • The other randomized card deck mechanism.
    • It deals with shuffling a large array (deck of cards) into random order, splitting individual variables into a new array (hand of cards), and the results are used to show what card was dealt to the hand.

I haven't had much luck posting JS code in the past, so for now at least I'll include the files which have lightbox slides including the basic code used in each case. The rest is based on triggers to set either the die or card to a unique state based on the random variables.

Hope this helps as an idea starter and/or further idea sharing.

Kevin Hayes

Owen and Scott - here is a challenge for you!

I have variable for student names (newName) and I am using some neat code (not mine) to grab a bunch of scores from my (SCORM 1.2) file to record results in a Google Sheet

Now I want to record the student name in the file too - but I want to encrypt the name so others can't determine whose score is whose. 

The encryption doesn't need to to be very strong - a Ceasar Cipher or something of that level would be sufficient.

So I need a simple script that takes a text value (newName = "Kevin Hayes" for instance), encrypts it (so it's stored as newNamecode in the Sheet = "ahjdh hjkkh"); but in such a way that I can then decypher it back to plain text as I know the key so I can know whose score is whose.

OWEN HOLT

You could just use 2 arrays, one with the possible characters and one with the target character. Next, convert your name into an array, find matching values in the first array, and replace them with the corresponding value from the 2nd array.  Something like the following:

var str = "Hello WOrld!"; //Replace with your StoryLine name variable.
str = str.toUpperCase();
var n = str.length;
var base = ['Q','W','E','R','T','Y','U','I','O','P','A','S','D','F','G','H','J','K','L','Z','X','C','V','B','N','M','1','2','3','4','5','6','7','8','9','0','!','*','-',' ',];
var target = ['B','N','M','1','2','3','4','5','6','7','8','9','0','!','*','-','Q','W','E','R','T','Y','U','I','O','P','A','S','D','F','G','H','J','K','L','Z','X','C','V',' ',];
str = str.split("");
for (var i = 0; i < n; i++) {
for (var ii = 0; ii<40; ii++) {
if(str[i] == base[ii]){
str[i] = target[ii];
}
}
};

str = str.join('');
console.log(str); //Send this back to StoryLine or to your score board

If you run this in your browser console, it should return 'VPEEH OHAEZX'

If you replace Hello WOrld! with Owen Holt and run it again, you will get HOPO VHES

Scott Wiley
found here

Hi Kevin,

If you don't need anything so custom, and could get by with a type of "masking" approach, I Googled a simple JavaScript encryption method (found here) that converts your string into Base64 and back.

For my test In Storyline, I created 3 text variables (baseString, encodedString, and decodedString).

On the stage, I created a text entry field (the default TextEntry 1 variable changed to the baseString variable).

Then added 2 buttons (one to encrypt and one to decrypt) with JavaScript triggers that will convert the entered text and display in two additional text fields displaying the other 2 variables (encodedString, and decodedString).

On the encrypt button, I added this JavaScript to the trigger:

var player = GetPlayer();
//The baseString variable could be populated by retrieving the learner name from the LMS, but just used text entry here.
var baseString = player.GetVar("baseString");
//Convert to base64
var encodedString = window.btoa( baseString );
//Display in text field, but this could be sent to your Google sheet.
player.SetVar("encodedString", encodedString);

On the decrypt button, it goes the other way:

var player = GetPlayer();
//Grab the encoded text
var encodedString = player.GetVar("encodedString");
//Convert back to normal text
var decodedString = window.atob( encodedString );
//Display in the text field
player.SetVar("decodedString", decodedString);

The test file is attached.

Hope that helps.

 

OWEN HOLT

Looking at my code and it isn't quite right.  I'll need to troubleshoot it a bit longer to make sure it works correctly consistently.
Scott's solution is a good one that I also considered, however my only concern would be that the btoa() method is not supported in IE9 and earlier. If that isn't a concern for your target audience, it is a nice simple and clean solution.

Kevin Hayes

Yeah I do like the idea of the Base64 and actually is one I tried myself but couldn't get it to work - so will definitely try Scott's solution.  What's neat about using Base64 is that there is (from memory) an exsisting formula within Google Sheets that easily reverts strings "back" from Base64 - so that would be cool.  If I wanted another layer of encryption I guess Base64, then a randomiser a'la Owen, and that would be pretty hard to break but fairly easy to reverse/unencrypt. 

Scott & Owen - thanks!

Kevin Hayes

Google Sheet

Scott and Owen  - thanks so much for all your help.  In the end I went for Scott's Base64 Solution although really liked Owen's idea (if you get the code working would love to take a look!)

Combining Scott's solution with the Google Sheet data export (see: https://community.articulate.com/discussions/articulate-storyline/articulate-storyline-export-to-google-drive ) means I can now automatically track a very sophisticated assessment using a SCORM 1.2 LMS (for Pass/Fail & Overall Score) and (simultaneously) store additional assessment data in a Google Sheet (see attached image)

The bonus is that there is a script available within Google Sheets that enables me to reverse the Base64 to Ascii (see:https://www.reddit.com/r/googlesheets/comments/4y2ebb/help_decoding_column_of_base64_data/) - so all in all a neat solution that enables me to "hide" user names (albeit not enough to stop an uber tracker but sufficient for my purposes) and I can easily unencrypt the Base64 by just copying the data into a sheet that has been setup with the Base64 reverse script.

Once again -  thanks!