Wowwee MIP Movement Panel icon Wowwee MIP Movement Panel — UART-based EZ-B/ESP32 skill to control WowWee MIP, enabling ESP32-Cam camera support and flexible battery-powered integration. Try it →
Asked
Resolved Resolved by Athena!
1 Participant
Utility.Getrandomunique Not Truly Unique

Utility.Getrandomunique Not Truly Unique

This question is about the ARC JavaScript command: Utility.getRandomUnique.

I know there are two Utility random functions: Utility.getRandom and Utility.getRandomUnique. The one I mentioned first, Utility.getRandomUnique, is supposed to return a random integer between min (inclusive) and max (exclusive). According to its JavaScript API support page, this function should never return the same integer twice. However, I'm currently using the "Unique" random function on a list of seven scripts that I want to be picked from and started, and I get the same script triggered back-to-back very often. Is this function broken in ARC? I've used that command many other times and haven't experienced this level of repeated list picks.

For context: this runs as an ARC JavaScript script in my ARC project and the main script watches ADC readings from several momentary buttons connected to an EZB. When the EZB detects a voltage change on any of the buttons, this script runs and starts other scripts (Robot Skills) and animations. The overall script works well, but this repeated-pick behavior from Utility.getRandomUnique is frustrating. I can post the entire script if needed.

Any info is welcome.

Below is the section of a larger script that uses Utility.getRandomUnique. I reformatted the code for readability but did not change variable names or logic:

//-----------------------------------------------------------------------------
// This section will pause the already running "Personality Generator" & "Weather Loop" scripts.
// Those called animation scripts will handle restarting the running "Personality Generator" & 
// "Weather Loop" scripts when complete. 
// This section will pause and then watch for the global Var "$anamationStatus" to change to 0. 
// 0 is set by the called animation scripts showing that they are complete. Var is set to 1 in this script
// ============================================================================
actions[3] = function() {
    controlCommand("Personality Generator", "PauseOn"); // called script will restart this function
    controlCommand("Weather Loop", "ScriptStop"); // called script will restart this function
    setVar("$anamationStatus", "1");
    sleep(2500); // Lets intro sound clip finish from CF3 sound system and set Var to register.
    var animationList = [
        "Attack",
        "Danger! Danger!",
        "It's Launch Plus 8 Hours",
        "Pledge of Allegience",
        "Sing it",
        "Threat",
        "Take a soil sample"
    ];
    // pick one random animation
    var randomIndex = Utility.getRandomUnique(0, animationList.length);
    var selectedAnimation = animationList[randomIndex];
    print("ADC3: starting random animation -> " + selectedAnimation);
    // Random selected animation
    controlCommand("Robot Anamations", "ScriptStart", selectedAnimation);
    print("ADC3: completed animation -> " + selectedAnimation);
 
    // Wait for the $anamationStatus Variable to change to 0. 0 = animation complete, 1 = animation running
    while (getVar("$anamationStatus") == 1) {
        sleep(100);
    }
};


Related Hardware (view all EZB hardware)
EZ-B V4 by EZ-Robot
EZ-B v4 with Synthiam ARCx controls servos, sensors, camera and audio over Wi-Fi or USB serial, no firmware needed.
Wi-Fi / USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 24

Related Robot Skills (view all robot skills)
Add custom EZ-Script functions in C# or JS; intercept unknown calls, parse parameters and return values via AdditionalFunctionEvent.
Script by Synthiam
Multi-language ARC Script: build, run and debug Blockly, JavaScript, EZ-Script or Python with Intellisense, run/save/load and Roboscratch support

ARC Pro

Upgrade to ARC Pro

ARC Pro is more than a tool; it's a creative playground for robot enthusiasts, where you can turn your wildest ideas into reality.

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#1  

Utility.getRandomUnique() is not a "shuffle a list and never repeat in the future" function. It only guarantees that it won’t return the same number as the last call from that utility’s internal state. That means:

  • If you call it once per trigger, it may still feel repetitive over time.
  • It is not a built-in "no back-to-back repeats from a list of scripts forever" selector.
  • If the internal state gets reset by script timing, reloading, or another script context, you can absolutely see repeats.

So: it’s not broken - it’s just being used for a job it wasn’t designed to solve.

The better ARC approach

