novac 2.1.1 → 2.2.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.
- package/LICENSE +1 -1
- package/README.md +0 -0
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +69 -0
- package/examples/math.nv +21 -0
- package/kits/birdAPI/kitdef.js +954 -0
- package/kits/kitRNG/kitdef.js +740 -0
- package/kits/kitSSH/kitdef.js +1272 -0
- package/kits/kitadb/kitdef.js +606 -0
- package/kits/kitai/kitdef.js +2185 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/kitdef.js +2 -0
- package/novac/LICENSE +21 -0
- package/novac/README.md +1823 -0
- package/novac/bin/novac +950 -0
- package/novac/bin/nvc +522 -0
- package/novac/bin/nvml +542 -0
- package/novac/demo.nv +245 -0
- package/novac/demo_builtins.nv +209 -0
- package/novac/demo_http.nv +62 -0
- package/novac/examples/bf.nv +69 -0
- package/novac/examples/math.nv +21 -0
- package/novac/kits/kitai/kitdef.js +2185 -0
- package/novac/kits/kitansi/kitdef.js +1402 -0
- package/novac/kits/kitformat/kitdef.js +1485 -0
- package/novac/kits/kitgps/kitdef.js +1862 -0
- package/novac/kits/kitlibfs/kitdef.js +231 -0
- package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
- package/novac/kits/kitmatrix/ex.js +19 -0
- package/novac/kits/kitmatrix/kitdef.js +960 -0
- package/novac/kits/kitmpatch/kitdef.js +906 -0
- package/novac/kits/kitnovacweb/README.md +1572 -0
- package/novac/kits/kitnovacweb/demo.nv +12 -0
- package/novac/kits/kitnovacweb/demo.nvml +71 -0
- package/novac/kits/kitnovacweb/index.nova +12 -0
- package/novac/kits/kitnovacweb/kitdef.js +692 -0
- package/novac/kits/kitnovacweb/nova.kit.json +8 -0
- package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
- package/novac/kits/kitnovacweb/nvml/index.js +67 -0
- package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
- package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
- package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
- package/novac/kits/kitparse/kitdef.js +1688 -0
- package/novac/kits/kitregex++/kitdef.js +1353 -0
- package/novac/kits/kitrequire/kitdef.js +1599 -0
- package/novac/kits/kitx11/kitdef.js +1 -0
- package/novac/kits/kitx11/kitx11.js +2472 -0
- package/novac/kits/kitx11/kitx11_conn.js +948 -0
- package/novac/kits/kitx11/kitx11_worker.js +121 -0
- package/novac/kits/libterm/ex.js +285 -0
- package/novac/kits/libterm/kitdef.js +1927 -0
- package/novac/node_modules/chalk/license +9 -0
- package/novac/node_modules/chalk/package.json +83 -0
- package/novac/node_modules/chalk/readme.md +297 -0
- package/novac/node_modules/chalk/source/index.d.ts +325 -0
- package/novac/node_modules/chalk/source/index.js +225 -0
- package/novac/node_modules/chalk/source/utilities.js +33 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/novac/node_modules/commander/LICENSE +22 -0
- package/novac/node_modules/commander/Readme.md +1176 -0
- package/novac/node_modules/commander/esm.mjs +16 -0
- package/novac/node_modules/commander/index.js +24 -0
- package/novac/node_modules/commander/lib/argument.js +150 -0
- package/novac/node_modules/commander/lib/command.js +2777 -0
- package/novac/node_modules/commander/lib/error.js +39 -0
- package/novac/node_modules/commander/lib/help.js +747 -0
- package/novac/node_modules/commander/lib/option.js +380 -0
- package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/novac/node_modules/commander/package-support.json +19 -0
- package/novac/node_modules/commander/package.json +82 -0
- package/novac/node_modules/commander/typings/esm.d.mts +3 -0
- package/novac/node_modules/commander/typings/index.d.ts +1113 -0
- package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
- package/novac/node_modules/node-addon-api/README.md +95 -0
- package/novac/node_modules/node-addon-api/common.gypi +21 -0
- package/novac/node_modules/node-addon-api/except.gypi +25 -0
- package/novac/node_modules/node-addon-api/index.js +14 -0
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
- package/novac/node_modules/node-addon-api/napi.h +3364 -0
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
- package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/novac/node_modules/node-addon-api/package-support.json +21 -0
- package/novac/node_modules/node-addon-api/package.json +480 -0
- package/novac/node_modules/node-addon-api/tools/README.md +73 -0
- package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/novac/node_modules/serialize-javascript/LICENSE +27 -0
- package/novac/node_modules/serialize-javascript/README.md +149 -0
- package/novac/node_modules/serialize-javascript/index.js +297 -0
- package/novac/node_modules/serialize-javascript/package.json +33 -0
- package/novac/package.json +27 -0
- package/novac/scripts/update-bin.js +24 -0
- package/novac/src/core/bstd.js +1035 -0
- package/novac/src/core/config.js +155 -0
- package/novac/src/core/describe.js +187 -0
- package/novac/src/core/emitter.js +499 -0
- package/novac/src/core/error.js +86 -0
- package/novac/src/core/executor.js +5606 -0
- package/novac/src/core/formatter.js +686 -0
- package/novac/src/core/lexer.js +1026 -0
- package/novac/src/core/nova_builtins.js +717 -0
- package/novac/src/core/nova_thread_worker.js +166 -0
- package/novac/src/core/parser.js +2181 -0
- package/novac/src/core/types.js +112 -0
- package/novac/src/index.js +28 -0
- package/novac/src/runtime/stdlib.js +244 -0
- package/package.json +3 -2
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +835 -361
- package/src/core/executor.js +427 -246
- package/src/core/lexer.js +19 -2
- package/src/core/parser.js +13 -0
- package/src/index.js +0 -0
- package/examples/example-project/README.md +0 -3
- package/examples/example-project/src/main.nova +0 -3
- package/src/core/environment.js +0 -0
- /package/{kits → novac/kits}/libtea/tf.js +0 -0
- /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
// kitdef.js — kitRandom
|
|
2
|
+
// Comprehensive random number generation, sampling, shuffling, weighted
|
|
3
|
+
// selection, seeded RNG, distributions, ID generation, geometry, and more.
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
kitdef: {
|
|
7
|
+
|
|
8
|
+
name: "kitRandom",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
description: "Comprehensive random utilities: floats, ints, booleans, sampling, shuffling, weighted picks, seeded RNG, distributions, ID generation, geometry, and dice.",
|
|
11
|
+
|
|
12
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
13
|
+
// BASIC FLOATS & INTS
|
|
14
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/** Random float in [0, 1) */
|
|
17
|
+
float01() {
|
|
18
|
+
return Math.random();
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Random float in [min, max).
|
|
23
|
+
* If only one argument, returns float in [0, min).
|
|
24
|
+
*/
|
|
25
|
+
float(min, max) {
|
|
26
|
+
if (min === undefined) return Math.random();
|
|
27
|
+
if (max === undefined) { max = min; min = 0; }
|
|
28
|
+
return min + Math.random() * (max - min);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/** Random float in [min, max], inclusive on both ends. */
|
|
32
|
+
floatInclusive(min, max) {
|
|
33
|
+
return min + Math.random() * (max - min + Number.EPSILON);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Random integer in [min, max], inclusive on both ends.
|
|
38
|
+
* If only one argument, returns int in [0, min].
|
|
39
|
+
*/
|
|
40
|
+
int(min, max) {
|
|
41
|
+
if (max === undefined) { max = min; min = 0; }
|
|
42
|
+
min = Math.ceil(min);
|
|
43
|
+
max = Math.floor(max);
|
|
44
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Random integer in [min, max), exclusive of max.
|
|
49
|
+
*/
|
|
50
|
+
intExclusive(min, max) {
|
|
51
|
+
if (max === undefined) { max = min; min = 0; }
|
|
52
|
+
min = Math.ceil(min);
|
|
53
|
+
max = Math.floor(max);
|
|
54
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/** Random sign: returns 1 or -1. */
|
|
58
|
+
sign() {
|
|
59
|
+
return Math.random() < 0.5 ? 1 : -1;
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/** Random number rounded to a given number of decimal places. */
|
|
63
|
+
floatRounded(min, max, decimals = 2) {
|
|
64
|
+
const f = this.float(min, max);
|
|
65
|
+
return parseFloat(f.toFixed(decimals));
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
69
|
+
// BOOLEANS
|
|
70
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/** Random boolean, 50/50. */
|
|
73
|
+
bool() {
|
|
74
|
+
return Math.random() < 0.5;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Random boolean with a given probability of being true.
|
|
79
|
+
* @param {number} p - Probability in [0, 1]. Default 0.5.
|
|
80
|
+
*/
|
|
81
|
+
chance(p = 0.5) {
|
|
82
|
+
return Math.random() < p;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/** Roll a 1-in-n chance. Returns true if it hits. */
|
|
86
|
+
oneIn(n) {
|
|
87
|
+
return Math.floor(Math.random() * n) === 0;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
91
|
+
// ARRAY SAMPLING & SHUFFLING
|
|
92
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Pick one random item from an array.
|
|
96
|
+
* Returns undefined for empty arrays.
|
|
97
|
+
*/
|
|
98
|
+
pick(arr) {
|
|
99
|
+
if (!arr || arr.length === 0) return undefined;
|
|
100
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Pick k unique random items from an array (no replacement).
|
|
105
|
+
* Returns a new array of length min(k, arr.length).
|
|
106
|
+
*/
|
|
107
|
+
sample(arr, k) {
|
|
108
|
+
if (!arr || arr.length === 0) return [];
|
|
109
|
+
k = Math.min(k, arr.length);
|
|
110
|
+
const copy = arr.slice();
|
|
111
|
+
const result = [];
|
|
112
|
+
for (let i = 0; i < k; i++) {
|
|
113
|
+
const j = Math.floor(Math.random() * (copy.length - i)) + i;
|
|
114
|
+
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
115
|
+
result.push(copy[i]);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Pick k items from an array with replacement (duplicates allowed).
|
|
122
|
+
*/
|
|
123
|
+
sampleWithReplacement(arr, k) {
|
|
124
|
+
if (!arr || arr.length === 0) return [];
|
|
125
|
+
const result = [];
|
|
126
|
+
for (let i = 0; i < k; i++) {
|
|
127
|
+
result.push(arr[Math.floor(Math.random() * arr.length)]);
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Shuffle an array in-place using Fisher-Yates.
|
|
134
|
+
* Returns the same array.
|
|
135
|
+
*/
|
|
136
|
+
shuffle(arr) {
|
|
137
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
138
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
139
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
140
|
+
}
|
|
141
|
+
return arr;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Return a shuffled copy of an array without mutating the original.
|
|
146
|
+
*/
|
|
147
|
+
shuffled(arr) {
|
|
148
|
+
return this.shuffle(arr.slice());
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Pick a random key from an object.
|
|
153
|
+
*/
|
|
154
|
+
pickKey(obj) {
|
|
155
|
+
const keys = Object.keys(obj);
|
|
156
|
+
return keys[Math.floor(Math.random() * keys.length)];
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Pick a random value from an object.
|
|
161
|
+
*/
|
|
162
|
+
pickValue(obj) {
|
|
163
|
+
const values = Object.values(obj);
|
|
164
|
+
return values[Math.floor(Math.random() * values.length)];
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Pick a random entry [key, value] from an object.
|
|
169
|
+
*/
|
|
170
|
+
pickEntry(obj) {
|
|
171
|
+
const entries = Object.entries(obj);
|
|
172
|
+
return entries[Math.floor(Math.random() * entries.length)];
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
176
|
+
// WEIGHTED SELECTION
|
|
177
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Pick an item by weight. Weights do not need to sum to 1.
|
|
181
|
+
* @param {Array} items - Array of items to pick from.
|
|
182
|
+
* @param {Array} weights - Parallel array of numeric weights (>= 0).
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* weighted(["common","rare","legendary"], [100, 10, 1])
|
|
186
|
+
*/
|
|
187
|
+
weighted(items, weights) {
|
|
188
|
+
if (items.length !== weights.length) throw new Error("items and weights must be the same length");
|
|
189
|
+
const total = weights.reduce((s, w) => s + w, 0);
|
|
190
|
+
if (total <= 0) throw new Error("weights must sum to a positive number");
|
|
191
|
+
let r = Math.random() * total;
|
|
192
|
+
for (let i = 0; i < items.length; i++) {
|
|
193
|
+
r -= weights[i];
|
|
194
|
+
if (r <= 0) return items[i];
|
|
195
|
+
}
|
|
196
|
+
return items[items.length - 1];
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Pick from an array of { item, weight } objects.
|
|
201
|
+
* @example
|
|
202
|
+
* weightedObjects([{ item: "sword", weight: 5 }, { item: "shield", weight: 3 }])
|
|
203
|
+
*/
|
|
204
|
+
weightedObjects(entries) {
|
|
205
|
+
return this.weighted(entries.map(e => e.item), entries.map(e => e.weight));
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Pick multiple items by weight, with replacement.
|
|
210
|
+
*/
|
|
211
|
+
weightedSample(items, weights, k) {
|
|
212
|
+
const result = [];
|
|
213
|
+
for (let i = 0; i < k; i++) result.push(this.weighted(items, weights));
|
|
214
|
+
return result;
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
218
|
+
// DISTRIBUTIONS
|
|
219
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Normal (Gaussian) distribution using Box-Muller transform.
|
|
223
|
+
* @param {number} mean - Default 0.
|
|
224
|
+
* @param {number} stddev - Default 1.
|
|
225
|
+
*/
|
|
226
|
+
gaussian(mean = 0, stddev = 1) {
|
|
227
|
+
let u, v;
|
|
228
|
+
do { u = Math.random(); } while (u === 0);
|
|
229
|
+
do { v = Math.random(); } while (v === 0);
|
|
230
|
+
return mean + Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v) * stddev;
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Gaussian clamped to [min, max] by resampling until in range.
|
|
235
|
+
*/
|
|
236
|
+
gaussianClamped(mean, stddev, min, max) {
|
|
237
|
+
let v;
|
|
238
|
+
do { v = this.gaussian(mean, stddev); } while (v < min || v > max);
|
|
239
|
+
return v;
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Exponential distribution: models time between events.
|
|
244
|
+
* @param {number} lambda - Rate parameter (default 1). Mean = 1/lambda.
|
|
245
|
+
*/
|
|
246
|
+
exponential(lambda = 1) {
|
|
247
|
+
return -Math.log(1 - Math.random()) / lambda;
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Triangular distribution between min and max with a peak at mode.
|
|
252
|
+
* Useful for estimates (optimistic/pessimistic/likely).
|
|
253
|
+
*/
|
|
254
|
+
triangular(min, mode, max) {
|
|
255
|
+
const u = Math.random();
|
|
256
|
+
const fc = (mode - min) / (max - min);
|
|
257
|
+
if (u < fc) return min + Math.sqrt(u * (max - min) * (mode - min));
|
|
258
|
+
return max - Math.sqrt((1 - u) * (max - min) * (max - mode));
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Bernoulli trial: returns 1 with probability p, else 0.
|
|
263
|
+
*/
|
|
264
|
+
bernoulli(p = 0.5) {
|
|
265
|
+
return Math.random() < p ? 1 : 0;
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Binomial: number of successes in n Bernoulli trials with probability p.
|
|
270
|
+
*/
|
|
271
|
+
binomial(n, p) {
|
|
272
|
+
let s = 0;
|
|
273
|
+
for (let i = 0; i < n; i++) if (Math.random() < p) s++;
|
|
274
|
+
return s;
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Poisson distribution: number of events in a fixed interval.
|
|
279
|
+
* @param {number} lambda - Expected number of events (mean).
|
|
280
|
+
*/
|
|
281
|
+
poisson(lambda) {
|
|
282
|
+
const L = Math.exp(-lambda);
|
|
283
|
+
let k = 0, p = 1;
|
|
284
|
+
do { k++; p *= Math.random(); } while (p > L);
|
|
285
|
+
return k - 1;
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Beta distribution via Johnk's method.
|
|
290
|
+
* Useful for modeling probabilities and proportions.
|
|
291
|
+
* @param {number} alpha
|
|
292
|
+
* @param {number} beta
|
|
293
|
+
*/
|
|
294
|
+
beta(alpha, beta) {
|
|
295
|
+
const x = this.gamma(alpha, 1);
|
|
296
|
+
const y = this.gamma(beta, 1);
|
|
297
|
+
return x / (x + y);
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Gamma distribution using Marsaglia-Tsang's method.
|
|
302
|
+
* @param {number} shape (k)
|
|
303
|
+
* @param {number} scale (theta) - Default 1.
|
|
304
|
+
*/
|
|
305
|
+
gamma(shape, scale = 1) {
|
|
306
|
+
if (shape < 1) return this.gamma(1 + shape, scale) * Math.pow(Math.random(), 1 / shape);
|
|
307
|
+
const d = shape - 1 / 3;
|
|
308
|
+
const c = 1 / Math.sqrt(9 * d);
|
|
309
|
+
while (true) {
|
|
310
|
+
let x, v;
|
|
311
|
+
do { x = this.gaussian(); v = 1 + c * x; } while (v <= 0);
|
|
312
|
+
v = v * v * v;
|
|
313
|
+
const u = Math.random();
|
|
314
|
+
if (u < 1 - 0.0331 * (x * x) * (x * x)) return d * v * scale;
|
|
315
|
+
if (Math.log(u) < 0.5 * x * x + d * (1 - v + Math.log(v))) return d * v * scale;
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Pareto distribution.
|
|
321
|
+
* @param {number} scale - Minimum value (xm).
|
|
322
|
+
* @param {number} shape - Shape parameter (alpha).
|
|
323
|
+
*/
|
|
324
|
+
pareto(scale, shape) {
|
|
325
|
+
return scale / Math.pow(Math.random(), 1 / shape);
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Log-normal distribution.
|
|
330
|
+
* @param {number} mean - Mean of the underlying normal.
|
|
331
|
+
* @param {number} stddev - Std dev of the underlying normal.
|
|
332
|
+
*/
|
|
333
|
+
logNormal(mean = 0, stddev = 1) {
|
|
334
|
+
return Math.exp(this.gaussian(mean, stddev));
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Cauchy distribution (heavy-tailed).
|
|
339
|
+
* @param {number} location - Default 0.
|
|
340
|
+
* @param {number} scale - Default 1.
|
|
341
|
+
*/
|
|
342
|
+
cauchy(location = 0, scale = 1) {
|
|
343
|
+
return location + scale * Math.tan(Math.PI * (Math.random() - 0.5));
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
347
|
+
// SEEDED RNG (Mulberry32)
|
|
348
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Create a seeded pseudo-random number generator (Mulberry32).
|
|
352
|
+
* Deterministic: same seed → same sequence every time.
|
|
353
|
+
* @param {number|string} seed
|
|
354
|
+
* @returns {object} A seeded RNG with the same API surface.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* const rng = kitRandom.seeded(42);
|
|
358
|
+
* rng.float(0, 10);
|
|
359
|
+
* rng.pick(["a", "b", "c"]);
|
|
360
|
+
*/
|
|
361
|
+
seeded(seed) {
|
|
362
|
+
if (typeof seed === "string") {
|
|
363
|
+
seed = Array.from(seed).reduce((h, c) => Math.imul(31, h) + c.charCodeAt(0) | 0, 0);
|
|
364
|
+
}
|
|
365
|
+
let s = seed >>> 0;
|
|
366
|
+
|
|
367
|
+
const next = () => {
|
|
368
|
+
s += 0x6d2b79f5;
|
|
369
|
+
let t = s;
|
|
370
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
371
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
372
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
_seed: seed,
|
|
377
|
+
_next: next,
|
|
378
|
+
float01: () => next(),
|
|
379
|
+
float: (min, max) => { if (max===undefined){max=min;min=0;} return min + next()*(max-min); },
|
|
380
|
+
int: (min, max) => { if (max===undefined){max=min;min=0;} return Math.floor(next()*(Math.floor(max)-Math.ceil(min)+1))+Math.ceil(min); },
|
|
381
|
+
bool: () => next() < 0.5,
|
|
382
|
+
chance: (p = 0.5) => next() < p,
|
|
383
|
+
sign: () => next() < 0.5 ? 1 : -1,
|
|
384
|
+
pick: (arr) => arr[Math.floor(next() * arr.length)],
|
|
385
|
+
shuffle(arr) {
|
|
386
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
387
|
+
const j = Math.floor(next() * (i + 1));
|
|
388
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
389
|
+
}
|
|
390
|
+
return arr;
|
|
391
|
+
},
|
|
392
|
+
shuffled(arr) { return this.shuffle(arr.slice()); },
|
|
393
|
+
gaussian(mean = 0, stddev = 1) {
|
|
394
|
+
let u, v;
|
|
395
|
+
do { u = next(); } while (u === 0);
|
|
396
|
+
do { v = next(); } while (v === 0);
|
|
397
|
+
return mean + Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v) * stddev;
|
|
398
|
+
},
|
|
399
|
+
weighted(items, weights) {
|
|
400
|
+
const total = weights.reduce((s, w) => s + w, 0);
|
|
401
|
+
let r = next() * total;
|
|
402
|
+
for (let i = 0; i < items.length; i++) { r -= weights[i]; if (r <= 0) return items[i]; }
|
|
403
|
+
return items[items.length - 1];
|
|
404
|
+
},
|
|
405
|
+
uuid() {
|
|
406
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
|
|
407
|
+
const r = Math.floor(next() * 16);
|
|
408
|
+
return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
|
|
409
|
+
});
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
415
|
+
// ID & TOKEN GENERATION
|
|
416
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Generate a UUID v4 string.
|
|
420
|
+
* Uses crypto.randomUUID() if available, falls back to Math.random().
|
|
421
|
+
*/
|
|
422
|
+
uuid() {
|
|
423
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
|
|
424
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
|
|
425
|
+
const r = Math.random() * 16 | 0;
|
|
426
|
+
return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Generate a short random ID (base62, URL-safe).
|
|
432
|
+
* @param {number} length - Default 8.
|
|
433
|
+
* @example nanoid(12) => "aB3kZ9xQ2mLp"
|
|
434
|
+
*/
|
|
435
|
+
nanoid(length = 8) {
|
|
436
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
437
|
+
let id = "";
|
|
438
|
+
for (let i = 0; i < length; i++) id += chars[Math.floor(Math.random() * chars.length)];
|
|
439
|
+
return id;
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Generate a random hex string of a given byte length.
|
|
444
|
+
* @param {number} bytes - Default 16 (128-bit).
|
|
445
|
+
* @example hex(4) => "a3f2c1b0"
|
|
446
|
+
*/
|
|
447
|
+
hex(bytes = 16) {
|
|
448
|
+
let h = "";
|
|
449
|
+
for (let i = 0; i < bytes; i++) h += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
|
|
450
|
+
return h;
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Generate a random string from a custom alphabet.
|
|
455
|
+
* @param {number} length
|
|
456
|
+
* @param {string} [alphabet]
|
|
457
|
+
*/
|
|
458
|
+
string(length, alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") {
|
|
459
|
+
let s = "";
|
|
460
|
+
for (let i = 0; i < length; i++) s += alphabet[Math.floor(Math.random() * alphabet.length)];
|
|
461
|
+
return s;
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Generate a random numeric PIN of a given length.
|
|
466
|
+
* @param {number} length - Default 4.
|
|
467
|
+
*/
|
|
468
|
+
pin(length = 4) {
|
|
469
|
+
return this.string(length, "0123456789");
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Generate a random color in hex format.
|
|
474
|
+
* @example color() => "#a3f2c1"
|
|
475
|
+
*/
|
|
476
|
+
color() {
|
|
477
|
+
return `#${this.hex(3)}`;
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Generate a random RGB color object.
|
|
482
|
+
* @returns {{ r, g, b }}
|
|
483
|
+
*/
|
|
484
|
+
rgb() {
|
|
485
|
+
return { r: this.int(0, 255), g: this.int(0, 255), b: this.int(0, 255) };
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
489
|
+
// GEOMETRY & SPATIAL
|
|
490
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Random point on the surface of a unit circle.
|
|
494
|
+
* Returns { x, y } with x²+y²=1.
|
|
495
|
+
*/
|
|
496
|
+
onUnitCircle() {
|
|
497
|
+
const a = Math.random() * 2 * Math.PI;
|
|
498
|
+
return { x: Math.cos(a), y: Math.sin(a) };
|
|
499
|
+
},
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Random point inside a unit disk (uniform, not center-biased).
|
|
503
|
+
* Returns { x, y }.
|
|
504
|
+
*/
|
|
505
|
+
inUnitDisk() {
|
|
506
|
+
let x, y;
|
|
507
|
+
do { x = Math.random() * 2 - 1; y = Math.random() * 2 - 1; } while (x * x + y * y >= 1);
|
|
508
|
+
return { x, y };
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Random point on the surface of a unit sphere.
|
|
513
|
+
* Returns { x, y, z } with x²+y²+z²=1.
|
|
514
|
+
*/
|
|
515
|
+
onUnitSphere() {
|
|
516
|
+
const u = Math.random() * 2 - 1;
|
|
517
|
+
const t = Math.random() * 2 * Math.PI;
|
|
518
|
+
const r = Math.sqrt(1 - u * u);
|
|
519
|
+
return { x: r * Math.cos(t), y: r * Math.sin(t), z: u };
|
|
520
|
+
},
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Random point uniformly inside an axis-aligned bounding box.
|
|
524
|
+
* @param {{ x?: number, y?: number, w: number, h: number }} rect
|
|
525
|
+
*/
|
|
526
|
+
inRect({ x = 0, y = 0, w, h }) {
|
|
527
|
+
return { x: x + Math.random() * w, y: y + Math.random() * h };
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Random point inside an axis-aligned bounding box in 3D.
|
|
532
|
+
* @param {{ x?: number, y?: number, z?: number, w: number, h: number, d: number }} box
|
|
533
|
+
*/
|
|
534
|
+
inBox({ x = 0, y = 0, z = 0, w, h, d }) {
|
|
535
|
+
return { x: x + Math.random() * w, y: y + Math.random() * h, z: z + Math.random() * d };
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Random angle in radians in [0, 2π).
|
|
540
|
+
*/
|
|
541
|
+
angle() {
|
|
542
|
+
return Math.random() * 2 * Math.PI;
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Random direction vector in 2D (unit length).
|
|
547
|
+
* @returns {{ x, y }}
|
|
548
|
+
*/
|
|
549
|
+
direction2D() {
|
|
550
|
+
return this.onUnitCircle();
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Random direction vector in 3D (unit length).
|
|
555
|
+
* @returns {{ x, y, z }}
|
|
556
|
+
*/
|
|
557
|
+
direction3D() {
|
|
558
|
+
return this.onUnitSphere();
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
562
|
+
// DICE & GAMES
|
|
563
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Roll dice.
|
|
567
|
+
* @param {number} count - Number of dice.
|
|
568
|
+
* @param {number} sides - Sides per die.
|
|
569
|
+
* @returns {{ rolls: number[], total: number }}
|
|
570
|
+
* @example dice(2, 6) => { rolls: [3, 5], total: 8 }
|
|
571
|
+
*/
|
|
572
|
+
dice(count, sides) {
|
|
573
|
+
const rolls = Array.from({ length: count }, () => this.int(1, sides));
|
|
574
|
+
return { rolls, total: rolls.reduce((s, v) => s + v, 0) };
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Parse and roll standard dice notation strings.
|
|
579
|
+
* Supports: "2d6", "1d20+5", "4d6k3" (keep highest 3), "2d8-1".
|
|
580
|
+
* @returns {{ rolls, kept, modifier, total }}
|
|
581
|
+
*
|
|
582
|
+
* @example
|
|
583
|
+
* rollNotation("4d6k3") // roll 4d6, keep highest 3
|
|
584
|
+
* rollNotation("1d20+5") // roll 1d20 and add 5
|
|
585
|
+
*/
|
|
586
|
+
rollNotation(notation) {
|
|
587
|
+
const m = notation.toLowerCase().match(/^(\d+)d(\d+)(?:k(\d+))?([+-]\d+)?$/);
|
|
588
|
+
if (!m) throw new Error(`Invalid dice notation: "${notation}"`);
|
|
589
|
+
const [, count, sides, keep, mod] = m;
|
|
590
|
+
const { rolls } = this.dice(Number(count), Number(sides));
|
|
591
|
+
const sorted = rolls.slice().sort((a, b) => b - a);
|
|
592
|
+
const kept = keep ? sorted.slice(0, Number(keep)) : sorted;
|
|
593
|
+
const modifier = mod ? Number(mod) : 0;
|
|
594
|
+
return { rolls, kept, modifier, total: kept.reduce((s, v) => s + v, 0) + modifier };
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Flip a coin. Returns "heads" or "tails".
|
|
599
|
+
*/
|
|
600
|
+
coin() {
|
|
601
|
+
return Math.random() < 0.5 ? "heads" : "tails";
|
|
602
|
+
},
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Draw a random card from a standard 52-card deck.
|
|
606
|
+
* @returns {{ suit, rank, value, card }}
|
|
607
|
+
*/
|
|
608
|
+
drawCard() {
|
|
609
|
+
const suits = ["♠", "♥", "♦", "♣"];
|
|
610
|
+
const ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
|
|
611
|
+
const suit = suits[Math.floor(Math.random() * 4)];
|
|
612
|
+
const rank = ranks[Math.floor(Math.random() * 13)];
|
|
613
|
+
const value = rank === "A" ? 11 : ["J","Q","K"].includes(rank) ? 10 : parseInt(rank);
|
|
614
|
+
return { suit, rank, value, card: `${rank}${suit}` };
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Shuffle and return a full 52-card deck.
|
|
619
|
+
* @returns {Array<{ suit, rank, value, card }>}
|
|
620
|
+
*/
|
|
621
|
+
shuffleDeck() {
|
|
622
|
+
const suits = ["♠", "♥", "♦", "♣"];
|
|
623
|
+
const ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
|
|
624
|
+
const deck = [];
|
|
625
|
+
for (const suit of suits) {
|
|
626
|
+
for (const rank of ranks) {
|
|
627
|
+
const value = rank === "A" ? 11 : ["J","Q","K"].includes(rank) ? 10 : parseInt(rank);
|
|
628
|
+
deck.push({ suit, rank, value, card: `${rank}${suit}` });
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return this.shuffle(deck);
|
|
632
|
+
},
|
|
633
|
+
|
|
634
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
635
|
+
// UTILITIES
|
|
636
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Randomly split a value into n parts that sum to total.
|
|
640
|
+
* Returns an array of n non-negative floats.
|
|
641
|
+
* @example split(100, 3) => [42.1, 31.7, 26.2]
|
|
642
|
+
*/
|
|
643
|
+
split(total, n) {
|
|
644
|
+
const cuts = Array.from({ length: n - 1 }, () => Math.random() * total).sort((a, b) => a - b);
|
|
645
|
+
const parts = [];
|
|
646
|
+
let prev = 0;
|
|
647
|
+
for (const c of cuts) { parts.push(c - prev); prev = c; }
|
|
648
|
+
parts.push(total - prev);
|
|
649
|
+
return parts;
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Randomly split a value into n integer parts that sum to total.
|
|
654
|
+
*/
|
|
655
|
+
splitInt(total, n) {
|
|
656
|
+
const parts = this.split(total, n).map(Math.round);
|
|
657
|
+
const diff = total - parts.reduce((s, v) => s + v, 0);
|
|
658
|
+
parts[0] += diff;
|
|
659
|
+
return parts;
|
|
660
|
+
},
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Jitter a value by up to ±amount.
|
|
664
|
+
* @example jitter(100, 5) => somewhere in [95, 105]
|
|
665
|
+
*/
|
|
666
|
+
jitter(value, amount) {
|
|
667
|
+
return value + (Math.random() * 2 - 1) * amount;
|
|
668
|
+
},
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Randomly perturb each element of an array by up to ±amount.
|
|
672
|
+
*/
|
|
673
|
+
jitterArray(arr, amount) {
|
|
674
|
+
return arr.map(v => this.jitter(v, amount));
|
|
675
|
+
},
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Choose a random date between two dates.
|
|
679
|
+
* @param {Date|string} start
|
|
680
|
+
* @param {Date|string} end
|
|
681
|
+
* @returns {Date}
|
|
682
|
+
*/
|
|
683
|
+
date(start, end) {
|
|
684
|
+
const s = new Date(start).getTime();
|
|
685
|
+
const e = new Date(end).getTime();
|
|
686
|
+
return new Date(s + Math.random() * (e - s));
|
|
687
|
+
},
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Pick a random element from a Set.
|
|
691
|
+
* @param {Set} set
|
|
692
|
+
*/
|
|
693
|
+
pickFromSet(set) {
|
|
694
|
+
const arr = Array.from(set);
|
|
695
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
696
|
+
},
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Pick a random element from a Map (returns [key, value]).
|
|
700
|
+
* @param {Map} map
|
|
701
|
+
*/
|
|
702
|
+
pickFromMap(map) {
|
|
703
|
+
const entries = Array.from(map.entries());
|
|
704
|
+
return entries[Math.floor(Math.random() * entries.length)];
|
|
705
|
+
},
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Generate a random IPv4 address.
|
|
709
|
+
*/
|
|
710
|
+
ipv4() {
|
|
711
|
+
return Array.from({ length: 4 }, () => this.int(0, 255)).join(".");
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Generate a random MAC address.
|
|
716
|
+
*/
|
|
717
|
+
mac() {
|
|
718
|
+
return Array.from({ length: 6 }, () => this.hex(1).padStart(2, "0")).join(":");
|
|
719
|
+
},
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Randomly rotate an array by a random amount.
|
|
723
|
+
* @param {Array} arr
|
|
724
|
+
* @returns {Array}
|
|
725
|
+
*/
|
|
726
|
+
rotate(arr) {
|
|
727
|
+
const n = this.int(0, arr.length - 1);
|
|
728
|
+
return [...arr.slice(n), ...arr.slice(0, n)];
|
|
729
|
+
},
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Return true with probability 1/n, simulating a random event trigger.
|
|
733
|
+
* Alias for oneIn() with more semantic naming.
|
|
734
|
+
*/
|
|
735
|
+
trigger(n) {
|
|
736
|
+
return this.oneIn(n);
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
}
|
|
740
|
+
};
|