forked from XpRienzo/DragonHeaven
-
Notifications
You must be signed in to change notification settings - Fork 0
/
prng.js
173 lines (151 loc) · 5.41 KB
/
prng.js
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
'use strict';
// This function is designed to emulate the on-cartridge PRNG for Gens 3 and 4, as described in
// http://www.smogon.com/ingame/rng/pid_iv_creation#pokemon_random_number_generator
// This RNG uses a 32-bit initial seed
// m and n are converted to integers via Math.floor. If the result is NaN, they are ignored.
/*
random(m, n) {
this.seed = (this.seed * 0x41C64E6D + 0x6073) >>> 0; // truncate the result to the last 32 bits
let result = this.seed >>> 16; // the first 16 bits of the seed are the random value
m = Math.floor(m)
n = Math.floor(n)
return (m ? (n ? (result % (n - m)) + m : result % m) : result / 0x10000)
}
*/
// This function is designed to emulate the on-cartridge PRNG for Gen 5 and uses a 64-bit initial seed
// This function has three different results, depending on arguments:
// - random() returns a real number in [0, 1), just like Math.random()
// - random(n) returns an integer in [0, n)
// - random(m, n) returns an integer in [m, n)
// m and n are converted to integers via Math.floor. If the result is NaN, they are ignored.
class PRNG {
/**
* Creates a new source of randomness for the given seed.
*
* @param {number[]} [seed]
*/
constructor(seed = PRNG.generateSeed()) {
// We slice() the seed so we get a copy of it instead of the original seed.
this.initialSeed = seed.slice();
this.seed = seed.slice();
}
/**
* Getter to the initial seed.
*
* This should be considered a hack and is only here for backwards compatibility.
*/
get startingSeed() {
return this.initialSeed;
}
/**
* Creates a clone of the current PRNG.
*
* The new PRNG will have its initial seed set to the seed of the current instance.
*/
clone() {
return new PRNG(this.seed);
}
/**
* Retrieves the next random number in the sequence.
*
* @param {number} [from]
* @param {number} [to]
* @returns {number} a real number in [0, 1) if no arguments are specified
* @returns {number} an integer in [0, from) if from is specified
* @returns {number} an integer in [from, to) if from and to are specified
*/
next(from, to) {
this.seed = this.nextFrame(this.seed); // Advance the RNG
let result = (this.seed[0] << 16 >>> 0) + this.seed[1]; // Use the upper 32 bits
from = Math.floor(from);
to = Math.floor(to);
if (!from) {
result = result / 0x100000000;
} else if (!to) {
result = Math.floor(result * from / 0x100000000);
} else {
result = Math.floor(result * (to - from) / 0x100000000) + from;
}
return result;
}
/**
The RNG is a Linear Congruential Generator (LCG) in the form: `x_{n + 1} = (a x_n + c) % m`
Where: `x_0` is the seed, `x_n` is the random number after n iterations,
````
a = 0x5D588B656C078965
c = 0x00269EC3
m = 2^64
````
Javascript doesnt handle such large numbers properly, so this function does it in 16-bit parts.
````
x_{n + 1} = (x_n * a) + c
````
Let any 64 bit number:
````
n = (n[0] << 48) + (n[1] << 32) + (n[2] << 16) + n[3]
````
Then:
````
x_{n + 1} =
((a[3] x_n[0] + a[2] x_n[1] + a[1] x_n[2] + a[0] x_n[3] + c[0]) << 48) +
((a[3] x_n[1] + a[2] x_n[2] + a[1] x_n[3] + c[1]) << 32) +
((a[3] x_n[2] + a[2] x_n[3] + c[2]) << 16) +
a[3] x_n[3] + c[3]
````
Which can be generalised where b is the number of 16 bit words in the number:
````
((a[b-1] + x_n[b-1] + c[b-1]) << (16 * 0)) +
((a[b-1] x_n[b-2] + a[b-2] x_n[b-1] + c[b-2]) << (16 * 1)) +
((a[b-1] x_n[b-3] + a[b-2] x_n[b-2] + a[b-3] x_n[b-1] + c[b-3]) << (16 * 2)) +
...
((a[b-1] x_n[1] + a[b-2] x_n[2] + ... + a[2] x_n[b-2] + a[1] + x_n[b-1] + c[1]) << (16 * (b-2))) +
((a[b-1] x_n[0] + a[b-2] x_n[1] + ... + a[1] x_n[b-2] + a[0] + x_n[b-1] + c[0]) << (16 * (b-1)))
````
Which produces this equation:
````
\sum_{l=0}^{b-1}\left(\sum_{m=b-l-1}^{b-1}\left\{a[2b-m-l-2] x_n[m]\right\}+c[b-l-1]\ll16l\right)
````
Notice how the `a[]` word starts at `b-1`, and decrements every time it appears again on the line;
`x_n[]` starts at `b-<line#>-1` and increments to b-1 at the end of the line per line, limiting the length of the line;
`c[]` is at `b-<line#>-1` for each line and the left shift is `16 * <line#>`)
This is all ignoring overflow/carry because that cannot be shown in a pseudo-mathematical equation.
The below code implements a optimised version of that equation while also checking for overflow/carry.
@param {number} [framesToAdvance = 1]
@return {number[]} the new seed
*/
nextFrame(initialSeed, framesToAdvance = 1) {
// Use Slice so we don't actually alter the original seed.
let seed = initialSeed.slice();
for (let frame = 0; frame < framesToAdvance; ++frame) {
const a = [0x5D58, 0x8B65, 0x6C07, 0x8965];
const c = [0, 0, 0x26, 0x9EC3];
const nextSeed = [0, 0, 0, 0];
let carry = 0;
for (let cN = seed.length - 1; cN >= 0; --cN) {
nextSeed[cN] = carry;
carry = 0;
let aN = seed.length - 1;
for (let seedN = cN; seedN < seed.length; --aN, ++seedN) {
let nextWord = a[aN] * seed[seedN];
carry += nextWord >>> 16;
nextSeed[cN] += nextWord & 0xFFFF;
}
nextSeed[cN] += c[cN];
carry += nextSeed[cN] >>> 16;
nextSeed[cN] &= 0xFFFF;
}
seed = nextSeed;
}
return seed;
}
static generateSeed() {
// use a random initial seed (64-bit, [high -> low])
return [
Math.floor(Math.random() * 0x10000),
Math.floor(Math.random() * 0x10000),
Math.floor(Math.random() * 0x10000),
Math.floor(Math.random() * 0x10000),
];
}
}
module.exports = PRNG;