diff --git a/src/index.d.ts b/src/index.d.ts index f40a6c6..7e67b6e 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -16,9 +16,10 @@ type RenderFunction = (resolution: number, indexX: number, indexY: number) => st * Returns a randomly generated string representation code of an avatar for a given `complexity` * @param {number} [complexity] - A positive integer that represents the number of rows and columns to be drawn * @param {string} [avatarDataSeparator] - A character to be used as data separator + * @param {any} [seed] - A value to be used as the seed for generating the avatar data; accepts any value that the `String` constructor would accept * @return {string} Output example: 0-6-6te25-9d9p0-xd5g */ -export function generateRandomAvatarData(complexity?: number, avatarDataSeparator?: string): string; +export function generateRandomAvatarData(complexity?: number, avatarDataSeparator?: string, seed?: any): string; /** * Returns a string with a valid SVG markup for a given `avatarData` diff --git a/src/index.js b/src/index.js index e65c955..8f10335 100644 --- a/src/index.js +++ b/src/index.js @@ -39,16 +39,56 @@ function parseAvatarData(data, separator) { return ret; } -export function generateRandomAvatarData(complexity = 16, avatarDataSeparator = '-') { - const xAxis = Math.floor(Math.random() * Math.pow(2, complexity - 1)); - const yAxis = Math.floor(Math.random() * (Math.pow(2, complexity) - 1)) + 1; +// https://stackoverflow.com/a/47593316/1714997 +function cyrb128(str) { + let h1 = 1779033703, h2 = 3144134277, + h3 = 1013904242, h4 = 2773480762; + for (let i = 0, k; i < str.length; i++) { + k = str.charCodeAt(i); + h1 = h2 ^ Math.imul(h1 ^ k, 597399067); + h2 = h3 ^ Math.imul(h2 ^ k, 2869860233); + h3 = h4 ^ Math.imul(h3 ^ k, 951274213); + h4 = h1 ^ Math.imul(h4 ^ k, 2716044179); + } + h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067); + h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233); + h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213); + h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179); + h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1; + return [h1>>>0, h2>>>0, h3>>>0, h4>>>0]; +} + +// https://stackoverflow.com/a/47593316/1714997 +function sfc32(a, b, c, d) { + return function() { + a |= 0; b |= 0; c |= 0; d |= 0; + let t = (a + b | 0) + d | 0; + d = d + 1 | 0; + a = b ^ b >>> 9; + b = c + (c << 3) | 0; + c = (c << 21 | c >>> 11); + c = c + t | 0; + return (t >>> 0) / 4294967296; + } +} + +function getRandomNumberGenerator(seed) { + const seedString = String(seed); // Making sure that the seed is a string. + const seedHashed = cyrb128(seedString); + return sfc32(seedHashed[0], seedHashed[1], seedHashed[2], seedHashed[3]); +} + +export function generateRandomAvatarData(complexity = 16, avatarDataSeparator = '-', seed = Math.random()) { + const getRandomNumber = getRandomNumberGenerator(seed); + const xAxis = Math.floor(getRandomNumber() * Math.pow(2, complexity - 1)); + const yAxis = Math.floor(getRandomNumber() * (Math.pow(2, complexity) - 1)) + 1; const rows = getBinaryList(yAxis, complexity); let ret = `${xAxis.toString(36)}${avatarDataSeparator}${yAxis.toString(36)}`; let color; rows.forEach(() => { - color = Math.floor(Math.random() * 16777215); + color = Math.floor(getRandomNumber() * 16777215); ret += `${avatarDataSeparator}${color.toString(36)}`; }); diff --git a/src/index.test.js b/src/index.test.js index 3245aa6..b68adad 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -1,6 +1,6 @@ import { getRandomAvatar, getAvatarFromData, generateRandomAvatarData } from './index.js'; -describe('Generate random avatar Ddta', () => { +describe('Generate random avatar Data', () => { it('should return a random code from default values', () => { const avatarCode = generateRandomAvatarData(); expect(avatarCode.split('-').length).toEqual(18); @@ -11,6 +11,27 @@ describe('Generate random avatar Ddta', () => { }); }); +describe('Generate random avatar data with seed', () => { + it('should return the same code for the same seed', () => { + const seed = "apples"; + const avatarCode1 = generateRandomAvatarData(16, '-', seed); + const avatarCode2 = generateRandomAvatarData(16, '-', seed); + expect(avatarCode1).toEqual(avatarCode2); + }); + it('should return different codes for different seeds', () => { + const seed1 = "apples"; + const seed2 = "oranges"; + const avatarCode1 = generateRandomAvatarData(16, '-', seed1); + const avatarCode2 = generateRandomAvatarData(16, '-', seed2); + expect(avatarCode1).not.toEqual(avatarCode2); + }); + it('should return different codes when no seed is provided', () => { + const avatarCode1 = generateRandomAvatarData(); + const avatarCode2 = generateRandomAvatarData(); + expect(avatarCode1).not.toEqual(avatarCode2); + }); +}); + describe('Get svg from avatar data', () => { it('should fail if wrong avatar code is submited', () => { expect(() => getAvatarFromData('')).toThrow('Incorrect avatar data'); @@ -41,4 +62,4 @@ describe('Get a random svg avatar', () => { expect(getRandomAvatar(2, 'circle')).toMatch(/.+<\/svg>/); expect(getRandomAvatar(2, 'circle', 16)).toMatch(/.+<\/svg>/); }); -}); \ No newline at end of file +});