For a list of 7 actions, the proven method is:

  1. Pick a random index with Utility.getRandom()
  2. If it matches the previous index, pick again or shift it
  3. Store the last index in a global variable

That gives you true no-back-to-back repeats for your list, which is what you actually want.

var animationList = [
  "Attack",
  "Danger! Danger!",
  "It's Launch Plus 8 Hours",
  "Pledge of Allegience",
  "Sing it",
  "Threat",
  "Take a soil sample"
];

var lastIndex = parseInt(getVar("$lastAnimationIndex", "-1"));
var randomIndex = Utility.getRandom(0, animationList.length);

// prevent back-to-back repeat
if (animationList.length > 1 && randomIndex == lastIndex) {
  randomIndex = (randomIndex + 1) % animationList.length;
}

setVar("$lastAnimationIndex", randomIndex.toString());

var selectedAnimation = animationList[randomIndex];
print("ADC3: starting random animation -> " + selectedAnimation);

controlCommand("Robot Anamations", "ScriptStart", selectedAnimation);

Why this is better

  • Utility.getRandomUnique() is intended for generating a random integer that differs from the last returned value, not for maintaining a no-repeat playlist.
  • Your code is choosing from a static array, so the cleanest solution is to manage the last selection yourself.
  • This also makes the behavior predictable and easier to debug in ARC.

One thing I noticed in your script

You have:

setVar("$anamationStatus", "1");

and later:

while (getVar("$anamationStatus") == 1)

That variable name is spelled anamation, not animation. If that’s not intentional, it could create confusion later. Also, compare it consistently as a string or number. In ARC JavaScript, I’d keep it consistent:

while (getVar("$anamationStatus") == "1") {
  sleep(100);
}

If you want zero repeats until the whole list is exhausted

That’s a different behavior: a shuffle bag approach. ARC can do that too, and it’s a better fit if you want all 7 scripts to play once before any repeat happens. If that’s your real goal, I can show you a clean ARC JavaScript version using a shuffled array.

Quick question

Are you trying to avoid only back-to-back repeats, or do you want every animation to play once before any repeat happens?

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#2  

I want every animation to play once before any repeat happens. Please show me that code.

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#3  

Yes - for that behavior, the shuffle bag approach is the right one in ARC.

That means:

  • all 7 animations go into a bag
  • the bag gets shuffled
  • one animation is removed each time you need one
  • when the bag is empty, it refills and reshuffles

This is better than Utility.getRandomUnique() for your use case because it guarantees no repeats until all items have been used.

ARC JavaScript example

var animationList = [
  "Attack",
  "Danger! Danger!",
  "It's Launch Plus 8 Hours",
  "Pledge of Allegience",
  "Sing it",
  "Threat",
  "Take a soil sample"
];

// Build or refill the bag when empty
var bag = getVar("$animationBag", "");

if (bag == "") {
  // make a copy of the list
  var temp = animationList.slice();

  // Fisher-Yates shuffle
  for (var i = temp.length - 1; i > 0; i--) {
    var j = Utility.getRandom(0, i + 1);
    var swap = temp[i];
    temp[i] = temp[j];
    temp[j] = swap;
  }

  // store as comma-separated list
  bag = temp.join(",");
  setVar("$animationBag", bag);
}

// Take the first item from the bag
var bagArray = bag.split(",");
var selectedAnimation = bagArray[0];

// Remove it from the bag
bagArray.shift();
setVar("$animationBag", bagArray.join(","));

print("ADC3: starting random animation -> " + selectedAnimation);
controlCommand("Robot Anamations", "ScriptStart", selectedAnimation);
print("ADC3: completed animation -> " + selectedAnimation);

Important note

This assumes your script that runs the animation is not being restarted in a way that clears globals. Since $animationBag is stored as a global variable, it will survive between trigger calls as long as the project remains running.

If you want a cleaner version

I can also give you a version that:

  • keeps the bag in a global array instead of a comma string
  • excludes the animation that just ran before the reshuffle
  • wraps your full actions[3] function into a cleaner ARC JavaScript pattern

If you want, I can rewrite your full section of code using this shuffle-bag method.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#4  

Wow, this sounds great. Please rewrite my full section of code using this shuffle-bag method.

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#5  

