toosoon-prng 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,420 @@
1
+ import type { Gui, GuiController } from 'toosoon-gui';
2
+
3
+ import prng from './prng';
4
+
5
+ export enum PRNGControllerTypes {
6
+ Boolean = 'Boolean',
7
+ Sign = 'Sign',
8
+ Int = 'Int',
9
+ Float = 'Float',
10
+ HexColor = 'HexColor',
11
+ Item = 'Item',
12
+ ObjectProperty = 'ObjectProperty',
13
+ Weights = 'Weights'
14
+ }
15
+
16
+ export interface PRNGController<T = unknown> {
17
+ seed: string;
18
+ value: T;
19
+ addGUI(gui: Gui, min?: number, max?: number, step?: number): GuiController;
20
+ getValue(): T;
21
+ dispose(): void;
22
+ }
23
+
24
+ export interface PRNGGroupController<T = unknown> {
25
+ seed: string;
26
+ addGUI(gui: Gui, min?: number, max?: number, step?: number): Gui;
27
+ createController(index: number): PRNGController<T>;
28
+ getValueAt(index: number): T;
29
+ dispose(): void;
30
+ }
31
+
32
+ // *********************
33
+ // Abstract controllers
34
+ // *********************
35
+ abstract class BasePRNGController<T> implements PRNGController<T> {
36
+ seed: string;
37
+ abstract value: T;
38
+ gui!: GuiController;
39
+
40
+ constructor(seed: string) {
41
+ this.seed = seed;
42
+ prng.addController(this);
43
+ }
44
+
45
+ abstract addGUI(gui: Gui, min?: number, max?: number, step?: number): GuiController;
46
+
47
+ abstract getValue(): T;
48
+
49
+ dispose() {
50
+ prng.removeController(this);
51
+ this.gui?.destroy();
52
+ }
53
+ }
54
+
55
+ abstract class BasePRNGGroupController<T> implements PRNGGroupController<T> {
56
+ seed: string;
57
+ controllers: PRNGController<T>[] = [];
58
+ gui!: Gui;
59
+ guiArgs!: {
60
+ min?: number;
61
+ max?: number;
62
+ step?: number;
63
+ };
64
+
65
+ constructor(seed: string) {
66
+ this.seed = seed;
67
+ }
68
+
69
+ addGUI(gui: Gui, min?: number, max?: number, step?: number) {
70
+ this.gui = gui.addFolder(this.seed).close();
71
+ this.guiArgs = { min, max, step };
72
+ return this.gui;
73
+ }
74
+
75
+ abstract createController(index: number): PRNGController<T>;
76
+
77
+ getValueAt(index: number): T {
78
+ let controller = this.controllers[index];
79
+ if (!controller) {
80
+ controller = this.createController(index);
81
+ if (this.gui) {
82
+ controller
83
+ .addGUI(this.gui, this.guiArgs.min, this.guiArgs.max, this.guiArgs.step)
84
+ .name(`${this.seed}-${index}`);
85
+ }
86
+ this.controllers[index] = controller;
87
+ }
88
+ return controller.value;
89
+ }
90
+
91
+ dispose() {
92
+ this.controllers.forEach((controller) => controller.dispose());
93
+ this.controllers = [];
94
+ this.gui?.destroy();
95
+ }
96
+ }
97
+
98
+ // *********************
99
+ // Controllers
100
+ // *********************
101
+ export class BooleanController extends BasePRNGController<boolean> {
102
+ value: boolean;
103
+ probability: number;
104
+
105
+ constructor(seed: string, probability = 0.5) {
106
+ super(seed);
107
+
108
+ this.probability = probability;
109
+ this.value = this.getValue();
110
+ }
111
+
112
+ addGUI(gui: Gui) {
113
+ this.gui = gui.add(this, 'value').name(this.seed);
114
+ return this.gui;
115
+ }
116
+
117
+ getValue() {
118
+ this.value = prng.randomBoolean(this.seed, this.probability);
119
+ this.gui?.updateDisplay();
120
+ return this.value;
121
+ }
122
+ }
123
+
124
+ export class SignController extends BasePRNGController<number> {
125
+ value: number;
126
+ probability: number;
127
+
128
+ constructor(seed: string, probability = 0.5) {
129
+ super(seed);
130
+
131
+ this.probability = probability;
132
+ this.value = this.getValue();
133
+ }
134
+
135
+ addGUI(gui: Gui) {
136
+ this.gui = gui.add(this, 'value', [-1, 1]).name(this.seed);
137
+ return this.gui;
138
+ }
139
+
140
+ getValue() {
141
+ this.value = prng.randomSign(this.seed, this.probability);
142
+ this.gui?.updateDisplay();
143
+ return this.value;
144
+ }
145
+ }
146
+
147
+ export class IntController extends BasePRNGController<number> {
148
+ value: number;
149
+ min: number;
150
+ max: number;
151
+
152
+ constructor(seed: string, min = 0, max = 1) {
153
+ super(seed);
154
+
155
+ this.min = min;
156
+ this.max = max;
157
+ this.value = prng.randomInt(this.seed, min, max);
158
+ }
159
+
160
+ addGUI(gui: Gui, min: number, max: number, step = 1) {
161
+ this.gui = gui.add(this, 'value', min, max, step).name(this.seed);
162
+ return this.gui;
163
+ }
164
+
165
+ getValue() {
166
+ this.value = prng.randomInt(this.seed, this.min, this.max);
167
+ this.gui?.updateDisplay();
168
+ return this.value;
169
+ }
170
+ }
171
+
172
+ export class FloatController extends BasePRNGController<number> {
173
+ value: number;
174
+ min: number;
175
+ max: number;
176
+
177
+ constructor(seed: string, min = 0, max = 1) {
178
+ super(seed);
179
+
180
+ this.min = min;
181
+ this.max = max;
182
+ this.value = this.getValue();
183
+ }
184
+
185
+ addGUI(gui: Gui, min: number, max: number, step: number = 0.01) {
186
+ this.gui = gui.add(this, 'value', min, max, step).name(this.seed);
187
+ return this.gui;
188
+ }
189
+
190
+ getValue() {
191
+ this.value = prng.randomFloat(this.seed, this.min, this.max);
192
+ this.gui?.updateDisplay();
193
+ return this.value;
194
+ }
195
+ }
196
+
197
+ export class HexColorController extends BasePRNGController<string> {
198
+ value: string;
199
+
200
+ constructor(seed: string) {
201
+ super(seed);
202
+
203
+ this.value = this.getValue();
204
+ }
205
+
206
+ addGUI(gui: Gui) {
207
+ this.gui = gui.addColor(this, 'value').name(this.seed);
208
+ return this.gui;
209
+ }
210
+
211
+ getValue() {
212
+ this.value = prng.randomHexColor(this.seed);
213
+ this.gui?.updateDisplay();
214
+ return this.value;
215
+ }
216
+ }
217
+
218
+ export class ItemController<T = unknown> extends BasePRNGController<T> {
219
+ value: T;
220
+ items: T[];
221
+
222
+ constructor(seed: string, items: T[]) {
223
+ super(seed);
224
+
225
+ this.items = items;
226
+ this.value = this.getValue();
227
+ }
228
+
229
+ addGUI(gui: Gui) {
230
+ this.gui = gui.add(this, 'value', this.items).name(this.seed);
231
+ return this.gui;
232
+ }
233
+
234
+ getValue() {
235
+ this.value = prng.randomItem(this.seed, this.items) as T;
236
+ this.gui?.updateDisplay();
237
+ return this.value;
238
+ }
239
+ }
240
+
241
+ export class ObjectPropertyController<T = unknown> extends BasePRNGController<T> {
242
+ value: T;
243
+ object: Object;
244
+
245
+ constructor(seed: string, object: Object) {
246
+ super(seed);
247
+
248
+ this.object = object;
249
+ this.value = this.getValue();
250
+ }
251
+
252
+ addGUI(gui: Gui) {
253
+ this.gui = gui.add(this, 'value', this.object).name(this.seed);
254
+ return this.gui;
255
+ }
256
+
257
+ getValue() {
258
+ this.value = prng.randomObjectProperty(this.seed, this.object) as T;
259
+ this.gui?.updateDisplay();
260
+ return this.value;
261
+ }
262
+ }
263
+
264
+ type WeightsItems<T> = Array<{ weight: number; value: T }>;
265
+
266
+ export class WeightsController<T = unknown> extends BasePRNGController<T> {
267
+ value: T;
268
+ items: WeightsItems<T>;
269
+ weights: number[];
270
+
271
+ constructor(seed: string, items: WeightsItems<T>) {
272
+ super(seed);
273
+
274
+ this.items = items;
275
+ this.weights = this.items.map((item) => item.weight);
276
+ this.value = this.getValue();
277
+ }
278
+
279
+ addGUI(gui: Gui) {
280
+ this.gui = gui
281
+ .add(
282
+ this,
283
+ 'value',
284
+ this.items.map((item) => item.value)
285
+ )
286
+ .name(this.seed);
287
+ return this.gui;
288
+ }
289
+
290
+ getValue() {
291
+ const index = prng.randomIndex(this.seed, this.weights);
292
+ this.value = this.items[index].value;
293
+ this.gui?.updateDisplay();
294
+ return this.value;
295
+ }
296
+ }
297
+
298
+ // *********************
299
+ // Group Controllers
300
+ // *********************
301
+ export class BooleanGroupController extends BasePRNGGroupController<boolean> {
302
+ probability: number;
303
+ controllers: BooleanController[] = [];
304
+
305
+ constructor(seed: string, probability: number) {
306
+ super(seed);
307
+
308
+ this.probability = probability;
309
+ }
310
+
311
+ createController(index: number) {
312
+ return new BooleanController(`${this.seed}-${index}`, this.probability);
313
+ }
314
+ }
315
+
316
+ export class SignGroupController extends BasePRNGGroupController<number> {
317
+ probability: number;
318
+ controllers: SignController[] = [];
319
+
320
+ constructor(seed: string, probability: number) {
321
+ super(seed);
322
+
323
+ this.probability = probability;
324
+ }
325
+
326
+ createController(index: number) {
327
+ return new SignController(`${this.seed}-${index}`, this.probability);
328
+ }
329
+ }
330
+
331
+ export class IntGroupController extends BasePRNGGroupController<number> {
332
+ min: number;
333
+ max: number;
334
+ controllers: IntController[] = [];
335
+
336
+ constructor(seed: string, min: number, max: number) {
337
+ super(seed);
338
+
339
+ this.min = min;
340
+ this.max = max;
341
+ }
342
+
343
+ createController(index: number) {
344
+ return new IntController(`${this.seed}-${index}`, this.min, this.max);
345
+ }
346
+ }
347
+
348
+ export class FloatGroupController extends BasePRNGGroupController<number> {
349
+ min: number;
350
+ max: number;
351
+ controllers: FloatController[] = [];
352
+
353
+ constructor(seed: string, min: number, max: number) {
354
+ super(seed);
355
+
356
+ this.min = min;
357
+ this.max = max;
358
+ }
359
+
360
+ createController(index: number) {
361
+ return new FloatController(`${this.seed}-${index}`, this.min, this.max);
362
+ }
363
+ }
364
+
365
+ export class HexColorGroupController extends BasePRNGGroupController<string> {
366
+ controllers: HexColorController[] = [];
367
+
368
+ // constructor(seed: string) {
369
+ // super(seed);
370
+ // }
371
+
372
+ createController(index: number) {
373
+ return new HexColorController(`${this.seed}-${index}`);
374
+ }
375
+ }
376
+
377
+ export class ItemGroupController<T = unknown> extends BasePRNGGroupController<T> {
378
+ items: T[];
379
+ controllers: ItemController<T>[] = [];
380
+
381
+ constructor(seed: string, items: T[]) {
382
+ super(seed);
383
+
384
+ this.items = items;
385
+ }
386
+
387
+ createController(index: number) {
388
+ return new ItemController<T>(`${this.seed}-${index}`, this.items);
389
+ }
390
+ }
391
+
392
+ export class ObjectPropertyGroupController<T = unknown> extends BasePRNGGroupController<T> {
393
+ object: Object;
394
+ controllers: ObjectPropertyController<T>[] = [];
395
+
396
+ constructor(seed: string, object: Object) {
397
+ super(seed);
398
+
399
+ this.object = object;
400
+ }
401
+
402
+ createController(index: number) {
403
+ return new ObjectPropertyController<T>(`${this.seed}-${index}`, this.object);
404
+ }
405
+ }
406
+
407
+ export class WeightsGroupController<T = unknown> extends BasePRNGGroupController<T> {
408
+ items: WeightsItems<T>;
409
+ controllers: WeightsController<T>[] = [];
410
+
411
+ constructor(seed: string, items: WeightsItems<T>) {
412
+ super(seed);
413
+
414
+ this.items = items;
415
+ }
416
+
417
+ createController(index: number) {
418
+ return new WeightsController<T>(`${this.seed}-${index}`, this.items);
419
+ }
420
+ }
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ export {
2
+ BooleanController,
3
+ SignController,
4
+ IntController,
5
+ FloatController,
6
+ HexColorController,
7
+ ItemController,
8
+ ObjectPropertyController,
9
+ WeightsController,
10
+ BooleanGroupController,
11
+ SignGroupController,
12
+ IntGroupController,
13
+ FloatGroupController,
14
+ HexColorGroupController,
15
+ ItemGroupController,
16
+ ObjectPropertyGroupController,
17
+ WeightsGroupController
18
+ } from './controllers';
19
+
20
+ export { cyrb128, sfc32, mulberry32, jsf32, xoshiro128ss } from './utils';
21
+
22
+ export type { PRNGControllerTypes, PRNGController, PRNGGroupController } from './controllers';
23
+
24
+ export { default } from './prng';
package/src/prng.ts ADDED
@@ -0,0 +1,112 @@
1
+ import { PRNGController } from './controllers';
2
+ import { cyrb128, jsf32, mulberry32, sfc32, xoshiro128ss } from './utils';
3
+
4
+ export enum PRNGMethod {
5
+ jsf32 = 'jsf32',
6
+ mulberry32 = 'mulberry32',
7
+ sfc32 = 'sfc32',
8
+ xoshiro128ss = 'xoshiro128**'
9
+ }
10
+
11
+ class PRNG {
12
+ public seed: string = '';
13
+ public method: PRNGMethod = PRNGMethod.sfc32;
14
+ public controllers: PRNGController[] = [];
15
+
16
+ constructor(seed: string = '') {
17
+ this.setSeed(seed);
18
+ }
19
+
20
+ public addController(controller: PRNGController) {
21
+ this.controllers.push(controller);
22
+ }
23
+
24
+ public removeController(controller: PRNGController) {
25
+ const index = this.controllers.indexOf(controller);
26
+ this.controllers.splice(index, 1);
27
+ }
28
+
29
+ public setSeed(seed: string) {
30
+ if (this.seed === seed) return;
31
+
32
+ this.seed = seed;
33
+
34
+ this.controllers.forEach((controller) => {
35
+ controller.getValue();
36
+ });
37
+ }
38
+
39
+ public setMethod(method: PRNGMethod) {
40
+ this.method = method;
41
+ }
42
+
43
+ public random(seed: string): number {
44
+ const hashes = cyrb128(this.seed + seed);
45
+ switch (this.method) {
46
+ case PRNGMethod.jsf32:
47
+ return jsf32(hashes[0], hashes[1], hashes[2], hashes[3]);
48
+ case PRNGMethod.mulberry32:
49
+ return mulberry32(hashes[0]);
50
+ case PRNGMethod.sfc32:
51
+ return sfc32(hashes[0], hashes[1], hashes[2], hashes[3]);
52
+ case PRNGMethod.xoshiro128ss:
53
+ return xoshiro128ss(hashes[0], hashes[1], hashes[2], hashes[3]);
54
+ }
55
+ }
56
+
57
+ public randomBoolean(seed: string, probability: number = 0.5): boolean {
58
+ return this.random(seed) < probability;
59
+ }
60
+
61
+ public randomSign(seed: string, probability: number = 0.5): number {
62
+ return this.randomBoolean(seed, probability) ? 1 : -1;
63
+ }
64
+
65
+ public randomInt(seed: string, min: number, max: number) {
66
+ return Math.floor(this.random(seed) * (max - min + 1) + min);
67
+ }
68
+
69
+ public randomFloat(seed: string, min: number = 0, max: number = 1, precision: number = 2): number {
70
+ return parseFloat(Math.min(min + this.random(seed) * (max - min), max).toFixed(precision));
71
+ }
72
+
73
+ public randomHexColor(seed: string): string {
74
+ return '#' + ('00000' + ((this.random(seed) * (1 << 24)) | 0).toString(16)).slice(-6);
75
+ }
76
+
77
+ public randomItem<T = unknown>(seed: string, array: T[] = []): T | undefined {
78
+ if (array.length === 0) return undefined;
79
+ return array[this.randomInt(seed, 0, array.length - 1)];
80
+ }
81
+
82
+ public randomObjectProperty(seed: string, object: object): unknown | undefined {
83
+ const keys = Object.keys(object);
84
+ const key = this.randomItem(seed, keys);
85
+ if (key && object.hasOwnProperty(key)) {
86
+ return object[key as keyof object];
87
+ }
88
+ }
89
+
90
+ public randomIndex(seed: string, weights: number[] = []): number {
91
+ if (weights.length === 0) return -1;
92
+
93
+ let totalWeight = 0;
94
+ for (let weight of weights) {
95
+ totalWeight += weight;
96
+ }
97
+
98
+ if (totalWeight <= 0) console.warn('PRNG.randomIndex()', 'Weights must sum to > 0', totalWeight);
99
+
100
+ let random = this.random(seed) * totalWeight;
101
+ for (let i = 0; i < weights.length; i++) {
102
+ if (random < weights[i]) return i;
103
+ random -= weights[i];
104
+ }
105
+
106
+ return 0;
107
+ }
108
+ }
109
+
110
+ const prng = new PRNG();
111
+
112
+ export default prng;
package/src/utils.ts ADDED
@@ -0,0 +1,106 @@
1
+ // Seeding random number generator
2
+ // https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript
3
+
4
+ /**
5
+ * Produce a 128-bit hash value from a string
6
+ *
7
+ * @param {string} seed
8
+ * @return {number}
9
+ */
10
+ export function cyrb128(seed: string): number[] {
11
+ let h1 = 1779033703;
12
+ let h2 = 3144134277;
13
+ let h3 = 1013904242;
14
+ let h4 = 2773480762;
15
+ for (let i = 0, k; i < seed.length; i++) {
16
+ k = seed.charCodeAt(i);
17
+ h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
18
+ h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
19
+ h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
20
+ h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
21
+ }
22
+ h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
23
+ h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
24
+ h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
25
+ h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
26
+ return [(h1 ^ h2 ^ h3 ^ h4) >>> 0, (h2 ^ h1) >>> 0, (h3 ^ h1) >>> 0, (h4 ^ h1) >>> 0];
27
+ }
28
+
29
+ // ******************************************
30
+ // Pseudo-random Number Generator
31
+ // ******************************************
32
+
33
+ /**
34
+ * Simple Fast Counter, Generator with a 128-bit state
35
+ *
36
+ * @param {number} a
37
+ * @param {number} b
38
+ * @param {number} c
39
+ * @param {number} d
40
+ * @return {Function}
41
+ */
42
+ export function sfc32(a: number, b: number, c: number, d: number): number {
43
+ a >>>= 0;
44
+ b >>>= 0;
45
+ c >>>= 0;
46
+ d >>>= 0;
47
+ let t = (a + b) | 0;
48
+ a = b ^ (b >>> 9);
49
+ b = (c + (c << 3)) | 0;
50
+ c = (c << 21) | (c >>> 11);
51
+ d = (d + 1) | 0;
52
+ t = (t + d) | 0;
53
+ c = (c + t) | 0;
54
+ return (t >>> 0) / 4294967296;
55
+ }
56
+
57
+ /**
58
+ * Mulberry32, Generator with a 32-bit state
59
+ *
60
+ * @param {number} a
61
+ * @return {Function}
62
+ */
63
+ export function mulberry32(a: number): number {
64
+ let t = (a += 0x6d2b79f5);
65
+ t = Math.imul(t ^ (t >>> 15), t | 1);
66
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
67
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
68
+ }
69
+
70
+ /**
71
+ * Jenkins' Small Fast, Generator with a 32-bit state
72
+ *
73
+ * @param {number} a
74
+ * @return {Function}
75
+ */
76
+ export function jsf32(a: number, b: number, c: number, d: number): number {
77
+ a |= 0;
78
+ b |= 0;
79
+ c |= 0;
80
+ d |= 0;
81
+ let t = (a - ((b << 27) | (b >>> 5))) | 0;
82
+ a = b ^ ((c << 17) | (c >>> 15));
83
+ b = (c + d) | 0;
84
+ c = (d + t) | 0;
85
+ d = (a + t) | 0;
86
+ return (d >>> 0) / 4294967296;
87
+ }
88
+
89
+ /**
90
+ * xoshiro128**, Generator with a 128-bit state
91
+ *
92
+ * @param {number} a
93
+ * @return {Function}
94
+ */
95
+ export function xoshiro128ss(a: number, b: number, c: number, d: number): number {
96
+ let t = b << 9;
97
+ let r = a * 5;
98
+ r = ((r << 7) | (r >>> 25)) * 9;
99
+ c ^= a;
100
+ d ^= b;
101
+ b ^= c;
102
+ a ^= d;
103
+ c ^= t;
104
+ d = (d << 11) | (d >>> 21);
105
+ return (r >>> 0) / 4294967296;
106
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "include": ["src"],
3
+ "exclude": ["node_modules"],
4
+ "compilerOptions": {
5
+ "baseUrl": ".",
6
+ "outDir": "lib",
7
+ "target": "es5",
8
+ "module": "commonjs",
9
+ "lib": ["DOM", "ESNext"],
10
+ "allowJs": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "declaration": true,
13
+ "esModuleInterop": true,
14
+ "experimentalDecorators": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "isolatedModules": true,
17
+ "incremental": true,
18
+ "noUnusedParameters": true,
19
+ "noUnusedLocals": true,
20
+ "resolveJsonModule": true,
21
+ "skipLibCheck": true,
22
+ "strict": true
23
+ }
24
+ }