I’m struggling to get truly random values in my ActionScript project, and my current approach keeps generating repeat patterns that break my game logic. I’ve tried using Math.random() in different ways, but the results still don’t feel random enough over time. Can someone explain the best practices for implementing reliable randomness in ActionScript for things like enemy spawns or loot drops, and maybe point out common mistakes that could cause predictable results?
Math.random() in ActionScript is a PRNG, so it will repeat patterns, especially if you hit it in a tight loop or frame-based logic without extra work.
A few things you can try to make it behave better for game logic:
- Seed it with some entropy
If you are on AS3 and using a custom PRNG, seed with something like time and input.
Example simple LCG:
public class RNG {
private var seed:uint;
public function RNG(s:uint = 0) {
if (s == 0) s = uint(new Date().time) ^ uint(Math.random() * 0xFFFFFFFF);
seed = s;
}
public function next():Number {
// LCG params from Numerical Recipes
seed = (1664525 * seed + 1013904223);
return (seed >>> 0) / 4294967296;
}
public function nextInt(max:int):int {
return int(next() * max);
}
}
Use one RNG instance for the whole game. Do not create new ones every frame.
-
Avoid calling random in predictable bursts
If you do something like:for (var i:int = 0; i < 100; i++) { … Math.random() … }
every frame, you will see patterns in gameplay. Spread decisions over time or tie them to game events instead of every frame.
-
Use a shuffle for random sequences
If you need random non-repeating values, pre-generate and shuffle an array.function shuffle(arr:Array):void {
for (var i:int = arr.length - 1; i > 0; i–) {
var j:int = int(Math.random() * (i + 1));
var tmp:* = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
Example for random spawn order of 0..9 without repeats:
var order:Array = [];
for (var k:int = 0; k < 10; k++) order.push(k);
shuffle(order);
Then pop from order instead of calling Math.random each time.
-
Check your usage, not only the RNG
A common bug is logic like:if (Math.random() < 0.5) { doA(); }
else { doB(); }
inside a state where A or B changes future calls, which looks “non random” in play. Log values to trace and inspect them:
trace('rand:', r);
You will often see the numbers are fine, but your conditions or ranges are biased.
-
Avoid integer bias
If you pick random ints, use something like:var idx:int = int(Math.random() * array.length);
Do not do things like Math.round(Math.random() * n) since values at the ends get half the probability.
If you post a small code snippet where the repetition happens, people here can point out if the issue is Math.random or the logic around it.
The short version: your problem probably isn’t that Math.random() is “not random enough,” it’s that the way it’s wired into your game logic is amplifying patterns.
@jeff already covered custom PRNGs, shuffling, and seeding nicely, so I’ll hit other angles and disagree on a couple of details.
1. Decide what “random” actually means for your game
Players rarely want true randomness; they want fairness or variety.
Examples:
-
Enemy spawns:
You probably don’t want 5 identical enemies in a row, even if that’s statistically fine.
Solution: after using a random choice, bias away from recently used values.// simple 'no immediate repeat' wrapper private var lastType:int = -1; function getEnemyType():int { var t:int; do { t = int(Math.random() * 3); // 0..2 } while (t == lastType); lastType = t; return t; } -
Loot drops:
Player shouldn’t go 30 kills without a rare drop. That’s not really random, that’s “pity timer.”
So before fighting Math.random(), define rules like:
- “No back-to-back repeats”
- “At most N of the same result in a window of M calls”
- “Guarantee at least one success after K failures”
Then implement controlled randomness instead of pure RNG.
2. Use stateful helpers instead of raw Math.random()
Instead of sprinkling Math.random() everywhere, wrap common patterns:
// Returns a random boolean with given probability
function chance(p:Number):Boolean {
return Math.random() < p;
}
// Random index from an array, with optional blacklist
function randomIndex(len:int, avoid:int = -1):int {
var idx:int;
do {
idx = int(Math.random() * len);
} while (idx == avoid && len > 1);
return idx;
}
Now the “rules” live in these helpers and you can tweak bias centrally instead of hunting through the whole codebase.
3. Avoid frame-synchronized random decisions
I disagree slightly with the idea that the tight loop alone “causes” the repetition you’re seeing. A decent PRNG will be fine in a tight loop. The perception of repetition usually comes from calling random:
- at the exact same logical point every frame
- on identical game state
- and mapping it to a tiny set of outcomes (like 2 or 3)
Try:
-
Randomizing timing as well as choices:
// schedule next spawn at a random time offset nextSpawnTime = currentTime + 0.8 + Math.random() * 0.4; // 0.8..1.2 sec -
Having events trigger on in‑game events (kills, pickups, waves) instead of “every frame maybe spawn something”.
Even with the same RNG, just moving calls around in time will change the perceived pattern a lot.
4. Use history-based smoothing
If your “pattern” is too many similar values in a row, track history and act on it:
private var lastValue:int = -1;
private var streak:int = 0;
function nextClampedRandom(max:int):int {
var v:int = int(Math.random() * max);
if (v == lastValue) {
streak++;
if (streak > 2 && max > 1) {
// force different value after 3 in a row
v = (v + 1 + int(Math.random() * (max - 1))) % max;
}
} else {
streak = 0;
}
lastValue = v;
return v;
}
Not mathematically pure, but players don’t care about purity, they care that stuff “feels” random.
5. Check your distribution mapping
You mentioned trying Math.random() “different ways”. Some common pitfalls besides what @jeff already called out:
Bad patterns:
// skewed toward middle
var idx:int = Math.round(Math.random() * (n - 1));
// missing last index occasionally due to float/int bugs
var idx:int = int(Math.random() * (n - 0.0001));
Stick to:
var idx:int = int(Math.random() * n); // 0..n-1
If you want weighted randomness (ex: type0 twice as likely as type1):
function weightedPick(weights:Array):int {
var total:Number = 0;
for each (var w:Number in weights) total += w;
var r:Number = Math.random() * total;
var acc:Number = 0;
for (var i:int = 0; i < weights.length; i++) {
acc += weights[i];
if (r < acc) return i;
}
return weights.length - 1;
}
A lot of “repeating patterns” are actually invisible biases like “one option is much more likely than the others.”
6. Debug the randomness separately from the game
Before blaming Math.random():
-
Log a sequence to the console:
for (var i:int = 0; i < 50; i++) { trace(int(Math.random() * 10)); } -
Paste into a text editor and eyeball: do you really see a simple pattern, or just a couple of repeats?
-
Temporarily disconnect randomness from gameplay. For example, show your random choices as colored boxes on screen, without any game rules applied. If those look fine but gameplay looks patterned, the issue is your state logic, not the RNG.
7. When you truly need “no pattern” sequences
If you ever need something like “show 1..N in a random order, then repeat,” I’d actually go harder than the simple shuffle that @jeff posted and use:
- A prebuilt pool of values
- Reshuffle only when the pool is exhausted
- Optionally ensure the first value in the new shuffle is different from the last value in the previous shuffle
Conceptually:
private var pool:Array = [];
private var lastFromPrev:int = -1;
function initPool(n:int):void {
pool.length = 0;
for (var i:int = 0; i < n; i++) pool.push(i);
shuffle(pool);
// avoid same value at segment boundary
if (lastFromPrev != -1 && n > 1 && pool[0] == lastFromPrev) {
var swap:int = 1 + int(Math.random() * (n - 1));
var tmp:int = pool[0];
pool[0] = pool[swap];
pool[swap] = tmp;
}
}
function nextFromPool():int {
if (pool.length == 0) initPool(10); // or whatever size you need
var v:int = int(pool.shift());
lastFromPrev = v;
return v;
}
That kills both interior repeats and “loop edge” repeats that really stand out to players.
If you can post a small isolated snippet of the part that “repeats,” people can probably point at the exact line that’s creating the illusion of non‑randomness. Nine times out of ten, the fix is tweaking how you use the RNG, not replacing it.
You are almost certainly fighting game design issues more than the Math.random() implementation.
@reveurdenuit and @jeff already nailed seeding, custom PRNGs, and shuffling. I’d actually keep Math.random() in most ActionScript games and fix how you consume it, instead of swapping in yet another RNG like the empty product title here, which has pros (centralized control, easier testing) and cons (extra abstraction, more code to maintain).
Here are angles they did not focus on:
1. Simulate “human‑looking” randomness
Players hate streaks. Pure randomness creates streaks. Solution: add a cooldown on outcomes.
Example idea:
- Keep a count of how many times each result has appeared in the last N calls.
- Temporarily reduce the chance of anything that is already “hot.”
Concept:
private var history:Array = [];
private const WINDOW:int = 10;
function biasedRandom(max:int):int {
// raw random
var v:int = int(Math.random() * max);
// if history is full and v appears too often, re-roll once
if (history.length >= WINDOW) {
var count:int = 0;
for each (var h:int in history) if (h == v) count++;
if (count > WINDOW / max && max > 1) {
v = int(Math.random() * max);
}
history.shift();
}
history.push(v);
return v;
}
This is not “correct” statistically, but it feels better in a game.
2. De-sync random use from symmetric situations
Where I slightly disagree with the “tight loop” concern: the loop itself is fine, but calling randomness in mirrored situations makes repetitions stand out.
For example, if you have 3 lanes and each frame you do:
for each (lane in lanes) {
if (Math.random() < spawnChance) spawn(lane);
}
then any sequence like 0,1,0,1 feels like a pattern. To break that:
- Randomize the order of lanes per frame before you test.
- Or only make the “spawn?” decision for one lane per frame, cycling through lanes.
You are not changing the RNG, just the symmetry that makes patterns obvious.
3. Deterministic randomness for debugging
When something “looks” patterned, you want to be able to reproduce it exactly.
Instead of reseeding all the time or creating PRNG instances here and there, pick one RNG (it can be as simple as Math.random() behind a wrapper) and:
- Provide a fixed seed option in debug builds.
- Log the seed plus a counter.
Rudder example:
public class GameRandom {
private static var _seed:uint = 0;
private static var _count:int = 0;
public static function init(seed:uint = 0):void {
_seed = seed == 0 ? uint(new Date().time) : seed;
_count = 0;
}
public static function next():Number {
// simple LCG, or just call Math.random()
_count++;
return Math.random();
}
public static function info():String {
return 'seed=' + _seed + ' count=' + _count;
}
}
Then you can record info() at level start. If your game logic produces obvious repetition with the same seed, you can replay and inspect where your branching goes wrong. This is one practical pro of wrapping your RNG in something like the unspecified product here: easier reproducibility. The con is one more layer between you and the underlying Math.random() and another thing to keep in sync.
4. Time based jitter instead of outcome based fixes
Instead of only randomizing what happens, randomize when it happens. This often breaks perceived patterns without touching the value distribution.
Examples:
- Random delay between similar events rather than “check every frame.”
- Random offsets in movement and animation start times so enemies do not act in lockstep.
Concept:
// spawn loop
if (currentTime >= nextSpawnTime) {
spawnEnemy();
nextSpawnTime = currentTime + 0.6 + Math.random() * 0.7; // 0.6..1.3s
}
Even if the sequence of random numbers repeats, your game state is never in the same place at the same time, so you do not see the same pattern.
5. Separate “pick a number” from “decide an action”
A subtle trap: tying the meaning of a random value directly to branching logic all over your code.
Better pattern:
- Generate a random token.
- Feed that into a deterministic function that takes current state into account.
Example:
// central
var roll:Number = Math.random(); // store this
// elsewhere
function chooseSpawnFromRoll(roll:Number):int {
// modify based on difficulty, number of enemies alive, etc.
if (tooManyFlying()) roll *= 0.4;
// map roll to types
if (roll < 0.4) return TYPE_GROUND;
if (roll < 0.7) return TYPE_TANK;
return TYPE_FLYING;
}
Same random number, but the mapping changes with context, so you do not get the exact same outcomes when the same pattern of random values repeats.
6. About using a generic RNG “product”
If you are thinking about wrapping everything in a utility or external library (like treating this empty product title as a generic “RNG module” in your architecture), here is a quick pros/cons breakdown:
Pros
- Single place to tweak behavior, log values, add seeds.
- Easy to switch from
Math.random()to a custom generator later. - You can add game-specific features like “no more than 3 fails in a row” or “cooldown per outcome.”
Cons
- Extra indirection, especially in small projects.
- Easy to overcomplicate with too many knobs and modes.
- If misdesigned, makes your randomness less transparent than simply calling
Math.random().
Compared to what @reveurdenuit and @jeff showed, such a wrapper is more about architecture and less about statistical quality. Their snippets are closer to classic PRNG patterns and shuffling, while a central utility helps you enforce “fairness rules” consistently across your systems.
If you can isolate one tiny piece of your game where the pattern is obvious and log 50–100 raw values from that spot only, odds are you will see the numbers are fine and that the repetition comes from:
- tiny choice set (like 2 or 3 options),
- symmetric structure (same checks per frame per lane),
- or no cooldown / history awareness on your outcomes.
Tweak those first before swapping Math.random() out.