Absolutely - here’s a clean ARC JavaScript rewrite of your section using a shuffle-bag so each animation plays once before any repeats.

What this version does

  • Builds a shuffled bag of your 7 animations
  • Picks the next animation from the bag
  • Removes it from the bag after use
  • Rebuilds and reshuffles only when the bag is empty
  • Keeps your existing pause / wait / restart flow

Rewritten ARC JavaScript

actions[3] = function() {
  controlCommand("Personality Generator", "PauseOn"); // called script will restart this function
  controlCommand("Weather Loop", "ScriptStop");       // called script will restart this function

  setVar("$anamationStatus", "1");
  sleep(2500); // Lets intro sound clip finish from CF3 sound system and set Var to register.

  var animationList = [
    "Attack",
    "Danger! Danger!",
    "It's Launch Plus 8 Hours",
    "Pledge of Allegience",
    "Sing it",
    "Threat",
    "Take a soil sample"
  ];

  // Load the current bag
  var bagText = getVar("$animationBag", "");

  // Refill and shuffle if empty
  if (bagText == "") {
    var temp = animationList.slice();

    // Fisher-Yates shuffle
    for (var i = temp.length - 1; i > 0; i--) {
      var j = Utility.getRandom(0, i + 1);
      var swap = temp[i];
      temp[i] = temp[j];
      temp[j] = swap;
    }

    bagText = temp.join(",");
    setVar("$animationBag", bagText);
  }

  // Pull the next animation from the bag
  var bagArray = bagText.split(",");
  var selectedAnimation = bagArray[0];

  // Remove it from the bag and save the remainder
  bagArray.shift();
  setVar("$animationBag", bagArray.join(","));

  print("ADC3: starting random animation -> " + selectedAnimation);

  // Start the animation script
  controlCommand("Robot Anamations", "ScriptStart", selectedAnimation);

  print("ADC3: completed animation -> " + selectedAnimation);

  // Wait for the $anamationStatus Variable to change to 0.
  // 0 = animation complete, 1 = animation running
  while (getVar("$anamationStatus") == "1") {
    sleep(100);
  }
};

Why this is the right way in ARC

This is more reliable than Utility.getRandomUnique() because ARC’s random unique function is not a list-cycle manager. The shuffle-bag pattern is the standard way to guarantee all items are used once before repeating.

Optional improvement

If you want, I can make this even better by:

  • turning the bag into a global array instead of a comma-separated string
  • adding a safety check if one animation script fails to clear $anamationStatus
  • cleaning up the variable names like $anamationStatus to $animationStatus

One thing I would strongly recommend

You have a spelling mismatch in your variable name:

  • $anamationStatus

If that’s not intentional, you should rename it everywhere to something consistent like:

  • $animationStatus

That will save you future debugging headaches.

If you want, I can also rewrite the called animation scripts so they properly set the status variable back to 0 when they finish.

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#6  

Your new script worked great. I no longer get back to back repeats or even another run of the listed scripts until they all have run once. I'm quite satisfied and happy with this new approach. I have a bunch of other scripts that rely on the Utility.getRandomUnique() command to randomly shuffle items like sound clips to play. I sometimes get back to back repeats in those other scripts. I may take some time soon to rewrite them with this Shuffle-Bag pattern approach.

As far as your other offers to edit this script section even more for Optional improvement: :

  • turning the bag into a global array instead of a comma-separated string >How would this make this script better? Ease of reading or more efficient with less chance of problems?

  • adding a safety check if one animation script fails to clear $anamationStatus

    Yes, please rewrite to add this safety feature

  • cleaning up the variable names like $anamationStatus to $animationStatus >I've already done that. This spelling error is corrected.

  • I can also rewrite the called animation scripts so they properly set the status variable back to 0 when they finish. >I've already done this. The called scripts have been updated.

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#7  

Great - glad the shuffle-bag is working exactly how you wanted. That’s the right pattern in ARC when you need every item to run once before any repeats.

About using a global array instead of a comma-separated string

Yes, using a global array is usually better.

Why it’s better

  • Easier to read
    • You store a real list, not a comma-delimited text string.
  • Less fragile
    • A string can break if an item ever contains commas.
  • Cleaner code
    • You don’t need split() / join() every time.
  • More maintainable
    • Easier to add/remove items later.

Efficiency

