Easier way to do Common Event random non-repeating results?

ZombieKidzRule

Aren't these two Zombie Kidz cute?
Veteran
Joined
Jan 9, 2022
Messages
397
Reaction score
369
First Language
English
Primarily Uses
RMMZ
Hi, everyone. For background, I am very new to RPG Maker (just bought MZ before Christmas) and I am trying to learn everything I can do with the Basic Engine before delving into Plugins. I have no programming background and I don't know how to use Scripts. That is also on my list of things to explore/learn. So I am experimenting with achieving things based on my still very limited knowledge.

Now, I want to be able to create a Common Event (CE) that can be used a set number of times to randomly determine a result. Once the number of times is reached, the CE wouldn't be called anymore.

Imagine a CE that is called to randomly select a result of 1-4. Each number is a different result. Each time you call the CE, if a number has previously been set with a result, there has to be a re-roll until all 4 results have occurred. I can think of many different ways to use something like this with many different random ranges, but to test the functionality, I wanted to make a CE where the player would be transported to 4 different locations, depending on the result of the CE roll.

So I made a simple crossroads map and created the CE to randomly determine the transport to 4 different locations. Then, on the map, at each exit, I had the Transfer Event call the CE. After much trial and error using variables and self switches, I was able to make something and successfully test it. It seems to work as intended. Each exit from the map transfers the player to a different random location and the results become repeatable. Meaning that if you go North the first time, where you go is initially randomly determined but if you go back to that same exit, you will arrive at the same place that you initially did. The same is true for each exit from the map. Random the first time, then repeatable. So in the end, the 4 exits on the map end up taking you to the 4 locations, it is just that how you got to each location was randomly determined. No exits take you to the same location.

My question is: Is there an easier way to do something like this concept using Scripts or Plugins?

Here are some screenshots that I think will show what I did.

CE Random Transfer 1.jpgCE Random Transfer 2.jpgExample Event Transfer Page 1.jpgExample Event Transfer Page 2 of 5.jpg

In the transfer event, pages 3-5 are similar to page 2, just with a different switch and different transfer location.

I hope the screenshots adequately show what I did. I am thinking there must be an easier way to do randomization of things, but I haven't stumbled across it yet.

Thanks for any suggestions.

EDIT: I should have also included asking if there is an easier way to do with without Scripting or Plugins too? I did it the only way I could figure out. Maybe there is an easier way.
 
Last edited:

Nolonar

Veteran
Veteran
Joined
Feb 18, 2018
Messages
389
Reaction score
544
First Language
French, German
Primarily Uses
RMMZ
I wouldn't exactly say "easier", but the proper way to do this is through a process called "shuffling", which is primarily used in card games to ensure the same card won't appear multiple times, while still giving you a random card.

