From c9df92ba6bc5e1e0c84698917d8b12de6731e962 Mon Sep 17 00:00:00 2001 From: Ricardo Matias Date: Wed, 28 Sep 2022 11:51:10 +0200 Subject: [PATCH] feat(tools): add new methods to random and fix noise generation --- lib/tools/random.ts | 62 +++++++++++------ test/tools/random.spec.ts | 139 ++++++++++++++++++++++++++------------ 2 files changed, 139 insertions(+), 62 deletions(-) diff --git a/lib/tools/random.ts b/lib/tools/random.ts index 5f1f912..6ff7402 100644 --- a/lib/tools/random.ts +++ b/lib/tools/random.ts @@ -1,13 +1,13 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable no-invalid-this */ /* eslint-disable no-var, new-cap */ -/* eslint-disable @typescript-eslint/ban-ts-comment */ import Alea from 'alea'; -import SimplexNoise from 'simplex-noise'; +import { NoiseFunction2D, createNoise2D } from 'simplex-noise'; import { PlayaError } from '../utils'; const INITIAL_SEED = 'PLAYA'; const INITIAL_X = 0; -const INITIAL_Y = 999; +const INITIAL_INCREMENT = 10; /** * It uses Simplex Noise in order for the Random numbers to follow a "more natural" progression. @@ -25,17 +25,17 @@ const INITIAL_Y = 999; export class Random { private static instance: Random; #x = INITIAL_X; - #y = INITIAL_Y; #prevX = 0; - #prevY = 0; - /*@ts-expect-error */ + // @ts-ignore #rng = new Alea(INITIAL_SEED); - #simplex: SimplexNoise = new SimplexNoise(this.#rng); + #simplex: NoiseFunction2D = createNoise2D(this.#rng); #seed: string | number = INITIAL_SEED; + #prevSeed: string | number = INITIAL_SEED; + #prevIncrement = 0; #state?: [number, number, number, number]; #seedCounter = 0; - public increment = 10; + public increment = INITIAL_INCREMENT; public static getInstance(): Random { if (!Random.instance) { @@ -83,9 +83,10 @@ export class Random { push(): void { this.#state = this.#rng.exportState(); this.#prevX = this.#x; - this.#prevY = this.#y; + this.#prevSeed = this.#seed; + this.#prevIncrement = this.increment; - this.setSeed(this.seed, this.increment); + this.setSeed(this.#seed, this.increment); } /** @@ -94,17 +95,22 @@ export class Random { * @memberof Tools.Random */ pop(): void { + this.#seed = this.#prevSeed; + this.increment = this.#prevIncrement; this.#x = this.#prevX; - this.#y = this.#prevY; - this.#prevX = 0; - this.#prevY = 0; if (this.#state) { + // @ts-ignore this.#rng.importState(this.#state); - this.#simplex = new SimplexNoise(this.#rng); + // this.#simplex = createNoise2D(this.#rng); } else { throw new PlayaError('Random', 'Must use .push() before .pop()'); } + + this.#prevX = 0; + this.#state = undefined; + this.#prevIncrement = 0; + this.#prevSeed = INITIAL_SEED; } /** @@ -117,12 +123,31 @@ export class Random { */ setSeed = (seed: string | number, increment: number = this.increment): void => { this.#x = 0; - this.#y = 999; this.#seed = seed; this.increment = increment; - /*@ts-ignore */ + // @ts-ignore this.#rng = new Alea(seed); - this.#simplex = new SimplexNoise(this.#rng); + this.#simplex = createNoise2D(this.#rng); + }; + + /** + * Creates a fresh seed based + * @function freshSeed + * @memberof Tools.Random + * + * @param {Number} increment + */ + freshSeed = (increment: number = this.increment): void => { + this.setSeed(Math.floor(Math.random() * 9999999).toString(), increment); + }; + + /** + * Resets to initial state with the defaults + * @function reset + * @memberof Tools.Random + */ + reset = () => { + this.setSeed(INITIAL_SEED, INITIAL_INCREMENT); }; /** @@ -153,9 +178,8 @@ export class Random { */ float = (max = 1.0, min = 0.0): number => { this.#x += this.increment; - this.#y += this.increment; - const value = (this.#simplex.noise2D(this.#x, this.#y) + 1) / 2; + const value = (this.#simplex(this.#x, 1.0) + 1) / 2; const result = min + value * (max - min); return result; diff --git a/test/tools/random.spec.ts b/test/tools/random.spec.ts index 406fc64..af7c197 100644 --- a/test/tools/random.spec.ts +++ b/test/tools/random.spec.ts @@ -2,6 +2,8 @@ import random, { Random } from '../../lib/tools/random'; import { mapRepeat } from '../../lib/tools/map-repeat'; describe('A Random test suite', () => { + beforeEach(() => random.reset()); + it('should return singleton', () => { const result = mapRepeat(10, () => { random.setSeed('test'); @@ -15,43 +17,87 @@ describe('A Random test suite', () => { it('should create new random int', () => { random.setSeed('test'); - const results = mapRepeat(10, () => random.int(10, 5)); + const results = mapRepeat(10, () => random.int(20, 10)); - expect(results.filter((n) => n >= 5 && n <= 10)).toHaveLength(10); + expect(results.filter((n) => n >= 10 && n <= 20)).toHaveLength(10); expect(results).toMatchInlineSnapshot(` - Array [ - 7, - 6, - 6, - 7, - 5, - 6, - 6, - 8, - 8, - 8, + [ + 15, + 10, + 12, + 15, + 17, + 15, + 15, + 17, + 15, + 15, + ] + `); + }); + + it('should create new random int small increment', () => { + random.setSeed('test', 0.1); + + const results = mapRepeat(10, () => random.int(20, 10)); + + expect(results.filter((n) => n >= 10 && n <= 20)).toHaveLength(10); + expect(results).toMatchInlineSnapshot(` + [ + 10, + 11, + 11, + 12, + 12, + 13, + 14, + 15, + 16, + 17, + ] + `); + }); + + it('should create new random int large increment', () => { + random.setSeed('test', 100); + + const results = mapRepeat(10, () => random.int(20, 10)); + + expect(results.filter((n) => n >= 10 && n <= 20)).toHaveLength(10); + expect(results).toMatchInlineSnapshot(` + [ + 15, + 17, + 13, + 17, + 18, + 15, + 14, + 19, + 13, + 16, ] `); }); it('should create new random float', () => { - random.setSeed('test'); + random.setSeed('test', 10); const results = mapRepeat(10, () => random.float(10, 5)); expect(results.filter((n) => n >= 5 && n <= 10)).toHaveLength(10); expect(results.map((n) => n.toFixed(3))).toMatchInlineSnapshot(` - Array [ - "7.373", - "6.428", - "6.259", - "7.137", - "5.220", - "6.585", - "6.335", - "7.507", - "7.824", - "8.275", + [ + "7.335", + "5.325", + "6.269", + "7.544", + "8.494", + "7.606", + "7.654", + "8.273", + "7.425", + "7.697", ] `); }); @@ -62,48 +108,55 @@ describe('A Random test suite', () => { expect(rnd.boolean()).toBe(true); }); + it('should return a fresh seed', () => { + const rnd = new Random(); + rnd.setSeed('test'); + + rnd.freshSeed(); + + expect(rnd.seed).not.toBe('test'); + }); + it('should keep state', () => { random.setSeed('test'); expect(random.int(5)).toEqual(2); - expect(random.int(10)).toEqual(3); + expect(random.int(10)).toEqual(0); expect(random.int(100)).toEqual(25); random.push(); expect(random.int(5)).toEqual(2); - expect(random.int(10)).toEqual(3); + expect(random.int(10)).toEqual(0); expect(random.int(100)).toEqual(25); - expect(random.int(1000)).toEqual(427); + expect(random.int(10000)).toEqual(5089); + expect(random.int(10000)).toEqual(6989); random.pop(); - expect(random.int(1000)).toEqual(427); + expect(random.int(10000)).toEqual(5089); + expect(random.int(10000)).toEqual(6989); }); it('should not keep state', () => { random.setSeed('test'); - expect(random.int(5)).toEqual(2); - expect(random.int(10)).toEqual(3); - expect(random.int(100)).toEqual(25); + expect(random.int(9999)).toEqual(4669); + expect(random.int(9999)).toEqual(650); + expect(random.int(9999)).toEqual(2538); - random.setSeed('test-2'); random.push(); + random.setSeed('test22222'); - expect(random.int(5)).not.toEqual(2); - expect(random.int(10)).not.toEqual(3); - expect(random.int(100)).not.toEqual(25); - - expect(random.int(1000)).not.toEqual(427); + expect(random.int(9999)).toEqual(5330); + expect(random.int(9999)).toEqual(4669); + expect(random.int(9999)).toEqual(5569); random.pop(); - random.setSeed('test'); - - expect(random.int(5)).toEqual(2); - expect(random.int(10)).toEqual(3); - expect(random.int(100)).toEqual(25); + expect(random.int(9999)).toEqual(5000); + expect(random.int(9999)).toEqual(2551); + expect(random.int(9999)).toEqual(2557); }); });