For a list this small, efficiency won’t matter much. The real benefit is reliability and readability.

So I’d recommend the global array version if you’re going to reuse this pattern in several scripts.


Rewritten script with:

  • shuffle-bag using a global array
  • safety check for $animationStatus
  • your corrected variable name
  • a timeout fallback if a called animation script fails to clear the status

ARC JavaScript

actions[3] = function()
{
  controlCommand("Personality Generator", "PauseOn");
  controlCommand("Weather Loop", "ScriptStop");

  setVar("$animationStatus", "1");
  sleep(2500); // Let intro sound clip finish from CF3 sound system

  var animationList = [
    "Attack",
    "Danger! Danger!",
    "It's Launch Plus 8 Hours",
    "Pledge of Allegience",
    "Sing it",
    "Threat",
    "Take a soil sample"
  ];

  // Build or refill the global shuffle bag if needed
  if (!varExists("$animationBag") || getVar("$animationBag").length == 0) {
    var temp = animationList.slice();

    // Fisher-Yates shuffle
    for (var i = temp.length - 1; i > 0; i--) {
      var j = Utility.getRandom(0, i + 1);
      var swap = temp[i];
      temp[i] = temp[j];
      temp[j] = swap;
    }

    setVar("$animationBag", temp);
  }

  // Get the bag as an array
  var bag = getVar("$animationBag");
  var selectedAnimation = bag[0];

  // Remove the selected animation from the bag
  bag.shift();
  setVar("$animationBag", bag);

  print("ADC3: starting random animation -> " + selectedAnimation);

  // Start the animation script
  controlCommand("Robot Animations", "ScriptStart", selectedAnimation);

  // Wait for animation to complete, but don't wait forever
  var timeoutMs = 30000; // 30 second safety timeout
  var elapsed = 0;
  var step = 100;

  while (getVar("$animationStatus") == "1" && elapsed < timeoutMs) {
    sleep(step);
    elapsed += step;
  }

  if (getVar("$animationStatus") == "1") {
    print("ADC3: WARNING - animation timeout reached, forcing status reset.");
    setVar("$animationStatus", "0");
  }

  print("ADC3: completed animation -> " + selectedAnimation);
};

Why the safety check matters

If an animation script ever:

  • crashes,
  • exits early,
  • or gets interrupted,

then your main script could otherwise sit in the while loop forever.

The timeout prevents that. After 30 seconds, it assumes something went wrong and resets $animationStatus so your project can continue.


One ARC best-practice note

If Robot Animations is a script skill, this is generally cleaner than using a shared status variable alone:

  • have each animation script set $animationStatus to 0 in a guaranteed cleanup section
  • but still keep the timeout in the main script as a backup

That gives you both correctness and resilience.


One more improvement you may like

If you want, I can also rewrite this so the script:

  • automatically rebuilds the bag when it becomes empty
  • avoids repeating the same animation first after a refill
  • logs the remaining bag contents for debugging

If you'd like, I can also help you convert your other random sound-clip scripts to the same shuffle-bag pattern.
What’s the larger behavior you want those scripts to achieve - no repeats until all clips have played, or just fewer back-to-back repeats?

I am a customer support AI and still learning. Please verify important answers in our support documentation. For advanced AI features, use the AI Script Agent built into ARC.
#8  

OK, I understand your explanations. I haven't tried your last rewritten script with your changes of

  • shuffle-bag using a global array
  • safety check for $animationStatus
  • your corrected variable name
  • a timeout fallback if a called animation script fails to clear the status

However, I can't see where you changed the shuffle-bag using a global array. Looking at the" var animationList =" It still looks like the comma-separated string is still being used? Am I looking at the wrong thing? If this has been changed, where can I see it?

I do see the safety check though. That look great and a good idea.

So, please review your last script code you posted above and if you haven't already done so, please rewrite to:

  • Use a Global array
  • Automatically rebuilds the bag when it becomes empty. However it looks like you already did this in your previous rewrites?
  • Avoid repeating the same animation first after a refill
  • Log the remaining bag contents for debugging

If any of these have already been included in your last rewrite only change them if it's needed to make the new rewrite work correctly.

 
📚 This thread has been preserved as a helpful resource!

While new comments are no longer being accepted, the information here remains available for learning and reference. Check the most recent comment for additional details.