I would like to implement a seeded pseudo-random number generator in Verse language!
Reliability of This Article
by Our Founder/CEO&CTO Hiroyuki Chishiro
- He has been involved in 12 years of research on real-time systems.
- He taught OS (Linux kernel) in English at the University of Tokyo.
- From September 2012 to August 2013, he was a visiting researcher at the Department of Computer Science, the University of North Carolina at Chapel Hill (UNC), Chapel Hill, North Carolina, United States. He has been involved in research and development of real-time Linux in C language.
- He has more than 15 years of programming experience in C/C++, Python, Solidity/Vyper, Java, Ruby, Go, Rust, D, HTML/CSS/JS/PHP, MATLAB, Verse (UEFN), Assembler (x64, ARM).
- While a faculty member at the University of Tokyo, he developed the "Extension of LLVM Compiler" in C++ language and his own real-time OS "Mcube Kernel" in C language, which he published as open source on GitHub.
- In January 2020-Present, he is CTO of Guarantee Happiness LLC, Chapel Hill, North Carolina, United States, in charge of e-commerce site development and web/social network marketing. In June 2022-Present, he is CEO&CTO of Japanese Tar Heel, Inc. in Chapel Hill, North Carolina, United States.
- We have been engaged in disseminating useful information on AI and Crypto (Web3), and working on game development with Unreal Editor for Fortnite (UEFN).
- We have written more than 20 articles on AI including AI chatbots such as ChatGPT, Auto-GPT, Gemini (formerly Bard). He has experience in contract work as a prompt engineer, manager, and quality assurance (QA) for training ChatGPT/Gemini in several companies in San Francisco, United States (Silicon Valley in the broadest sense of the word).
- We have written more than 40 articles on cryptocurrency (including smart contract programming). He has experience as an outsourced translator of English articles on cryptocurrency into Japanese for a company in London, England.
- We have developed more than 10 games on UEFN and published on Fortnite (Fortnite, Fortnite.GG).
This article explains how to implement a seeded pseudo-random number generator in UEFN/Verse.
When creating random dungeons and similar systems in UEFN, you may want to reproduce the same generated result during debugging.
In this article, we implement a pseudo-random number generator named my_seeded_random_generator that allows you to specify a seed in Verse.
For production gameplay, we use the standard Verse Random module.
This implementation is mainly intended for debugging and reproducibility testing.
What Is a Seeded Pseudo-Random Number Generator?
A seeded pseudo-random number generator generates pseudo-random numbers from an initial value called a seed.
The seed is used to initialize the internal state of the pseudo-random number generator. The internal state is the data retained by the generator to calculate the next pseudo-random number.
If you set the same seed and generate random numbers in the same order, you can reproduce the same results.
For example, suppose a certain seed generates the following values:
|
1 2 3 4 5 |
15 80 39 4 34 |
If you set the same seed again, the same pseudo-random number sequence is generated.
The Verse Random module provides the following functions:
|
1 2 3 4 5 |
using { /Verse.org/Random } GetRandomInt() GetRandomFloat() Shuffle() |
GetRandomInt() and GetRandomFloat() generate random numbers, while Shuffle() randomly rearranges the elements of an array.
However, the Random module does not provide a public function for setting an arbitrary seed.
Therefore, if you want to reproduce the same generated results, you need to implement a separate seeded pseudo-random number generator.
Use Cases for a Seeded Pseudo-Random Number Generator
The main use case for a seeded pseudo-random number generator is debugging.
For example, suppose a random dungeon becomes impossible to complete only with a particular seed. By fixing the seed, you can generate the same dungeon repeatedly.
|
1 |
MyRandomGenerator.MySetSeed(12345) |
It can also be useful for:
- Reproducing random dungeons
- Reproducing enemy and trap placement
- Testing item rolls
- Reproducing bugs
- Regression testing
- Fixed maps such as a Daily Seed
However, when reproducibility is not required in production gameplay, use the standard Verse Random module.
|
1 2 3 4 5 |
Debugging my_seeded_random_generator Production gameplay Standard Verse Random module |
Why the Mersenne Twister Is Difficult to Implement in Verse
The Mersenne Twister is a high-quality pseudo-random number generator, but it makes extensive use of bitwise operations.
For more information about the algorithm, see Mersenne Twister on Wikipedia.
It uses operations such as:
|
1 2 3 4 5 |
XOR AND Left shift Right shift Bit mask |
In Verse, integer bitwise operations cannot be used as directly as they can in C or C++.
Therefore, these operations need to be reproduced manually.
Reproducing bitwise operations with per-bit loops makes the implementation more complicated and increases the amount of processing required.
Why We Chose MRG32k3a for a Seeded Pseudo-Random Number Generator
MRG32k3a is a method proposed by Pierre L'Ecuyer in 1999 that combines two multiple recursive pseudo-random number generators.
Its main characteristics are:
- No bitwise operations are required
- It can be implemented using integer multiplication, subtraction, and modulo operations
- The internal state consists of six integers
- Its period is approximately \(2^{191}\)
- It can be implemented relatively efficiently in Verse
For debugging random dungeons, it provides a sufficiently long period and sufficient random-number quality.
Compared with the Mersenne Twister:
| Item | MRG32k3a | Mersenne Twister |
|---|---|---|
| Main operations | Multiplication, subtraction, modulo | Bitwise operations |
| Internal state | 6 values | 624 values |
| Implementation in Verse | Relatively simple | Complex |
| Period | Sufficiently long | Extremely long |
For use in Verse, we decided that MRG32k3a would be easier to implement.
Implementing my_seeded_random_generator in Verse
The class and functions implemented in this article are:
|
1 |
my_seeded_random_generator |
|
1 2 3 4 |
MySetSeed() MyGetRandomInt() MyGetRandomFloat() MyShuffle() |
We prefix the function names with My to distinguish them from the functions provided by the standard Verse Random module.
Our implementation is shown below.
MyGetRandomInt() assumes that the requested integer range does not exceed the output range of one MRG32k3a value.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# # Author: Hiroyuki Chishiro # License: 2-Clause BSD # using { /Verse.org/Verse } my_seeded_random_generator<public> := class<concrete>: # The two moduli used by MRG32k3a. # Modulus1 = 2^32 - 209 # Modulus2 = 2^32 - 22853 Modulus1<private>:int = 4294967087 Modulus2<private>:int = 4294944443 # Internal state of MRG32k3a. # The two generators each maintain three state values. var State10<private>:int = 1 var State11<private>:int = 1 var State12<private>:int = 1 var State20<private>:int = 1 var State21<private>:int = 1 var State22<private>:int = 1 # Initializes the internal state from a seed. MySetSeed<public>(Seed:int)<transacts>:void = # Modulus2 is smaller than Modulus1, # so this range is valid for all six state values. if (NormalizedSeed := Mod[Seed, Modulus2 - 1]): StateValue := NormalizedSeed + 1 set State10 = StateValue set State11 = StateValue set State12 = StateValue set State20 = StateValue set State21 = StateValue set State22 = StateValue # Generates a raw pseudo-random number using MRG32k3a. NextRaw<private>()<transacts>:int = # Calculates the two recurrence equations defined by MRG32k3a. if: Component1 := Mod[ (1403580 * State11) - (810728 * State10), Modulus1 ] Component2 := Mod[ (527612 * State22) - (1370589 * State20), Modulus2 ] then: # Advances the internal state by one step. set State10 = State11 set State11 = State12 set State12 = Component1 set State20 = State21 set State21 = State22 set State22 = Component2 # Combines the results of the two generators. if (Component1 <= Component2): return Component1 - Component2 + Modulus1 return Component1 - Component2 return 1 # Generates an integer from 0 through Count - 1. # Rejection sampling prevents modulo bias. NextBounded<private>(Count:int)<transacts>:int = if (Count <= 1): return 0 if (Count > Modulus1): return 0 if (Remainder := Mod[Modulus1, Count]): Limit := Modulus1 - Remainder loop: Candidate := NextRaw() - 1 if (Candidate < Limit): if (Result := Mod[Candidate, Count]): return Result return 0 # Generates an integer from Low through High, including both endpoints. MyGetRandomInt<public>( Low:int, High:int )<transacts>:int = Minimum := if (Low <= High): Low else: High Maximum := if (Low <= High): High else: Low Count := Maximum - Minimum + 1 if (Count <= 0 or Count > Modulus1): return Minimum return Minimum + NextBounded(Count) # Generates a floating-point number between Low and High. MyGetRandomFloat<public>( Low:float, High:float )<transacts>:float = UnitValue := (1.0 * (NextRaw() - 1)) / (1.0 * (Modulus1 - 1)) return Low + ((High - Low) * UnitValue) # Shuffles an array using the Fisher-Yates algorithm. MyShuffle<public>( Input:[]t where t:type )<transacts>:[]t = var Result:[]t = Input var CurrentIndex:int = Result.Length - 1 loop: if (CurrentIndex <= 0): break SwapIndex := MyGetRandomInt(0, CurrentIndex) if: CurrentValue := Result[CurrentIndex] SwapValue := Result[SwapIndex] set Result[CurrentIndex] = SwapValue set Result[SwapIndex] = CurrentValue set CurrentIndex = CurrentIndex - 1 return Result |
How to Use my_seeded_random_generator
The following example demonstrates how to use my_seeded_random_generator.
It first verifies that the same seed produces the same value, and then uses MyGetRandomInt(), MyGetRandomFloat(), and MyShuffle().
FirstValue and SecondValue have the same value.
After that, an integer, a floating-point value, and a shuffled array are output.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# # Author: Hiroyuki Chishiro # License: 2-Clause BSD # using { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } my_seeded_random_generator_device := class(creative_device): MyRandomGenerator:my_seeded_random_generator = my_seeded_random_generator{} OnBegin<override>()<suspends>:void = # Verify that the same seed produces the same value. MyRandomGenerator.MySetSeed(12345) FirstValue := MyRandomGenerator.MyGetRandomInt(0, 100) MyRandomGenerator.MySetSeed(12345) SecondValue := MyRandomGenerator.MyGetRandomInt(0, 100) Print("FirstValue = {FirstValue}") Print("SecondValue = {SecondValue}") # Generate a pseudo-random integer from 0 through 100. RandomInt := MyRandomGenerator.MyGetRandomInt(0, 100) Print("RandomInt = {RandomInt}") # Generate a pseudo-random float from 0.5 through 3.0. RandomFloat := MyRandomGenerator.MyGetRandomFloat(0.5, 3.0) Print("RandomFloat = {RandomFloat}") # Shuffle an array. RoomOrder := MyRandomGenerator.MyShuffle( array{ "Entrance", "Combat", "Trap", "Treasure", "Boss" } ) for (Index -> Room : RoomOrder): Print("RoomOrder[{Index}] = {Room}") |
With this implementation, if you use the same seed and the same function call order, the output is always as follows:
|
1 2 3 4 5 6 7 8 9 |
FirstValue = 26 SecondValue = 26 RandomInt = 79 RandomFloat = 1.308464 RoomOrder[0] = Trap RoomOrder[1] = Entrance RoomOrder[2] = Treasure RoomOrder[3] = Combat RoomOrder[4] = Boss |
The output from the Print function can be viewed under Verse Diagnostics in the Message Log.
The file path, line number, timestamp, and other log information have been omitted.
Important Notes When Using my_seeded_random_generator
When using my_seeded_random_generator, pay attention to the order in which pseudo-random numbers are generated.
Even if you use the same seed, changing the order or number of calls to the random-number functions changes the pseudo-random numbers generated afterward.
For example, suppose pseudo-random numbers are used in this order:
- Room
- Enemy
- Trap
If a random-number operation for decorations is inserted:
- Room
- Decoration
- Enemy
- Trap
The number of calls to the random-number functions has increased by one, so the pseudo-random numbers used for enemies and traps also change.
For this reason, in a large random dungeon, we recommend using separate pseudo-random number generators for different purposes.
|
1 2 3 4 5 6 7 8 |
TopologyRandomGenerator:my_seeded_random_generator = my_seeded_random_generator{} EncounterRandomGenerator:my_seeded_random_generator = my_seeded_random_generator{} DecorationRandomGenerator:my_seeded_random_generator = my_seeded_random_generator{} |
Also, random-number generation on the same my_seeded_random_generator instance is not reentrant.
Reentrant means that a procedure can be invoked again before a previous invocation has completed without the invocations interfering with each other.
Because the generator modifies its internal state every time it generates a pseudo-random number, using the same instance from multiple concurrent operations may cause the result to depend on the order in which random numbers are generated.
When using the generator in concurrent operations, use a different my_seeded_random_generator instance for each operation.
Reference: Comparison with the Epic Developer Community Pseudo-Random Number Generator
The Epic Developer Community has another seeded pseudo-random number generator implemented by TyrantKingBen.
That implementation uses a string seed and SFC32.
SFC32 itself is a lightweight pseudo-random number generator, but it uses bitwise operations.
Therefore, its Verse implementation reproduces operations such as:
|
1 2 3 4 5 6 |
LeftShift RightShift XOR OR UInt32 IMul |
Because the bitwise operations are reproduced using loops, the Verse implementation requires more processing.
The implementations can be compared as follows:
| Item | MRG32k3a Implementation in This Article | Epic Developer Community SFC32 Implementation |
|---|---|---|
| Seed | int | string |
| Main operations | Multiplication, subtraction, modulo | XOR, shifts, OR |
| Bitwise-operation emulation | Not required | Required |
| Processing in Verse | Relatively low | Relatively high |
| String seed | Not supported | Supported |
| Debugging | Suitable | Suitable |
If you want to use a string directly as a seed, the SFC32 implementation is convenient.
On the other hand, if an integer seed is sufficient and you want to avoid emulating bitwise operations in Verse, MRG32k3a is a suitable choice.
Summary
This article explained how to implement a seeded pseudo-random number generator in UEFN/Verse.
For debugging random dungeons and similar systems, use my_seeded_random_generator.
For production gameplay, use the standard Verse Random module.
If you'd like to play the game we developed using the Unreal Editor for Fortnite (UEFN) in Fortnite, click the following.
-
-
Our Game in Unreal Editor for Fortnite (UEFN) for Fortnite
You can learn from us. We introduce our game in Unreal Editor for Fortnite (UEFN) for Fortnite. Let's come and play our games! If you want to learn UEFN first, please click the following. March 20, 2024: 🌸🥚Spring Easy Deathrun 250+🌸🥚 [Island Code: 7022-3666-7030] 🌸🥚Spring Easy Deathrun 250+🌸🥚 is a spring-themed deathrun (parkour) game. Deathrun is an action game like a 3D version of Super Mario. You can enjoy cherry blossoms and Easter eggs! https://www.youtube.com/watch?v=zPpiu_hDqTo If you want to play this game, copy the following Island Code "7022-3666-7030" and search for it in Fortnite! April 3, 2024: ✒Math Times Table ...