The problem with your current approach is that there's a chance the player will need to wait a long amount of time if the random number generator keeps returning a number that has already been given before. In the worst case, the player may even have to wait forever until a new number appears. This may not be noticeable in your example, since you only have 4 options, but the more options you have, the more likely it is you'll run into this problem (and the longer it'll take to get out).

Also, your event appears to be missing the case when the player has already been transported to all 4 locations. In this case the event will remain stuck in an infinite loop.



The simplest, most popular shuffling algorithm is the Fisher-Yates algorithm. It works like this:
  1. Take a random number between 1 and the number of cards you have.
  2. Swap the first card with the card given by the random number.
  3. Take a random number between 2 and the number of cards you have.
  4. Swap the second card with the card given by the random number.
  5. Repeat until you reach the last card.

We could in theory implement this with events only, but shuffling is best performed with an array (a collection of values), and we don't want to waste too many variables on that. Fortunately, we won't need a plugin for that. Instead, we'll be using the Script event command, which gives us access to JavaScript, but without needing a plugin.

First we want an array with our options. In your example, our options can be represented by numbers, so an array with numbers is good enough:
JavaScript:
const options = [...Array(4).keys()];
This will give you an array containing the number 0 - 3 (4 numbers in total).
Note: I'm not sure if MV supports this, but since this is about MZ, I'll just leave this code here, as it's the shortest (and easiest) way to create an enumerated array.

Next, we shuffle our array:
JavaScript:
for (let i = options.length - 1; i > 0; i--) {
    const index = Math.randomInt(i + 1);

    const tmp = options[i];
    options[i] = options[index];
    options[index] = tmp;
}
This is what is happening:
  • The for keyword allows us to loop while a certain condition is met.
  • let i = options.length - 1 creates a new variable named i (this is a JavaScript variable, and is separate from RPG Maker variables). The number is initialized with the number of cards we have (you'll understand the -1 part later). This variable only exists within the for loop.
  • i > 0 this is the condition for the for loop. As soon as i > 0 is no longer true (e.g. i === 0), the loop will stop.
  • i-- ensures that our i variable is decreased by 1 every time we're about to repeat the loop (this happens before the condition is checked).

  • const index = Math.randomInt(i + 1); gives us a random number between 0 and max - 1 (in this case, max is i + 1). Note: Math.randomInt() is a function that only exists in RPG Maker.

  • const tmp = options[i]; allows us to save the value of options[i] in a variable named tmp. We need this, because we're about to replace the value of options[i] with something else. Since we want to swap numbers, we need to remember what the old number was before we overwrite it. By the way, arrays start with the number 0, so if you want the first element of an array, you need [0] instead of [1]. This is why we originally set i to be options.length - 1 instead of simply options.length; otherwise we'd try and access a value that doesn't exist in our options array.
  • options[i] = options[index]; is the first step of our swap.
  • options[index] = tmp; is the second step of our swap.

As you can see, in this implementation of the Fisher-Yates algorithm, we shuffled from the last to first card (as opposed to shuffling from the first to last card). The reason is to simplify the math around getting the random number (it's easier to get a random number between 0 and n, than to get one between m and n).

Now that we have shuffled our options array, how do we use it? Simple:
JavaScript:
options.pop();
will give us the last element of our options array and remove it from the array. Thus, we ensure that we will never receive the same number twice.

Now to combine this with RPG Maker events:
  1. Use a Control Variables event command to create a variable that will contain our array. Operation: Set, Operand: Script. For the script, write [...Array(4).keys()] (change the 4 to the number of options you want to support). Also, remember the ID of the variable you're using. We'll need this so our shuffling algorithm can access the array.

  2. Use a Script event command and write the following code inside:
    JavaScript:
    const arr = $gameVariables.value(13);
    for (let i = arr.length - 1; i > 0; i--) {
        const index = Math.randomInt(i + 1);
    
        const tmp = arr[i];
        arr[i] = arr[index];
        arr[index] = tmp;
    }
    Replace the 13 with the ID of the variable from step 1 (if the ID is #0001, then use 1; do not add zeroes in front of the number, as this can lead to unexpected results).

The above two steps must be performed only once. They must not be repeated (unless you want to create a new array and shuffle it).

Once you've shuffled the array, every time you want to get your next random number, use a Control Variables event command to save the number in your D4Roll variable. Operation: Set, Operand: Script. For the script, write $gameVariables.value(13).pop(); Remember to replace the number 13 with the ID of your array variable.

As you keep popping numbers from your array, you'll eventually run out of numbers (and then you'll only get 0). To avoid that, you'll need a Conditional Branch event command with the Script: $gameVariables.value(13).length > 0. Again, remember to replace the number 13 with the ID of your array variable. If you enter that conditional branch, it means you still have numbers in your array. If you land in the Else branch, then you ran out of numbers to use. What to do in that case is yours to decide. Maybe you want to create a new array and shuffle it whenever your current array runs out?

The rest is very similar to your existing code:
Code:
If: D4Roll = 0
    Transfer Player: Normal Town
End
If: D4Roll = 1
    Transfer Player: Abandoned Town
End
If: D4Roll = 2
    Transfer Player: Oasis
End
If: D4Roll = 3
    Transfer Player: Mining City
End

Remember that our array had numbers from 0 to 3, so you need to adapt your event to use 0-3 instead of 1-4.



The advantages of this approach:
  • You don't need any Switches.
  • There are no unpredictable lags from when your game has to reroll a random number multiple times if the player is unlucky.
  • Your teleport event becomes much shorter and easier to read.
The disadvantages of this approach:
  • It might be harder to read and understand, depending on your programming skills.
  • You need to prepare an additional event that will create the array and shuffle it, and make sure it can only run once per playthrough (and always before the teleport event can run).
  • Your game can crash if you made any mistake writing (or copy-pasting) the script. If it does, finding and fixing the mistake may be harder.
 
Last edited:

ZombieKidzRule

Aren't these two Zombie Kidz cute?
Veteran
Joined
Jan 9, 2022
Messages
397
Reaction score
369
First Language
English
Primarily Uses
RMMZ
I wouldn't exactly say "easier", but the proper way to do this is through a process called "shuffling", which is primarily used in card games to ensure the same card won't appear multiple times, while still giving you a random card.

The problem with your current approach is that there's a chance the player will need to wait a long amount of time if the random number generator keeps returning a number that has already been given before. In the worst case, the player may even have to wait forever until a new number appears. This may not be noticeable in your example, since you only have 4 options, but the more options you have, the more likely it is you'll run into this problem (and the longer it'll take to get out).

Also, your event appears to be missing the case when the player has already been transported to all 4 locations. In this case the event will remain stuck in an infinite loop.



The simplest, most popular shuffling algorithm is the Fisher-Yates algorithm. It works like this:
  1. Take a random number between 1 and the number of cards you have.
  2. Swap the first card with the card given by the random number.
  3. Take a random number between 2 and the number of cards you have.
  4. Swap the second card with the card given by the random number.
  5. Repeat until you reach the last card.

We could in theory implement this with events only, but shuffling is best performed with an array (a collection of values), and we don't want to waste too many variables on that. Fortunately, we won't need a plugin for that. Instead, we'll be using the Script event command, which gives us access to JavaScript, but without needing a plugin.

First we want an array with our options. In your example, our options can be represented by numbers, so an array with numbers is good enough:
JavaScript:
const options = [...Array(4).keys()];
This will give you an array containing the number 0 - 3 (4 numbers in total).
Note: I'm not sure if MV supports this, but since this is about MZ, I'll just leave this code here, as it's the shortest (and easiest) way to create an enumerated array.

Next, we shuffle our array:
JavaScript:
for (let i = options.length - 1; i > 0; i--) {
    const index = Math.randomInt(i + 1);

    const tmp = options[i];
    options[i] = options[index];
    options[index] = tmp;
}
This is what is happening:
  • The for keyword allows us to loop while a certain condition is met.
  • let i = options.length - 1 creates a new variable named i (this is a JavaScript variable, and is separate from RPG Maker variables). The number is initialized with the number of cards we have (you'll understand the -1 part later). This variable only exists within the for loop.
  • i > 0 this is the condition for the for loop. As soon as i > 0 is no longer true (e.g. i === 0), the loop will stop.
  • i-- ensures that our i variable is decreased by 1 every time we're about to repeat the loop (this happens before the condition is checked).

  • const index = Math.randomInt(i + 1); gives us a random number between 0 and max - 1. Note: Math.randomInt() is a function that only exists in RPG Maker.

  • const tmp = options[i]; allows us to save the value of options[i] in a variable named tmp. We need this, because we're about to replace the value of options[i] with something else. Since we want to swap numbers, we need to remember what the old number was before we overwrite it. By the way, arrays start with the number 0, so if you want the first element of an array, you need [0] instead of [1]. This is why we originally set i to be options.length - 1 instead of simply options.length; otherwise we'd try and access a value that doesn't exist in our options array.
  • options[i] = options[index]; is the first step of our swap.
  • options[index] = tmp; is the second step of our swap.

As you can see, in this implementation of the Fisher-Yates algorithm, we shuffled from the last to first card (as opposed to shuffling from the first to last card). The reason is to simplify the math around getting the random number.

Now that we have shuffled our options array, how do we use it? Simple:
JavaScript:
options.pop();
will give us the last element of our options array and remove it from the array. Thus, we ensure that we will never receive the same number twice.

Now to combine this with RPG Maker events:
  1. Use a Control Variables event command to create a variable that will contain our array. Operation: Set, Operand: Script. For the script, write [...Array(4).keys()] (change the 4 to the number of options you want to support). Also, remember the ID of the variable you're using. We'll need this so our shuffling algorithm can access the array.

  2. Use a Script even command and write the following code inside:
    JavaScript:
    const arr = $gameVariables.value(13);
    for (let i = arr.length - 1; i > 0; i--) {
        const index = Math.randomInt(i + 1);
    
        const tmp = arr[i];
        arr[i] = arr[index];
        arr[index] = tmp;
    }
    Replace the 13 with the ID of the variable from step 1 (if the ID is #0001, then use 1; do not add zeroes in front of the number, as this can lead to unexpected results).

The above two steps must be performed only once. They must not be repeated (unless you want to create a new array and shuffle it).

Once you've shuffled the array, every time you want to get your next random number, use a Control Variables event command to save the number in your D4Roll variable. Operation: Set, Operand: Script. For the script, write $gameVariables.value(13).pop();. Remember to replace the number 13 with the ID of your array variable.

As you keep popping numbers from your array, you'll eventually run out of numbers (and then you'll only get 0). To avoid that, you'll need a Conditional Branch event command with the Script: $gameVariables.value(13).length > 0. Again, remember to replace the number 13 with the ID of your array variable. If you enter that conditional branch, it means you still have numbers in your array. If you land in the Else branch, then you ran out of numbers to use. What to do in that case is yours to decide.

The rest is very similar to your existing code:
Code:
If: D4Roll = 0
    Transfer Player: Normal Town
End
If: D4Roll = 1
    Transfer Player: Abandoned Town
End
If: D4Roll = 2
    Transfer Player: Oasis
End
If: D4Roll = 3
    Transfer Player: Mining City
End

Remember that our array had numbers from 0 to 3, so you need to adapt your event to use 0-3 instead of 1-4.



The advantages of this approach:
  • You don't need any Switches.
  • There are no unpredictable lags from when your game has to reroll a random number multiple times if the player is unlucky.
  • Your teleport event becomes much shorter and easier to read.
The disadvantages of this approach:
  • It uses JavaScript (which might be harder to understand, depending on your programming skills)
  • You need to prepare an additional event that will create the array and shuffle it, and make sure it can only run once per playthrough (and always before the teleport event can run).
Thank you so much for taking the time to provide this detailed explanation! I must admit my head started to swim in the beginning...math and apparently Java Script and I don't always get along...but by providing the exact steps to incorporate it, that helps tremendously. And a big thanks for saying what different things are doing.

I will try to replicate this to see if I get the results I previously achieved.

I can already tell that learning Scripting is going to be much more difficult for me than learning the basics of MZ!
 

Latest Threads

Latest Posts

Latest Profile Posts

Making progress. And simplifying.
jV7qfoy.gif
Not sure why I'm a jack of all trades , master of none... Sigh. Album out 06/03/2022 on all ur favorite platforms. 10 tracks.
Hmm... Mushrooms, maybe they're not poisonous...
unknown.png
Hi, I'm a new and upcoming Indie Developer, and I could really use some help when it comes to system programming. Explorers can only go so far before needing a map, you know?
I made a comic strip with the characters of my game.Flor en la cola eng.jpg

Forum statistics

Threads
122,057
Messages
1,146,191
Members
160,327
Latest member
OLEKsandr_GFE
Top