@futdevpro/fsm-dynamo 1.16.29 → 1.16.31
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/build/_modules/game/_collections/audio-mixer.util.d.ts +55 -0
- package/build/_modules/game/_collections/audio-mixer.util.d.ts.map +1 -0
- package/build/_modules/game/_collections/audio-mixer.util.js +114 -0
- package/build/_modules/game/_collections/audio-mixer.util.js.map +1 -0
- package/build/_modules/game/_enums/audio-channel.enum.d.ts +17 -0
- package/build/_modules/game/_enums/audio-channel.enum.d.ts.map +1 -0
- package/build/_modules/game/_enums/audio-channel.enum.js +21 -0
- package/build/_modules/game/_enums/audio-channel.enum.js.map +1 -0
- package/build/_modules/game/_models/audio-mixer-settings.interface.d.ts +23 -0
- package/build/_modules/game/_models/audio-mixer-settings.interface.d.ts.map +1 -0
- package/build/_modules/game/_models/audio-mixer-settings.interface.js +3 -0
- package/build/_modules/game/_models/audio-mixer-settings.interface.js.map +1 -0
- package/build/_modules/game/_models/seeded-random-state.interface.d.ts +17 -0
- package/build/_modules/game/_models/seeded-random-state.interface.d.ts.map +1 -0
- package/build/_modules/game/_models/seeded-random-state.interface.js +3 -0
- package/build/_modules/game/_models/seeded-random-state.interface.js.map +1 -0
- package/build/_modules/game/_models/seeded-random.control-model.d.ts +125 -0
- package/build/_modules/game/_models/seeded-random.control-model.d.ts.map +1 -0
- package/build/_modules/game/_models/seeded-random.control-model.js +263 -0
- package/build/_modules/game/_models/seeded-random.control-model.js.map +1 -0
- package/build/_modules/game/index.d.ts +6 -0
- package/build/_modules/game/index.d.ts.map +1 -0
- package/build/_modules/game/index.js +12 -0
- package/build/_modules/game/index.js.map +1 -0
- package/package.json +12 -3
- package/src/_modules/game/_collections/audio-mixer.util.spec.ts +198 -0
- package/src/_modules/game/_collections/audio-mixer.util.ts +143 -0
- package/src/_modules/game/_enums/audio-channel.enum.ts +18 -0
- package/src/_modules/game/_models/audio-mixer-settings.interface.ts +28 -0
- package/src/_modules/game/_models/seeded-random-state.interface.ts +21 -0
- package/src/_modules/game/_models/seeded-random.control-model.spec.ts +290 -0
- package/src/_modules/game/_models/seeded-random.control-model.ts +311 -0
- package/src/_modules/game/index.ts +10 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { DyFM_Error } from '../../../_models/control-models/error.control-model';
|
|
4
|
+
import { DyFM_RandomWeight } from '../../../_models/interfaces/random-weight.interface';
|
|
5
|
+
|
|
6
|
+
import { DyFM_SeededRandom_State } from './seeded-random-state.interface';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Determinisztikus, **seed-elt** veletlenszam-generator mentheto allapottal.
|
|
11
|
+
*
|
|
12
|
+
* **Miert letezik:** a `DyFM_Random` `Math.random()`-ra epul — gyors es kenyelmes, de
|
|
13
|
+
* NEM reprodukalhato es nincs allapota. A flotta harom jateka ezert epitett maganak sajat
|
|
14
|
+
* PRNG-t (`sm-random.service.ts`, `random-seed.service.ts`, Portalhold `worldSeed`).
|
|
15
|
+
* Ez az osztaly az egysegesitett valtozat.
|
|
16
|
+
*
|
|
17
|
+
* **Algoritmus:** sfc32 (Small Fast Counting) — 128 bites allapot (4x32 bit), kivalo
|
|
18
|
+
* statisztikai minoseg, gyors, es az allapota trivialisan serializalhato. A string-seedet
|
|
19
|
+
* cyrb128 hash-eli 4x32 bitre, tehat `'my-world-seed'` es `42` egyarant ervenyes seed.
|
|
20
|
+
*
|
|
21
|
+
* **Mire jo (a jatekon tul):**
|
|
22
|
+
* - reprodukalhato vilag-generalas es mentheto RNG-allapot,
|
|
23
|
+
* - **determinisztikus e2e/QA** — ugyanaz a seed ugyanazt a jatekmenetet adja,
|
|
24
|
+
* - perf-osszemeres azonos bemeneten,
|
|
25
|
+
* - mock-adat es teszt-fixture stabil generalasa.
|
|
26
|
+
*
|
|
27
|
+
* **Hasznalat:**
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const rng = DyFM_SeededRandom.fromSeed('world-42');
|
|
30
|
+
* const damage: number = rng.nextInt(5, 15);
|
|
31
|
+
* const loot = rng.getRandomWeighted([
|
|
32
|
+
* { chance: 0.7, result: 'common' },
|
|
33
|
+
* { chance: 0.3, result: 'rare' },
|
|
34
|
+
* ]);
|
|
35
|
+
*
|
|
36
|
+
* // mentes
|
|
37
|
+
* save.rngState = rng.getState();
|
|
38
|
+
*
|
|
39
|
+
* // betoltes — a sorozat ott folytatodik, ahol abbamaradt
|
|
40
|
+
* const restored = DyFM_SeededRandom.fromState(save.rngState);
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* **Fontos:** ket kulon instance NEM befolyasolja egymast; ha determinisztikus alrendszereket
|
|
44
|
+
* akarsz (pl. vilag-generalas kulon a harci dobasoktol), adj mindegyiknek sajat instance-ot.
|
|
45
|
+
*/
|
|
46
|
+
export class DyFM_SeededRandom {
|
|
47
|
+
|
|
48
|
+
/** 2^32 — a 32 bites egeszek normalizalasahoz. */
|
|
49
|
+
private static readonly uint32Range: number = 4294967296;
|
|
50
|
+
|
|
51
|
+
/** sfc32 allapot-szo #1. */
|
|
52
|
+
private a: number;
|
|
53
|
+
|
|
54
|
+
/** sfc32 allapot-szo #2. */
|
|
55
|
+
private b: number;
|
|
56
|
+
|
|
57
|
+
/** sfc32 allapot-szo #3. */
|
|
58
|
+
private c: number;
|
|
59
|
+
|
|
60
|
+
/** sfc32 counter-szo. */
|
|
61
|
+
private d: number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Kozvetlen allapotbol epit. A tipikus belepesi pont a `fromSeed()` / `fromState()`
|
|
65
|
+
* statikus factory.
|
|
66
|
+
*/
|
|
67
|
+
constructor(state: DyFM_SeededRandom_State) {
|
|
68
|
+
if (!state || typeof state.a !== 'number' || typeof state.b !== 'number'
|
|
69
|
+
|| typeof state.c !== 'number' || typeof state.d !== 'number') {
|
|
70
|
+
throw new DyFM_Error({
|
|
71
|
+
errorCode: 'DYFM-SEEDED-RANDOM-001',
|
|
72
|
+
message: 'DyFM_SeededRandom: invalid state — all four state words must be numbers',
|
|
73
|
+
additionalContent: { state: state },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.a = state.a | 0;
|
|
78
|
+
this.b = state.b | 0;
|
|
79
|
+
this.c = state.c | 0;
|
|
80
|
+
this.d = state.d | 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generator letrehozasa seed-bol. A seed lehet string (pl. mentett `worldSeed`) vagy szam.
|
|
85
|
+
* Ugyanaz a seed MINDIG ugyanazt a sorozatot adja.
|
|
86
|
+
*/
|
|
87
|
+
static fromSeed(seed: string | number): DyFM_SeededRandom {
|
|
88
|
+
if (typeof seed !== 'string' && typeof seed !== 'number') {
|
|
89
|
+
throw new DyFM_Error({
|
|
90
|
+
errorCode: 'DYFM-SEEDED-RANDOM-002',
|
|
91
|
+
message: 'DyFM_SeededRandom.fromSeed: seed must be a string or a number',
|
|
92
|
+
additionalContent: { seed: seed },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return new DyFM_SeededRandom(this.hashSeed(String(seed)));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Generator letrehozasa korabban elmentett allapotbol (save-betoltes). */
|
|
100
|
+
static fromState(state: DyFM_SeededRandom_State): DyFM_SeededRandom {
|
|
101
|
+
return new DyFM_SeededRandom(state);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* String seed -> 4x32 bites allapot (cyrb128). Determinisztikus es jol szoro:
|
|
106
|
+
* a `'a'` es `'b'` seed teljesen kulonbozo sorozatot ad.
|
|
107
|
+
*/
|
|
108
|
+
private static hashSeed(seed: string): DyFM_SeededRandom_State {
|
|
109
|
+
let h1: number = 1779033703;
|
|
110
|
+
let h2: number = 3144134277;
|
|
111
|
+
let h3: number = 1013904242;
|
|
112
|
+
let h4: number = 2773480762;
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < seed.length; i++) {
|
|
115
|
+
const k: number = seed.charCodeAt(i);
|
|
116
|
+
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
|
|
117
|
+
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
|
|
118
|
+
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
|
|
119
|
+
h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
|
|
123
|
+
h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
|
|
124
|
+
h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
|
|
125
|
+
h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
|
|
126
|
+
|
|
127
|
+
// a 0-allapot degeneralt lenne (az sfc32 megallna) — ezert biztositjuk hogy ne legyen csupa 0
|
|
128
|
+
return {
|
|
129
|
+
a: ((h1 ^ h2 ^ h3 ^ h4) >>> 0) || 1,
|
|
130
|
+
b: ((h2 ^ h1) >>> 0) || 2,
|
|
131
|
+
c: ((h3 ^ h1) >>> 0) || 3,
|
|
132
|
+
d: ((h4 ^ h1) >>> 0) || 4,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** A generator teljes allapotanak masolata — ezt kell a save-payloadba tenni. */
|
|
137
|
+
getState(): DyFM_SeededRandom_State {
|
|
138
|
+
return { a: this.a, b: this.b, c: this.c, d: this.d };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Allapot visszatoltese — a sorozat innentol az elmentett ponton folytatodik. */
|
|
142
|
+
setState(state: DyFM_SeededRandom_State): void {
|
|
143
|
+
if (!state || typeof state.a !== 'number' || typeof state.b !== 'number'
|
|
144
|
+
|| typeof state.c !== 'number' || typeof state.d !== 'number') {
|
|
145
|
+
throw new DyFM_Error({
|
|
146
|
+
errorCode: 'DYFM-SEEDED-RANDOM-003',
|
|
147
|
+
message: 'DyFM_SeededRandom.setState: invalid state — all four state words must be numbers',
|
|
148
|
+
additionalContent: { state: state },
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.a = state.a | 0;
|
|
153
|
+
this.b = state.b | 0;
|
|
154
|
+
this.c = state.c | 0;
|
|
155
|
+
this.d = state.d | 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Fuggetlen masolat az AKTUALIS allapotrol — a klon ugyanazt a sorozatot folytatja,
|
|
160
|
+
* de a ket instance tovabbiakban nem befolyasolja egymast.
|
|
161
|
+
*
|
|
162
|
+
* Tipikus hasznalat: „mi lenne, ha" elore-szimulacio a fo-jatekmenet elrontasa nelkul.
|
|
163
|
+
*/
|
|
164
|
+
clone(): DyFM_SeededRandom {
|
|
165
|
+
return new DyFM_SeededRandom(this.getState());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A kovetkezo veletlen lebegopontos szam a `[0, 1)` tartomanybol (sfc32 mag).
|
|
170
|
+
* Minden mas metodus erre epul.
|
|
171
|
+
*/
|
|
172
|
+
nextFloat(): number {
|
|
173
|
+
this.a |= 0;
|
|
174
|
+
this.b |= 0;
|
|
175
|
+
this.c |= 0;
|
|
176
|
+
this.d |= 0;
|
|
177
|
+
|
|
178
|
+
const t: number = (((this.a + this.b) | 0) + this.d) | 0;
|
|
179
|
+
|
|
180
|
+
this.d = (this.d + 1) | 0;
|
|
181
|
+
this.a = this.b ^ (this.b >>> 9);
|
|
182
|
+
this.b = (this.c + (this.c << 3)) | 0;
|
|
183
|
+
this.c = (this.c << 21) | (this.c >>> 11);
|
|
184
|
+
this.c = (this.c + t) | 0;
|
|
185
|
+
|
|
186
|
+
return (t >>> 0) / DyFM_SeededRandom.uint32Range;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Veletlen egesz a `[min, max]` tartomanybol (mindket veg ZART) — a `DyFM_Random.getRandomInt`
|
|
191
|
+
* seedelt parja. Ha `max < min`, a ket erteket felcsereli.
|
|
192
|
+
*/
|
|
193
|
+
nextInt(min: number, max: number): number {
|
|
194
|
+
const low: number = Math.min(min, max);
|
|
195
|
+
const high: number = Math.max(min, max);
|
|
196
|
+
|
|
197
|
+
return low + Math.floor(this.nextFloat() * (high - low + 1));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** A `DyFM_Random.getRandomInt` API-paritas alias — l. `nextInt`. */
|
|
201
|
+
getRandomInt(min: number, max: number): number {
|
|
202
|
+
return this.nextInt(min, max);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Veletlen lebegopontos szam a `[min, max)` tartomanybol. */
|
|
206
|
+
nextRange(min: number, max: number): number {
|
|
207
|
+
const low: number = Math.min(min, max);
|
|
208
|
+
const high: number = Math.max(min, max);
|
|
209
|
+
|
|
210
|
+
return low + this.nextFloat() * (high - low);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Veletlen logikai ertek. A `chance` a `true` valoszinusege (0..1; default 0.5).
|
|
215
|
+
*/
|
|
216
|
+
nextBool(chance: number = 0.5): boolean {
|
|
217
|
+
return this.nextFloat() < chance;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Veletlen elem a listabol (`null`, ha a lista ures) — a `DyFM_Random.getRandomListElement`
|
|
222
|
+
* seedelt parja.
|
|
223
|
+
*/
|
|
224
|
+
getRandomListElement<T>(list: T[]): T | null {
|
|
225
|
+
if (!list?.length) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return list[this.nextInt(0, list.length - 1)];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Sulyozott veletlen elem (`null`, ha a lista ures) — a `DyFM_Random.getRandomWeighted`
|
|
234
|
+
* seedelt parja.
|
|
235
|
+
*
|
|
236
|
+
* **Elteres a `DyFM_Random`-tol:** ez a valtozat NEM mutalja a bemeneti tombot
|
|
237
|
+
* (a statikus verzio helyben rendez es atirja a `chance` ertekeket).
|
|
238
|
+
*/
|
|
239
|
+
getRandomWeighted<T>(weights: DyFM_RandomWeight<T>[]): T | null {
|
|
240
|
+
if (!weights?.length) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let chanceSum = 0;
|
|
245
|
+
weights.forEach((weight): void => { chanceSum += weight.chance; });
|
|
246
|
+
|
|
247
|
+
if (chanceSum <= 0) {
|
|
248
|
+
return this.getRandomListElement(weights.map((weight): T => weight.result));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const roll: number = this.nextFloat() * chanceSum;
|
|
252
|
+
let cumulated = 0;
|
|
253
|
+
|
|
254
|
+
for (let i = 0; i < weights.length; i++) {
|
|
255
|
+
cumulated += weights[i].chance;
|
|
256
|
+
|
|
257
|
+
if (roll < cumulated) {
|
|
258
|
+
return weights[i].result;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// lebegopontos kerekitesi maradek eseten az utolso elem a nyertes
|
|
263
|
+
return weights[weights.length - 1].result;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Sulyozott veletlen egesz — a `DyFM_Random.getRandomWeightedInt` seedelt parja.
|
|
268
|
+
* Ha a sulyozas nem ad talalatot, `nextInt(min, max)`-ra esik vissza.
|
|
269
|
+
*/
|
|
270
|
+
getRandomWeightedInt(min: number, max: number, weights: DyFM_RandomWeight<number>[]): number {
|
|
271
|
+
const weighted: number | null = this.getRandomWeighted(weights);
|
|
272
|
+
|
|
273
|
+
if (weighted === null) {
|
|
274
|
+
return this.nextInt(min, max);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return weighted;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Fisher-Yates keveres — UJ tombot ad vissza, a bemenetet nem mutalja.
|
|
282
|
+
*/
|
|
283
|
+
shuffle<T>(list: T[]): T[] {
|
|
284
|
+
if (!list?.length) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const result: T[] = list.slice();
|
|
289
|
+
|
|
290
|
+
for (let i = result.length - 1; 0 < i; i--) {
|
|
291
|
+
const j: number = this.nextInt(0, i);
|
|
292
|
+
const swap: T = result[i];
|
|
293
|
+
result[i] = result[j];
|
|
294
|
+
result[j] = swap;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* `count` darab KULONBOZO elem kivalasztasa a listabol (sorrendje veletlen).
|
|
302
|
+
* Ha `count` nagyobb a lista hosszanal, a teljes (megkevert) listat adja vissza.
|
|
303
|
+
*/
|
|
304
|
+
pick<T>(list: T[], count: number): T[] {
|
|
305
|
+
if (!list?.length || count <= 0) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return this.shuffle(list).slice(0, Math.min(count, list.length));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// ENUMS
|
|
2
|
+
export * from './_enums/audio-channel.enum';
|
|
3
|
+
|
|
4
|
+
// MODELS
|
|
5
|
+
export * from './_models/audio-mixer-settings.interface';
|
|
6
|
+
export * from './_models/seeded-random-state.interface';
|
|
7
|
+
export * from './_models/seeded-random.control-model';
|
|
8
|
+
|
|
9
|
+
// COLLECTIONS
|
|
10
|
+
export * from './_collections/audio-mixer.util';
|