From f1b450c4da5a1acfb560ba280f06c6b4d17ebf00 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:32:50 +0400 Subject: [PATCH 01/22] Add read-dna script --- ts/scripts/read-dna.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 ts/scripts/read-dna.ts diff --git a/ts/scripts/read-dna.ts b/ts/scripts/read-dna.ts new file mode 100644 index 0000000..58b10c0 --- /dev/null +++ b/ts/scripts/read-dna.ts @@ -0,0 +1,11 @@ +import { DNAFactory } from '../src'; + +function run() { + const dna = process.argv[2]; + const df = new DNAFactory(); + const data = df.parse(dna, '3.2.0'); + debugger; + console.log(data); +} + +run(); From 607f8af3a60e85bb25f5e6a047af61a96dfcba3f Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:33:30 +0400 Subject: [PATCH 02/22] Update read dna script --- ts/scripts/read-dna.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/scripts/read-dna.ts b/ts/scripts/read-dna.ts index 58b10c0..2ecf34a 100644 --- a/ts/scripts/read-dna.ts +++ b/ts/scripts/read-dna.ts @@ -3,7 +3,7 @@ import { DNAFactory } from '../src'; function run() { const dna = process.argv[2]; const df = new DNAFactory(); - const data = df.parse(dna, '3.2.0'); + const data = df.parse(dna); debugger; console.log(data); } From b36728c2138e129ffc0a96a2bcc67337d99509f3 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:17:52 +0400 Subject: [PATCH 03/22] Remove unused code --- ts/scripts/convert-adv-schema.ts | 28 ++++++++++++++++++++++++++++ ts/scripts/generate-dna.ts | 11 +++++++++++ ts/src/adventure_stats.ts | 25 ------------------------- 3 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 ts/scripts/convert-adv-schema.ts create mode 100644 ts/scripts/generate-dna.ts diff --git a/ts/scripts/convert-adv-schema.ts b/ts/scripts/convert-adv-schema.ts new file mode 100644 index 0000000..c2bbe94 --- /dev/null +++ b/ts/scripts/convert-adv-schema.ts @@ -0,0 +1,28 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +function getCurrentDateFormatted() { + const date = new Date(); + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based, so add 1 + const year = date.getFullYear(); + + return `${day}/${month}/${year}`; +} + +// Define the input and output file paths +const inputFilePath = './src/deps/schemas/adventures/v0.0.6.json'; +function run() { + const advSchema = JSON.parse(fs.readFileSync(inputFilePath, 'utf8').toString()); + const nefties = advSchema.nefties; + const version = '4.0.0'; + const date = getCurrentDateFormatted(); + const newSchema = { + version, + date, + nefties, + }; + console.log(advSchema); +} + +run(); diff --git a/ts/scripts/generate-dna.ts b/ts/scripts/generate-dna.ts new file mode 100644 index 0000000..b15e075 --- /dev/null +++ b/ts/scripts/generate-dna.ts @@ -0,0 +1,11 @@ +import { DNAFactory, EggsFactory } from '../src'; + +function run() { + const df = new DNAFactory(); + const eggs = EggsFactory.getAllEggs(); + const dna = df.generateNeftyDNA('0', 'prime'); + const data = df.parse(dna); + console.log(data); +} + +run(); diff --git a/ts/src/adventure_stats.ts b/ts/src/adventure_stats.ts index c72f4bc..951ac18 100644 --- a/ts/src/adventure_stats.ts +++ b/ts/src/adventure_stats.ts @@ -144,31 +144,6 @@ function convertStats(tacticsStats: ParseDataRangeCompleteness): ParseDataPerc { return advArrToObj(adventuresStats); } -function fixGlitchedSchimmering( - tacticsStatsObj: ParseDataRangeCompleteness, - adventuresStatsObj: ParseDataPerc -): ParseDataPerc { - const tacticsStats = tacticsStatsObjToArr(tacticsStatsObj); - const floorAvgGame1 = floorAverage(tacticsStats); - const adventuresStats = Object.values(adventuresStatsObj); - const isGlitched1 = tacticsStats.every((stat) => stat <= 5); - const isSchimmering1 = tacticsStats.every((stat) => stat >= 95); - const isGlitched2 = adventuresStats.every((stat) => stat <= 5); - const isSchimmering2 = adventuresStats.every((stat) => stat >= 95); - - let adventuresStatsCorrected; - if (isGlitched1 === isGlitched2 && isSchimmering1 === isSchimmering2) { - return adventuresStatsObj; - } else if (isGlitched1 !== isGlitched2) { - if (isGlitched1) { - adventuresStatsCorrected = makeGlitched(floorAvgGame1, adventuresStats); - } - adventuresStatsCorrected = removeGlitched(floorAvgGame1, adventuresStats); - } else if (isSchimmering1) adventuresStatsCorrected = makeSchimmering(floorAvgGame1, adventuresStats); - else adventuresStatsCorrected = removeSchimmering(floorAvgGame1, adventuresStats); - return advArrToObj(adventuresStatsCorrected); -} - export function getAdventuresStats(dnaSchemaReader: DNASchemaReader, adventuresStats: AdvStatsJSON): ParseDataAdv { const tacticsStats: Partial = {}; dnaSchemaReader.getCompletenessGenes().forEach((gene: GeneWithValues) => { From a3b65185454897e19d6cf714ef8c19651ce6bfb6 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:55:50 +0200 Subject: [PATCH 04/22] Wip --- ts/package.json | 3 + ts/src/adventure_stats.ts | 4 +- ts/src/constants.ts | 10 +- ts/src/deps/schemas/aurory_dna_v4.0.0.json | 48 ++++ ts/src/deps/schemas/latest.ts | 2 +- ts/src/{dna_factory.ts => dna_factory_v1.ts} | 6 +- ts/src/dna_factory_v2.ts | 232 ++++++++++++++++++ .../{eggs_factory.ts => eggs_factory_v1.ts} | 17 +- ts/src/eggs_factory_v2.ts | 56 +++++ ts/src/index.ts | 11 +- ts/src/interfaces/types.ts | 43 +++- ts/src/utils.ts | 22 +- ts/tests/distribution.spec.ts | 2 +- ts/tests/dna.spec.ts | 20 +- ts/tests/dna.v2.spec.ts | 11 + ts/tests/workers/distribution-worker.ts | 4 +- ts/tests/workers/stats-mean-worker.ts | 4 +- ts/tsconfig.build.json | 2 +- ts/yarn.lock | 10 + 19 files changed, 469 insertions(+), 38 deletions(-) create mode 100644 ts/src/deps/schemas/aurory_dna_v4.0.0.json rename ts/src/{dna_factory.ts => dna_factory_v1.ts} (99%) create mode 100644 ts/src/dna_factory_v2.ts rename ts/src/{eggs_factory.ts => eggs_factory_v1.ts} (78%) create mode 100644 ts/src/eggs_factory_v2.ts create mode 100644 ts/tests/dna.v2.spec.ts diff --git a/ts/package.json b/ts/package.json index 3771314..660eb62 100644 --- a/ts/package.json +++ b/ts/package.json @@ -12,6 +12,7 @@ "sp": "ts-node $1", "test": "ts-mocha -p ./tsconfig.json -t 2500000 --exit tests/**/*.spec.ts", "test:dna": "ts-mocha -p ./tsconfig.json -t 2500000 --exit tests/dna.spec.ts", + "test:dna2": "ts-mocha -p ./tsconfig.json -t 2500000 --exit tests/dna.v2.spec.ts", "test:distribution": "ts-mocha -p ./tsconfig.json -t 2500000 --exit tests/distribution.spec.ts", "build": "tsc -p tsconfig.build.json && cp -r src/deps lib/", "prepare": "cd .. && husky install && cd ts && yarn build", @@ -41,6 +42,7 @@ "eslint-plugin-prettier": "^4.2.1", "husky": "^8.0.1", "mocha": "^10.0.0", + "nanoid": "^5.0.7", "prettier": "^2.6.2", "ts-mocha": "^10.0.0", "ts-node": "^10.2.1", @@ -53,6 +55,7 @@ "dependencies": { "@types/randombytes": "^2.0.0", "camel-case": "^4.1.2", + "compress-json": "^3.1.0", "dotenv": "^16.3.1", "google-spreadsheet": "^3.3.0", "randombytes": "^2.1.0", diff --git a/ts/src/adventure_stats.ts b/ts/src/adventure_stats.ts index 951ac18..205737a 100644 --- a/ts/src/adventure_stats.ts +++ b/ts/src/adventure_stats.ts @@ -7,6 +7,8 @@ import { AdvStatsJSON, ParseDataPerc, AdvStatsJSONValue, + NeftyCodeNameId, + NeftyCodeName, } from './interfaces/types'; /** @@ -150,7 +152,7 @@ export function getAdventuresStats(dnaSchemaReader: DNASchemaReader, adventuresS tacticsStats[gene.name as keyof ParseDataRangeCompleteness] = Math.round((gene.completeness as number) * 100); }); const fixedStats = convertStats(tacticsStats as ParseDataRangeCompleteness); - const neftieName = TACTICS_ADV_NAMES_MAP[dnaSchemaReader.archetype.fixed_attributes.name]; + const neftieName = TACTICS_ADV_NAMES_MAP[dnaSchemaReader.archetype.fixed_attributes.name as NeftyCodeName]; const advStatsRanges = adventuresStats.nefties[neftieName]; Object.keys(fixedStats).forEach((key) => { const min = advStatsRanges[`${key}Min` as keyof AdvStatsJSONValue]; diff --git a/ts/src/constants.ts b/ts/src/constants.ts index 9bb2937..873d2a9 100644 --- a/ts/src/constants.ts +++ b/ts/src/constants.ts @@ -3,7 +3,7 @@ export const GLITCHED_PERIOD = 1500; export const GLITCHED_RANGE_START = 5; export const SCHIMMERING_RANGE_START = 95; -export const TACTICS_ADV_NAMES_MAP: Record = { +export const TACTICS_ADV_NAMES_MAP = { Nefty_Bitebit: 'id_bitebit', Nefty_Dipking: 'id_dipking', Nefty_Dinobit: 'id_dinobit', @@ -24,4 +24,10 @@ export const TACTICS_ADV_NAMES_MAP: Record = { Nefty_Chocorex: 'id_chocorex', Nefty_Keybab: 'id_keybab', Nefty_Bloomtail: 'id_bloomtail', -}; +} as const; + +export const VERSION_LENGTH = 4; +export const LAST_SUPPORTED_VERSION_BY_V1 = '3.2.0'; + +// hp, atk, def, speed +export const N_STATS_SOT = 4; diff --git a/ts/src/deps/schemas/aurory_dna_v4.0.0.json b/ts/src/deps/schemas/aurory_dna_v4.0.0.json new file mode 100644 index 0000000..b2e094b --- /dev/null +++ b/ts/src/deps/schemas/aurory_dna_v4.0.0.json @@ -0,0 +1,48 @@ +{ + "version": "4.0.0", + "version_date": "05/07/2024", + "global_genes_header": [ + { + "name": "version", + "base": 2 + }, + { + "name": "data_end", + "base": 2 + } + ], + "archetypes": { + "0": "Nefty_Bitebit", + "1": "Nefty_Dipking", + "2": "Nefty_Dinobit", + "3": "Nefty_ShibaIgnite", + "4": "Nefty_Zzoo", + "5": "Nefty_Blockchoy", + "6": "Nefty_Number9", + "7": "Nefty_Axobubble", + "8": "Nefty_Unika", + "9": "Nefty_Chocomint", + "10": "Nefty_Cybertooth", + "11": "Nefty_Wassie", + "12": "Nefty_Dracurve", + "13": "Nefty_Raccoin", + "14": "Nefty_Shibark", + "15": "Nefty_Unikirin", + "16": "Nefty_Beeblock", + "17": "Nefty_Chocorex", + "18": "Nefty_Keybab", + "19": "Nefty_Bloomtail", + "20": "Nefty_Tokoma", + "21": "Nefty_Ghouliath", + "22": "Nefty_Whiskube", + "23": "Nefty_Walpuff", + "24": "Nefty_Dinotusk" + }, + "rarities": { + "0": "Common", + "1": "Uncommon", + "2": "Rare", + "3": "Epic", + "4": "Legendary" + } +} diff --git a/ts/src/deps/schemas/latest.ts b/ts/src/deps/schemas/latest.ts index c4bb975..c45cff9 100644 --- a/ts/src/deps/schemas/latest.ts +++ b/ts/src/deps/schemas/latest.ts @@ -1 +1 @@ -export const LATEST_VERSION = '3.2.0'; +export const LATEST_VERSION = '4.0.0'; diff --git a/ts/src/dna_factory.ts b/ts/src/dna_factory_v1.ts similarity index 99% rename from ts/src/dna_factory.ts rename to ts/src/dna_factory_v1.ts index 23efd37..7644569 100644 --- a/ts/src/dna_factory.ts +++ b/ts/src/dna_factory_v1.ts @@ -34,7 +34,6 @@ import dnaSchemaV3_1 from './deps/schemas/aurory_dna_v3.1.0.json'; import dnaSchemaV3_2 from './deps/schemas/aurory_dna_v3.2.0.json'; import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; -import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from './deps/schemas/latest'; import { LATEST_VERSION as LATEST_ABILTIIES_VERSION } from './deps/dictionaries/latest'; import abiltiesDictionaryV4 from './deps/dictionaries/abilities_dictionary_v0.4.0.json'; import neftiesInfo from './deps/nefties_info.json'; @@ -51,6 +50,7 @@ import { } from './utils'; import { DNASchemaReader } from './dna_schema_reader'; import { getAdventuresStats } from './adventure_stats'; +import { LAST_SUPPORTED_VERSION_BY_V1 } from './constants'; const dnaSchemas: Record = { '0.2.0': dnaSchemaV0_2 as DNASchema, @@ -72,7 +72,7 @@ const abilitiesDictionaries: Record = { '0.4.0': abiltiesDictionaryV4 as AbilityDictionary, }; -export class DNAFactory { +export class DNAFactoryV1 { dnaSchemas: Record; abilitiesDictionary: Record; neftiesInfo: NeftiesInfo; @@ -91,7 +91,7 @@ export class DNAFactory { this.dnaBytes = dnaBytes ?? 64; this.encodingBase = encodingBase ?? 16; this.baseSize = this.encodingBase / 8; - this.latestSchemaVersion = LATEST_SCHEMA_VERSION; + this.latestSchemaVersion = LAST_SUPPORTED_VERSION_BY_V1; this.latestAbilitiesVersion = LATEST_ABILTIIES_VERSION; this.dnaSchemas = dnaSchemas; this.abilitiesDictionary = abilitiesDictionaries; diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts new file mode 100644 index 0000000..9e77d06 --- /dev/null +++ b/ts/src/dna_factory_v2.ts @@ -0,0 +1,232 @@ +import { + Grade, + Rarity, + version, + DNASchemaV4, + DnaDataV2, + ParseDataPerc, + NeftyCodeName, + ParseV2, + DnaDataData, + ParseDataComputed, + AdvStatsJSON, + AdvStatsJSONValue, +} from './interfaces/types'; +import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from './deps/schemas/latest'; +import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; +import dnaSchemaV4_0 from './deps/schemas/aurory_dna_v4.0.0.json'; +import { + getAverageFromRaw, + getCategoryKeyFromName, + getLatestSubversion, + randomInt, + randomNormal, + toPaddedHexa, + toUnPaddedHexa, + unpad, +} from './utils'; +import raritiesGeneration from './deps/rarities_generation.json'; +import zlib from 'zlib'; +import { N_STATS_SOT, TACTICS_ADV_NAMES_MAP, VERSION_LENGTH } from './constants'; +import { DNAFactoryV1 } from './dna_factory_v1'; +import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; + +const dnaSchemas: Record = { + '4.0.0': dnaSchemaV4_0 as DNASchemaV4, +}; + +const adventuresStats: Record = { + '0.0.6': adventuresStatsV0_0_6, +}; + +export class DNAFactoryV2 { + latestSchemasSubversions: Record; + constructor() { + this.latestSchemasSubversions = {}; + return; + } + + // get latest minor version from a major version + private getLatestSchemaSubversion(schemaVersion?: string): string { + if (!schemaVersion) return LATEST_SCHEMA_VERSION; + else if (schemaVersion?.includes('.')) return schemaVersion; + else if (schemaVersion && this.latestSchemasSubversions[schemaVersion]) + return this.latestSchemasSubversions[schemaVersion]; + const completeVersion = getLatestSubversion(dnaSchemas, schemaVersion); + this.latestSchemasSubversions[schemaVersion] = completeVersion; + return completeVersion; + } + + private _getRandomRarity(grade: Grade): Rarity { + const rarities = raritiesGeneration[grade]; + if (!rarities) { + throw new Error(`No rarity found for input ${grade}`); + } + const precision = 3; + const multiplier = Math.pow(10, precision); + const weightsSum = Object.values(rarities).reduce((acc, rarity) => acc + rarity.probability * multiplier, 0); + const random = Math.random() * weightsSum; + let total = 0; + for (const [rarity, rarityInfo] of Object.entries(rarities)) { + total += rarityInfo.probability * multiplier; + if (random <= total) return rarity as Rarity; + } + throw new Error(`No rarity found: ${weightsSum}, ${random}`); + } + + private getDNASchema(version?: version): DNASchemaV4 { + if (!version) return dnaSchemas[LATEST_SCHEMA_VERSION]; + else if (dnaSchemas[version]) return dnaSchemas[version]; + else if (this.latestSchemasSubversions[version]) return dnaSchemas[this.latestSchemasSubversions[version]]; + + const completeVersion = version.includes('.') ? version : this.getLatestSchemaSubversion(version); + + if (!completeVersion) throw new Error(`No schema found for ${version}`); + + const dnaSchema: DNASchemaV4 = dnaSchemas[completeVersion]; + if (completeVersion !== dnaSchema.version) + throw new Error(`Versions mismatch: ${completeVersion} (filename) vs ${dnaSchema.version} (schema)`); + return dnaSchemas[completeVersion]; + } + + private deserializeDna(dna: string): DnaDataV2 { + return JSON.parse(Buffer.from(dna, 'base64').toString()); + } + + private getDna(version: version, data: DnaDataV2): string { + const versionDNAFormat = toPaddedHexa(version, 4); + const serializedData = Buffer.from(JSON.stringify(data)).toString('base64'); + const dna = versionDNAFormat + serializedData; + return dna; + } + + /** + * Generate statsCount number of stats with a mean value in the rarity range. + * @param rarity Rarity + * @param statsCount Number of stats to generate + */ + private _generateStatsForRarity(nStats: number, grade: Grade, rarity: Rarity): number[] { + const [minStatAvg, maxStatAvg] = raritiesGeneration[grade][rarity].average_stats_range; + const stats = Array.from(Array(nStats).keys()).map(() => 0); + + const mean = randomInt(minStatAvg, maxStatAvg, true); + // adding up to 5 will still result in the same mean as we are rounding down + const totalPoints = mean * nStats; + const maxValuePerStat = 100; + + // As 100 is the upper limit, this value is over represented in the distribution + // This makes the max random stat randomly set to a value between mean + 1 and 100 + const maxStatValue = randomInt(Math.min(mean + 1, maxValuePerStat), maxValuePerStat, false); + + const distributePoints = () => { + while (pointsLeft) { + const statIndex = randomInt(0, stats.length, true); + const statValue = stats[statIndex]; + + const maxPoints = Math.min(pointsLeft, maxStatValue - statValue); + if (!maxPoints) continue; + if (pointsLeft < 0) throw new Error('pointsLeft < 0'); + const points = randomNormal(1, Math.ceil(maxPoints / stats.length), -100, 200); + stats[statIndex] += points; + pointsLeft -= points; + } + }; + + let pointsLeft = totalPoints; + let raw = [] as number[]; + let average; + + while (pointsLeft) { + distributePoints(); + raw = stats.map((stat, index) => Math.round((stat / 100) * maxValuePerStat)); + + average = Math.floor( + getAverageFromRaw( + raw, + stats.map((_, index) => maxValuePerStat) + ) * 100 + ); + + // the average is done on raw stats but points are distributed on the % stats. It may happen the means are not the same. + if (Math.floor(average) !== mean) pointsLeft += 1; + } + + // if average = 1 for a non glitched or 95 for a schimmering, we may end up not enterring in the previous loop + if (!raw.length) raw = stats.map((stat, index) => Math.round((stat / 100) * maxValuePerStat)); + return raw; + } + + generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { + const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + console.log(dnaSchema.version); + const dnaData = {} as DnaDataV2; + dnaData.version = dnaSchema.version; + + const dataData = {} as DnaDataData; + dataData.grade = grade; + dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); + dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; + dnaData.data = dataData; + + const dataAdv = {} as ParseDataPerc; + const [hp, atk, def, speed] = this._generateStatsForRarity(N_STATS_SOT, grade, dataData.rarity); + Object.assign(dataAdv, { hp, atk, def, speed }); + dnaData.dataAdv = dataAdv; + + const dna = this.getDna(dnaSchema.version, dnaData); + return dna; + } + + generateNeftyDNAFromV1Dna( + dnaFactoryV1: DNAFactoryV1, + v1Dna: string, + newSotStats?: ParseDataPerc, + newVersion?: version + ) { + const dataV1 = dnaFactoryV1.parse(v1Dna); + const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); + const dnaData = {} as DnaDataV2; + const dataAdv = {} as ParseDataPerc; + if (newSotStats) { + const { hp: hpP, atk: atkP, def: defP, speed: speedP } = newSotStats; + Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + } else { + const { hp: hpP, atk: atkP, def: defP, speed: speedP } = dataV1.dataAdv; + Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + } + dnaData.dataAdv = dataAdv; + const dataData = {} as DnaDataData; + dataData.grade = dataV1.data.grade; + dataData.rarity = dataV1.data.rarity; + dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; + dnaData.data = dataData; + dnaData.version = dnaSchema.version; + const dna = this.getDna(dnaSchema.version, dnaData); + return dna; + } + + parse(dnaString: string): ParseV2 { + const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); + const data = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); + const computedStats: ParseDataComputed = this.computeSOTStats(data.data.neftyCodeName, data.dataAdv); + Object.assign(data.dataAdv, computedStats); + return data as ParseV2; + } + + private computeSOTStats(neftyCodeName: NeftyCodeName, dataAdv: ParseDataPerc): ParseDataComputed { + const neftyCodenameId = TACTICS_ADV_NAMES_MAP[neftyCodeName]; + const computed = {} as ParseDataComputed; + const sotStatsCurrent = adventuresStats[LATEST_ADVENTURES_STATS_VERSION].nefties[neftyCodenameId]; + if (!sotStatsCurrent) throw new Error(`No SOT stats found for ${neftyCodenameId}`); + Object.entries(dataAdv).forEach(([statName, percentage]) => { + const key = `${statName}Computed` as keyof ParseDataComputed; + const minK = `${statName}Min` as keyof AdvStatsJSONValue; + const min = sotStatsCurrent[minK]; + const maxK = `${statName}Max` as keyof AdvStatsJSONValue; + const max = sotStatsCurrent[maxK]; + const value = (percentage / 100) * (max - min) + min; + computed[key] = Math.round(value); + }); + return computed; + } +} diff --git a/ts/src/eggs_factory.ts b/ts/src/eggs_factory_v1.ts similarity index 78% rename from ts/src/eggs_factory.ts rename to ts/src/eggs_factory_v1.ts index f822063..d9e2058 100644 --- a/ts/src/eggs_factory.ts +++ b/ts/src/eggs_factory_v1.ts @@ -1,9 +1,10 @@ import { EggInfo, Archetype, DroppableNeftyInfo } from './interfaces/types'; import eggsInfo from './deps/eggs_info.json'; import standardEggsInfo from './deps/standard_eggs_info.json'; -import { DNAFactory } from './dna_factory'; +import { DNAFactoryV1 as DNAFactory } from './dna_factory_v1'; +import { LAST_SUPPORTED_VERSION_BY_V1 } from './constants'; -export class EggsFactory { +export class EggsFactoryV1 { eggInfo: EggInfo; standardEggInfo: EggInfo; df: DNAFactory; @@ -14,17 +15,17 @@ export class EggsFactory { } static getAllEggs(): Record { - return eggsInfo; + return eggsInfo as Record; } static getAllStandardEggs(): Record { - return standardEggsInfo; + return standardEggsInfo as Record; } getDroppableNefties(): DroppableNeftyInfo[] { return this.eggInfo.archetypes.map((neftyCodeName) => { const r: DroppableNeftyInfo = {} as DroppableNeftyInfo; - Object.assign(r, this.df.getArchetypeByNeftyCodeName(neftyCodeName)); + Object.assign(r, this.df.getArchetypeByNeftyCodeName(neftyCodeName, LAST_SUPPORTED_VERSION_BY_V1)); r.displayName = this.df.getDisplayNameFromCodeName(neftyCodeName); r.description = this.df.getFamilyDescription(r.archetype.fixed_attributes.family as string); return r; @@ -34,7 +35,7 @@ export class EggsFactory { getDroppableStandardNefties(): DroppableNeftyInfo[] { return this.standardEggInfo.archetypes.map((neftyCodeName) => { const r: DroppableNeftyInfo = {} as DroppableNeftyInfo; - Object.assign(r, this.df.getArchetypeByNeftyCodeName(neftyCodeName)); + Object.assign(r, this.df.getArchetypeByNeftyCodeName(neftyCodeName, LAST_SUPPORTED_VERSION_BY_V1)); r.displayName = this.df.getDisplayNameFromCodeName(neftyCodeName); r.description = this.df.getFamilyDescription(r.archetype.fixed_attributes.family as string); return r; @@ -44,12 +45,12 @@ export class EggsFactory { hatch(): { archetypeKey: string; archetype: Archetype } { const droppableArchetypes = this.eggInfo.archetypes; const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; - return this.df.getArchetypeByNeftyCodeName(neftyCodeName); + return this.df.getArchetypeByNeftyCodeName(neftyCodeName, LAST_SUPPORTED_VERSION_BY_V1); } hatchStandard(): { archetypeKey: string; archetype: Archetype } { const droppableArchetypes = this.standardEggInfo.archetypes; const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; - return this.df.getArchetypeByNeftyCodeName(neftyCodeName); + return this.df.getArchetypeByNeftyCodeName(neftyCodeName, LAST_SUPPORTED_VERSION_BY_V1); } } diff --git a/ts/src/eggs_factory_v2.ts b/ts/src/eggs_factory_v2.ts new file mode 100644 index 0000000..3110b94 --- /dev/null +++ b/ts/src/eggs_factory_v2.ts @@ -0,0 +1,56 @@ +import { EggInfo, DroppableNeftyInfoV2, NeftyCodeName } from './interfaces/types'; +import eggsInfo from './deps/eggs_info.json'; +import standardEggsInfo from './deps/standard_eggs_info.json'; +import { DNAFactoryV2 as DNAFactory } from './dna_factory_v2'; +import neftiesInfo from './deps/nefties_info.json'; + +export class EggsFactoryV2 { + eggInfo: EggInfo; + standardEggInfo: EggInfo; + df: DNAFactory; + constructor(eggPk: string, df: DNAFactory) { + this.eggInfo = (eggsInfo as Record)[eggPk]; + this.standardEggInfo = (standardEggsInfo as Record)[eggPk]; + this.df = df; + } + + static getAllEggs(): Record { + return eggsInfo as Record; + } + + static getAllStandardEggs(): Record { + return standardEggsInfo as Record; + } + + getDroppableNefties(): DroppableNeftyInfoV2[] { + return this.eggInfo.archetypes.map((neftyCodeName) => { + const r = {} as DroppableNeftyInfoV2; + r.neftyCodeName = neftyCodeName; + r.displayName = neftiesInfo.code_to_displayName[neftyCodeName] as string; + return r; + }); + } + + getDroppableStandardNefties(): DroppableNeftyInfoV2[] { + return this.eggInfo.archetypes.map((neftyCodeName) => { + const r = {} as DroppableNeftyInfoV2; + r.neftyCodeName = neftyCodeName; + r.displayName = neftiesInfo.code_to_displayName[neftyCodeName] as string; + return r; + }); + } + + hatch(): { archetypeKey: string; neftyCodeName: NeftyCodeName } { + const droppableArchetypes = this.eggInfo.archetypes; + const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; + const archetypeKey = neftyCodeName; + return { archetypeKey, neftyCodeName }; + } + + hatchStandard(): { archetypeKey: string; neftyCodeName: NeftyCodeName } { + const droppableArchetypes = this.standardEggInfo.archetypes; + const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; + const archetypeKey = neftyCodeName; + return { archetypeKey, neftyCodeName }; + } +} diff --git a/ts/src/index.ts b/ts/src/index.ts index 4a24fc3..7c61e79 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -1,5 +1,12 @@ -export * from './dna_factory'; -export * from './eggs_factory'; +import { DNAFactoryV2 as DNAFactory } from './dna_factory_v2'; +export { DNAFactory }; +import { EggsFactoryV2 as EggsFactory } from './eggs_factory_v2'; +export { EggsFactory }; + +export * from './dna_factory_v1'; +export * from './dna_factory_v2'; +export * from './eggs_factory_v1'; +export * from './eggs_factory_v2'; export * from './dna_schema_reader'; export * from './interfaces/types'; export * from './constants'; diff --git a/ts/src/interfaces/types.ts b/ts/src/interfaces/types.ts index 6590f93..83161e5 100644 --- a/ts/src/interfaces/types.ts +++ b/ts/src/interfaces/types.ts @@ -1,4 +1,5 @@ import abiltiesDictionaryV4 from '../deps/dictionaries/abilities_dictionary_v0.4.0.json'; +import { TACTICS_ADV_NAMES_MAP } from '../constants'; export type GeneType = 'index' | 'range_completeness'; @@ -56,6 +57,19 @@ export interface DNASchemaV2 { export type DNASchemaV3 = DNASchemaV2; +// eg: Nefty_Bitebit +export type NeftyCodeName = keyof typeof TACTICS_ADV_NAMES_MAP; +// eg: id_bitebit +export type NeftyCodeNameId = typeof TACTICS_ADV_NAMES_MAP[keyof typeof TACTICS_ADV_NAMES_MAP]; + +export interface DNASchemaV4 { + version: string; + version_date: string; + global_genes_header: Gene[]; + archetypes: Record; + rarities: Record; +} + export type DNASchema = DNASchemaV0 | DNASchemaV2 | DNASchemaV3; export type Rarity = 'Common' | 'Uncommon' | 'Rare' | 'Epic' | 'Legendary'; @@ -83,7 +97,7 @@ export interface ImageNeftyByGame { export type NeftyImageFormat = keyof ImageNeftyByGame; export interface ParseDataNefty { - name: string; + name: NeftyCodeName; displayName: string; family: string; passiveSkill: string; @@ -160,7 +174,6 @@ export interface Parse { metadata: { version: string }; genes: Gene[]; } - export interface AbilityLocalizedValue { EN: string; FR: string; @@ -187,9 +200,6 @@ export interface NeftiesInfo { family_to_description: Record; } -// eg: Nefty_Bitebit -type NeftyCodeName = string; - export interface EggInfo { name: string; description: string; @@ -203,6 +213,11 @@ export interface DroppableNeftyInfo { description: string; } +export interface DroppableNeftyInfoV2 { + neftyCodeName: string; + displayName: string; +} + export type version = string; export type GeneWithValues = Gene & { @@ -211,3 +226,21 @@ export type GeneWithValues = Gene & { completeness?: number; skill_info?: AbilityInfo; }; + +export interface DnaDataData { + grade: Grade; + rarity: Rarity; + neftyCodeName: NeftyCodeName; +} + +export interface DnaDataV2 { + version: string; + data: DnaDataData; + dataAdv: ParseDataPerc; +} + +export interface ParseV2 { + version: string; + data: DnaDataData; + dataAdv: ParseDataAdv; +} diff --git a/ts/src/utils.ts b/ts/src/utils.ts index 3d018c9..6b255b6 100644 --- a/ts/src/utils.ts +++ b/ts/src/utils.ts @@ -1,4 +1,4 @@ -import { AbilityDictionary, Category, DNASchema } from './interfaces/types'; +import { AbilityDictionary, Category, DNASchema, DNASchemaV4 } from './interfaces/types'; // you should multiply by 100 to get in % export function getAverageFromRaw(numbers: number[], maxValuePerStat: number[]): number { @@ -23,7 +23,7 @@ export function getCategoryKeyFromName(name: string, categories: Record, + completeVersionsDict: Record, schemaVersionInput?: string ): string { const schemaVersion = schemaVersionInput @@ -87,3 +87,21 @@ export function randomNormal(min: number, max: number, leftLimit: number, rightL export function unpad(v: string, encodingBase: number | undefined): string { return parseInt(v, encodingBase).toString(); } + +export function toPadded(n: string, maxLength: number, radix?: number): string { + return parseInt(n).toString(radix).padStart(maxLength, '0'); +} + +/** + * Converts a string representation of a number to a hexadecimal string + * and pads it with leading zeros to ensure it has the specified length. + * @example + * _toPaddedHexa('255', 4); / Returns '00ff' + */ +export function toPaddedHexa(n: string, maxLength: number): string { + return toPadded(n, maxLength, 16); +} + +export function toUnPaddedHexa(n: string): string { + return unpad(n, 16); +} diff --git a/ts/tests/distribution.spec.ts b/ts/tests/distribution.spec.ts index 23a1434..219274e 100644 --- a/ts/tests/distribution.spec.ts +++ b/ts/tests/distribution.spec.ts @@ -1,4 +1,4 @@ -import { DNAFactory, EggsFactory, GLITCHED_PERIOD, Rarity, SCHIMMERING_PERIOD, utils } from '../src'; +import { DNAFactoryV1 as DNAFactory, EggsFactoryV1 as EggsFactory, Rarity } from '../src'; import assert from 'assert'; import raritiesGeneration from '../src/deps/rarities_generation.json'; diff --git a/ts/tests/dna.spec.ts b/ts/tests/dna.spec.ts index 41ed732..f3e9922 100644 --- a/ts/tests/dna.spec.ts +++ b/ts/tests/dna.spec.ts @@ -1,11 +1,10 @@ -import { DNAFactory, EggsFactory, Rarity, utils } from '../src'; +import { DNAFactoryV1 as DNAFactory, EggsFactoryV1 as EggsFactory, Rarity, utils } from '../src'; import nefties_info from '../src/deps/nefties_info.json'; import assert from 'assert'; import raritiesGeneration from '../src/deps/rarities_generation.json'; import { readdirSync, readFileSync } from 'fs'; -import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from '../src/deps/schemas/latest'; -import { DNASchema } from '../src/interfaces/types'; -import { TACTICS_ADV_NAMES_MAP } from '../src/constants'; +import { DNASchema, NeftyCodeName } from '../src/interfaces/types'; +import { LAST_SUPPORTED_VERSION_BY_V1, TACTICS_ADV_NAMES_MAP } from '../src/constants'; const displayNamesProd = [ 'Axobubble', @@ -138,12 +137,15 @@ const abilitiesProd = new Set([ 'N/A', ]); +const LAST_FACTORY_V1_SUPPORTED_VERSION = '3.2.0'; + const allSchemaVersions = readdirSync('./src/deps/schemas') .filter((v) => v.endsWith('json')) .map((v) => { const index = v.indexOf('_v'); return v.slice(index + 2, index + 7); - }); + }) + .filter((v) => parseInt(v.split('.')[0]) <= 3); describe('Basic', () => { it('DNA should parse', () => { @@ -155,7 +157,7 @@ describe('Basic', () => { try { assert.ok(displayName); assert.ok(description); - const dna = df.generateNeftyDNA(archetypeKey, 'prime'); + const dna = df.generateNeftyDNA(archetypeKey, 'prime', LAST_FACTORY_V1_SUPPORTED_VERSION); const data = df.parse(dna); assert.ok(data.data); assert.ok(data.data.name); @@ -233,6 +235,7 @@ describe('Compute possible names, families and abilities', () => { const df = new DNAFactory(); const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); const neftyIndex = ef.hatch().archetypeKey; + debugger; const dna = df.generateNeftyDNA(neftyIndex, 'prime'); const category = df.getCategory('nefties', df.getDnaVersion(dna)); const neftyNames = new Set(); @@ -334,11 +337,12 @@ describe('Rarity', () => { describe('Adventures', () => { it('All archetypes have adventure stats', () => { const schema: DNASchema = JSON.parse( - readFileSync(`./src/deps/schemas/aurory_dna_v${LATEST_SCHEMA_VERSION}.json`, 'utf8') + readFileSync(`./src/deps/schemas/aurory_dna_v${LAST_SUPPORTED_VERSION_BY_V1}.json`, 'utf8') ); + Object.values(schema.categories['0'].archetypes).forEach((archetype) => { assert.ok( - TACTICS_ADV_NAMES_MAP[archetype.fixed_attributes.name], + TACTICS_ADV_NAMES_MAP[archetype.fixed_attributes.name as NeftyCodeName], `${archetype.fixed_attributes.name} not found in TACTICS_ADV_NAMES_MAP: ${JSON.stringify( TACTICS_ADV_NAMES_MAP )}` diff --git a/ts/tests/dna.v2.spec.ts b/ts/tests/dna.v2.spec.ts new file mode 100644 index 0000000..4751a02 --- /dev/null +++ b/ts/tests/dna.v2.spec.ts @@ -0,0 +1,11 @@ +import { DNAFactory } from '../src'; + +describe('Basic', () => { + it('should work', () => { + const df = new DNAFactory(); + const dna = df.generateNeftyDNA('0', 'prime'); + console.log(dna); + const parsed = df.parse(dna); + console.log(parsed); + }); +}); diff --git a/ts/tests/workers/distribution-worker.ts b/ts/tests/workers/distribution-worker.ts index 8d299a6..bedaf02 100644 --- a/ts/tests/workers/distribution-worker.ts +++ b/ts/tests/workers/distribution-worker.ts @@ -1,6 +1,6 @@ import { parentPort, workerData } from 'worker_threads'; -import { DNAFactory } from '../../src/dna_factory'; -import { EggsFactory } from '../../src/eggs_factory'; +import { DNAFactoryV1 as DNAFactory } from '../../src/index'; +import { EggsFactoryV1 as EggsFactory } from '../../src/index'; import { Rarity } from '../../src/interfaces/types'; import { utils } from '../../src'; diff --git a/ts/tests/workers/stats-mean-worker.ts b/ts/tests/workers/stats-mean-worker.ts index 4bebd2d..178dc61 100644 --- a/ts/tests/workers/stats-mean-worker.ts +++ b/ts/tests/workers/stats-mean-worker.ts @@ -1,6 +1,6 @@ import { parentPort, workerData } from 'worker_threads'; -import { DNAFactory } from '../../src/dna_factory'; -import { EggsFactory } from '../../src/eggs_factory'; +import { DNAFactoryV1 as DNAFactory } from '../../src/index'; +import { EggsFactoryV1 as EggsFactory } from '../../src/index'; import { Grade, Rarity } from '../../src/interfaces/types'; import { utils } from '../../src'; import raritiesGeneration from '../../src/deps/rarities_generation.json'; diff --git a/ts/tsconfig.build.json b/ts/tsconfig.build.json index 9865daa..25d4201 100644 --- a/ts/tsconfig.build.json +++ b/ts/tsconfig.build.json @@ -3,5 +3,5 @@ "rootDir": "./src" }, "extends": "./tsconfig.json", - "exclude": ["node_modules", "**/tests/*"] + "exclude": ["node_modules", "**/tests/*", "scripts"] } diff --git a/ts/yarn.lock b/ts/yarn.lock index 58800f1..9280e3e 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -608,6 +608,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +compress-json@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/compress-json/-/compress-json-3.1.0.tgz#337e0a5b28b180fb849bcb6913bf361a5b3c0d57" + integrity sha512-Zcq4jRC5ZpfaOY3mbBWOANtGuMHJ/hsTENcwN1/lEkrogcoAF7HBma1RLe/CICZO6IquK1U0EaPzmnlDIFRNjA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1732,6 +1737,11 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== +nanoid@^5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6" + integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" From 45280bb168bf70608ec53ecb01e120ad7554dd6f Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:55:43 +0200 Subject: [PATCH 05/22] Functional --- README.md | 2 +- ts/package.json | 3 +- ts/scripts/convert-adv-schema.ts | 28 ---- ts/scripts/generate-dna.ts | 11 -- ts/scripts/read-dna.ts | 11 -- ts/src/deps/nefties_info.json | 4 +- ts/src/dna_factory_v2.ts | 280 ++++++++++++++++++++++++------- ts/src/eggs_factory_v2.ts | 6 +- ts/src/interfaces/types.ts | 9 +- ts/tests/dna.v2.spec.ts | 191 ++++++++++++++++++++- ts/yarn.lock | 15 +- 11 files changed, 428 insertions(+), 132 deletions(-) delete mode 100644 ts/scripts/convert-adv-schema.ts delete mode 100644 ts/scripts/generate-dna.ts delete mode 100644 ts/scripts/read-dna.ts diff --git a/README.md b/README.md index bee2c0e..7b09eac 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ const parsed = df.parse(dna); console.log(parsed); ``` -> See `Parse` interface for more details on [df.parse](./ts/src/interfaces/types.ts)'s output. +> See `ParseV2` interface for more details on [df.parse](./ts/src/interfaces/types.ts)'s output. ### Get all eggs diff --git a/ts/package.json b/ts/package.json index 660eb62..68c8958 100644 --- a/ts/package.json +++ b/ts/package.json @@ -42,7 +42,6 @@ "eslint-plugin-prettier": "^4.2.1", "husky": "^8.0.1", "mocha": "^10.0.0", - "nanoid": "^5.0.7", "prettier": "^2.6.2", "ts-mocha": "^10.0.0", "ts-node": "^10.2.1", @@ -55,9 +54,9 @@ "dependencies": { "@types/randombytes": "^2.0.0", "camel-case": "^4.1.2", - "compress-json": "^3.1.0", "dotenv": "^16.3.1", "google-spreadsheet": "^3.3.0", + "lz-string": "^1.5.0", "randombytes": "^2.1.0", "snake-case": "^3.0.4" } diff --git a/ts/scripts/convert-adv-schema.ts b/ts/scripts/convert-adv-schema.ts deleted file mode 100644 index c2bbe94..0000000 --- a/ts/scripts/convert-adv-schema.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -function getCurrentDateFormatted() { - const date = new Date(); - const day = String(date.getDate()).padStart(2, '0'); - const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based, so add 1 - const year = date.getFullYear(); - - return `${day}/${month}/${year}`; -} - -// Define the input and output file paths -const inputFilePath = './src/deps/schemas/adventures/v0.0.6.json'; -function run() { - const advSchema = JSON.parse(fs.readFileSync(inputFilePath, 'utf8').toString()); - const nefties = advSchema.nefties; - const version = '4.0.0'; - const date = getCurrentDateFormatted(); - const newSchema = { - version, - date, - nefties, - }; - console.log(advSchema); -} - -run(); diff --git a/ts/scripts/generate-dna.ts b/ts/scripts/generate-dna.ts deleted file mode 100644 index b15e075..0000000 --- a/ts/scripts/generate-dna.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DNAFactory, EggsFactory } from '../src'; - -function run() { - const df = new DNAFactory(); - const eggs = EggsFactory.getAllEggs(); - const dna = df.generateNeftyDNA('0', 'prime'); - const data = df.parse(dna); - console.log(data); -} - -run(); diff --git a/ts/scripts/read-dna.ts b/ts/scripts/read-dna.ts deleted file mode 100644 index 2ecf34a..0000000 --- a/ts/scripts/read-dna.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DNAFactory } from '../src'; - -function run() { - const dna = process.argv[2]; - const df = new DNAFactory(); - const data = df.parse(dna); - debugger; - console.log(data); -} - -run(); diff --git a/ts/src/deps/nefties_info.json b/ts/src/deps/nefties_info.json index 270addb..2d58523 100644 --- a/ts/src/deps/nefties_info.json +++ b/ts/src/deps/nefties_info.json @@ -25,9 +25,9 @@ "Bitebit": "Bitebit may look cuddly, but don't be fooled by its charm - those aren't paper hands... they're DIAMOND CLAWS!", "Dipking": "Dipking is bursting with personality and magical power, but handle with care - all that pent up energy may have EXPLOSIVE results!", "Dinobit": "Dinobit is big, strong and tough, but careful of that temper - Make it mad and watch out for a charge that can PLOW THROUGH EVERYTHING in its path!", - "Shiba": "Shiba Ignite is always ready to jump into the heat of battle, but while it may not be the fastest - a helping paw is always there to DEFEND ITS ALLIES.", + "ShibaIgnite": "Shiba Ignite is always ready to jump into the heat of battle, but while it may not be the fastest - a helping paw is always there to DEFEND ITS ALLIES.", "Zzoo": "Zzoo has a big beak and a bad attitude, but if it's on your side, both can be an asset - SWIFT STRIKES make its mean streak your advantage!", - "Blockchoy": "Block Choy may look tasty, but its real gift is far more delicious - a menu of healing powers is standing by to RE-FUEL its allies.", + "BlockChoy": "Block Choy may look tasty, but its real gift is far more delicious - a menu of healing powers is standing by to RE-FUEL its allies.", "Number9": "Number 9 is a solid choice despite appearances, but remember - it has to FLY THROUGH ENEMIES before it can attack them.", "Axobubble": "Axobubble is a born defender, but its magic is sneaky - buffs, curses and other mischief are sure to bubble up to HELP THE TEAM.", "Unika": "Unika is cool under pressure, but don't let its delicate appearance confuse you - it's ready with an ICE COLD STRIKE!", diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index 9e77d06..c1c1c2d 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -30,6 +30,9 @@ import zlib from 'zlib'; import { N_STATS_SOT, TACTICS_ADV_NAMES_MAP, VERSION_LENGTH } from './constants'; import { DNAFactoryV1 } from './dna_factory_v1'; import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; +import { compressToBase64, decompressFromBase64 } from 'lz-string'; +import raritiesRead from './deps/rarities_read.json'; +import neftiesInfo from './deps/nefties_info.json'; const dnaSchemas: Record = { '4.0.0': dnaSchemaV4_0 as DNASchemaV4, @@ -40,10 +43,14 @@ const adventuresStats: Record = { }; export class DNAFactoryV2 { - latestSchemasSubversions: Record; + private latestSchemasSubversions: Record; + private codeNameToKey: Record; constructor() { this.latestSchemasSubversions = {}; - return; + this.codeNameToKey = {} as any; + Object.entries(this.getDNASchema(LATEST_SCHEMA_VERSION).archetypes).forEach(([key, codename]) => { + this.codeNameToKey[codename as NeftyCodeName] = key; + }); } // get latest minor version from a major version @@ -74,7 +81,7 @@ export class DNAFactoryV2 { throw new Error(`No rarity found: ${weightsSum}, ${random}`); } - private getDNASchema(version?: version): DNASchemaV4 { + getDNASchema(version?: version): DNASchemaV4 { if (!version) return dnaSchemas[LATEST_SCHEMA_VERSION]; else if (dnaSchemas[version]) return dnaSchemas[version]; else if (this.latestSchemasSubversions[version]) return dnaSchemas[this.latestSchemasSubversions[version]]; @@ -89,13 +96,17 @@ export class DNAFactoryV2 { return dnaSchemas[completeVersion]; } + private serializeDna(data: DnaDataV2): string { + return compressToBase64(JSON.stringify(data)); + } + private deserializeDna(dna: string): DnaDataV2 { - return JSON.parse(Buffer.from(dna, 'base64').toString()); + return JSON.parse(decompressFromBase64(dna)) as DnaDataV2; } private getDna(version: version, data: DnaDataV2): string { const versionDNAFormat = toPaddedHexa(version, 4); - const serializedData = Buffer.from(JSON.stringify(data)).toString('base64'); + const serializedData = this.serializeDna(data); const dna = versionDNAFormat + serializedData; return dna; } @@ -156,77 +167,234 @@ export class DNAFactoryV2 { return raw; } - generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { - const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); - console.log(dnaSchema.version); + private computeSOTStats(neftyCodeName: NeftyCodeName, dataAdv: ParseDataPerc): ParseDataComputed { + const neftyCodenameId = TACTICS_ADV_NAMES_MAP[neftyCodeName]; + const computed = {} as ParseDataComputed; + const sotStatsCurrent = adventuresStats[LATEST_ADVENTURES_STATS_VERSION].nefties[neftyCodenameId]; + if (!sotStatsCurrent) { + debugger; + throw new Error(`No SOT stats found for ${neftyCodenameId}`); + } + Object.entries(dataAdv).forEach(([statName, percentage]) => { + const key = `${statName}Computed` as keyof ParseDataComputed; + const minK = `${statName}Min` as keyof AdvStatsJSONValue; + const min = sotStatsCurrent[minK]; + const maxK = `${statName}Max` as keyof AdvStatsJSONValue; + const max = sotStatsCurrent[maxK]; + const value = (percentage / 100) * (max - min) + min; + computed[key] = Math.round(value); + }); + return computed; + } + + private validateArchetypeIndex(archetypeIndex: string) { + if (!Number.isInteger(parseInt(archetypeIndex))) { + throw new Error(`Invalid archetype index: ${archetypeIndex}`); + } + } + + private initializeDnaData(version: string): DnaDataV2 { const dnaData = {} as DnaDataV2; - dnaData.version = dnaSchema.version; + dnaData.version = version; + return dnaData; + } + private createDataData(dnaSchema: any, archetypeIndex: string, grade: Grade, rarityPreset?: Rarity): DnaDataData { const dataData = {} as DnaDataData; dataData.grade = grade; dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; - dnaData.data = dataData; + if (!dataData.neftyCodeName) { + throw new Error(`No archetype found for index ${archetypeIndex}`); + } + return dataData; + } + private createDataDataFromV1(dataV1: any): DnaDataData { + const dataData = {} as DnaDataData; + dataData.grade = dataV1.data.grade; + dataData.rarity = dataV1.data.rarity; + dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; + return dataData; + } + + private createDataAdv(stats: number[]): ParseDataPerc { + const [hp, atk, def, speed] = stats; const dataAdv = {} as ParseDataPerc; - const [hp, atk, def, speed] = this._generateStatsForRarity(N_STATS_SOT, grade, dataData.rarity); Object.assign(dataAdv, { hp, atk, def, speed }); - dnaData.dataAdv = dataAdv; + return dataAdv; + } - const dna = this.getDna(dnaSchema.version, dnaData); - return dna; + private createDataAdvFromV1(stats: ParseDataPerc): ParseDataPerc { + const { hp: hpP, atk: atkP, def: defP, speed: speedP } = stats; + const dataAdv = {} as ParseDataPerc; + Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + return dataAdv; + } + + getArchetypeKeyByNeftyCodeName(neftyCodeName: NeftyCodeName): string { + const archetypeKey = this.codeNameToKey[neftyCodeName]; + if (!archetypeKey) { + throw new Error(`No archetype found for ${neftyCodeName}`); + } + return archetypeKey; + } + + /** + * Returns rarity from stats average + * @param statsAverage average of all stats, from 0 to 100; + */ + getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true, grade: Grade = 'prime'): Rarity | null { + const rarity = Object.entries(raritiesRead).find(([rarity, rarityInfo]) => { + return ( + statsAverage >= rarityInfo.average_stats_range[0] && + ((statsAverage === 100 && statsAverage === rarityInfo.average_stats_range[1]) || + statsAverage < rarityInfo.average_stats_range[1]) + ); + }); + if (!rarity) { + if (raiseErrorOnNotFound) throw new Error(`Rarity not found for stats average ${statsAverage}`); + else return null; + } + return rarity[0] as Rarity; + } + + // generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { + // if (!Number.isInteger(parseInt(archetypeIndex))) { + // throw new Error(`Invalid archetype index: ${archetypeIndex}`); + // } + // const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + // const dnaData = {} as DnaDataV2; + // dnaData.version = dnaSchema.version; + + // const dataData = {} as DnaDataData; + // dataData.grade = grade; + // dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); + // dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; + // if (!dataData.neftyCodeName) { + // throw new Error(`No archetype found for index ${archetypeIndex}`); + // } + // dnaData.data = dataData; + + // const dataAdv = {} as ParseDataPerc; + // const [hp, atk, def, speed] = this._generateStatsForRarity(N_STATS_SOT, grade, dataData.rarity); + // Object.assign(dataAdv, { hp, atk, def, speed }); + // dnaData.dataAdv = dataAdv; + + // const dna = this.getDna(dnaSchema.version, dnaData); + // return dna; + // } + + // generateStarterNeftyDNA(archetypeIndex: string, version?: string) { + // if (!Number.isInteger(parseInt(archetypeIndex))) { + // throw new Error(`Invalid archetype index: ${archetypeIndex}`); + // } + // const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + // const dnaData = {} as DnaDataV2; + // dnaData.version = dnaSchema.version; + + // const dataData = {} as DnaDataData; + // dataData.grade = 'standard'; + // dataData.rarity = 'Uncommon'; + // dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; + // if (!dataData.neftyCodeName) { + // throw new Error(`No archetype found for index ${archetypeIndex}`); + // } + // dnaData.data = dataData; + + // const dataAdv = {} as ParseDataPerc; + // const [hp, atk, def, speed] = Array(N_STATS_SOT).fill(30); + // Object.assign(dataAdv, { hp, atk, def, speed }); + // dnaData.dataAdv = dataAdv; + + // const dna = this.getDna(dnaSchema.version, dnaData); + // return dna; + // } + + // generateNeftyDNAFromV1Dna( + // dnaFactoryV1: DNAFactoryV1, + // v1Dna: string, + // newSotStats?: ParseDataPerc, + // newVersion?: version + // ) { + // const dataV1 = dnaFactoryV1.parse(v1Dna); + // const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); + // const dnaData = {} as DnaDataV2; + // const dataAdv = {} as ParseDataPerc; + // if (newSotStats) { + // const { hp: hpP, atk: atkP, def: defP, speed: speedP } = newSotStats; + // Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + // } else { + // const { hp: hpP, atk: atkP, def: defP, speed: speedP } = dataV1.dataAdv; + // Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + // } + // dnaData.dataAdv = dataAdv; + // const dataData = {} as DnaDataData; + // dataData.grade = dataV1.data.grade; + // dataData.rarity = dataV1.data.rarity; + // dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; + // dnaData.data = dataData; + // dnaData.version = dnaSchema.version; + // const dna = this.getDna(dnaSchema.version, dnaData); + // return dna; + // } + + parse(dnaString: string): ParseV2 { + const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); + const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); + debugger; + const dataAdv = Object.assign( + {}, + dataRaw.dataAdv, + this.computeSOTStats(dataRaw.data.neftyCodeName, dataRaw.dataAdv) + ); + const displayName = neftiesInfo.code_to_displayName[dataRaw.data.neftyCodeName]; + const data = Object.assign(dataRaw.data, { displayName }); + const parsed: ParseV2 = Object.assign({ version: dataRaw.version }, { dataAdv, dataRaw, data }); + return parsed; + } + + generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { + this.validateArchetypeIndex(archetypeIndex); + const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + + const dnaData = this.initializeDnaData(dnaSchema.version); + dnaData.data = this.createDataData(dnaSchema, archetypeIndex, grade, rarityPreset); + + const stats = this._generateStatsForRarity(N_STATS_SOT, grade, dnaData.data.rarity); + dnaData.dataAdv = this.createDataAdv(stats); + + return this.getDna(dnaSchema.version, dnaData); + } + + generateStarterNeftyDNA(archetypeIndex: string, version?: string) { + this.validateArchetypeIndex(archetypeIndex); + const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + + const dnaData = this.initializeDnaData(dnaSchema.version); + dnaData.data = this.createDataData(dnaSchema, archetypeIndex, 'standard', 'Uncommon'); + + const stats = Array(N_STATS_SOT).fill(30); + dnaData.dataAdv = this.createDataAdv(stats); + + return this.getDna(dnaSchema.version, dnaData); } generateNeftyDNAFromV1Dna( dnaFactoryV1: DNAFactoryV1, v1Dna: string, newSotStats?: ParseDataPerc, - newVersion?: version + newVersion?: string ) { const dataV1 = dnaFactoryV1.parse(v1Dna); const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); - const dnaData = {} as DnaDataV2; - const dataAdv = {} as ParseDataPerc; - if (newSotStats) { - const { hp: hpP, atk: atkP, def: defP, speed: speedP } = newSotStats; - Object.assign(dataAdv, { hpP, atkP, defP, speedP }); - } else { - const { hp: hpP, atk: atkP, def: defP, speed: speedP } = dataV1.dataAdv; - Object.assign(dataAdv, { hpP, atkP, defP, speedP }); - } - dnaData.dataAdv = dataAdv; - const dataData = {} as DnaDataData; - dataData.grade = dataV1.data.grade; - dataData.rarity = dataV1.data.rarity; - dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; - dnaData.data = dataData; - dnaData.version = dnaSchema.version; - const dna = this.getDna(dnaSchema.version, dnaData); - return dna; - } - parse(dnaString: string): ParseV2 { - const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); - const data = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); - const computedStats: ParseDataComputed = this.computeSOTStats(data.data.neftyCodeName, data.dataAdv); - Object.assign(data.dataAdv, computedStats); - return data as ParseV2; - } + const dnaData = this.initializeDnaData(dnaSchema.version); + dnaData.data = this.createDataDataFromV1(dataV1); - private computeSOTStats(neftyCodeName: NeftyCodeName, dataAdv: ParseDataPerc): ParseDataComputed { - const neftyCodenameId = TACTICS_ADV_NAMES_MAP[neftyCodeName]; - const computed = {} as ParseDataComputed; - const sotStatsCurrent = adventuresStats[LATEST_ADVENTURES_STATS_VERSION].nefties[neftyCodenameId]; - if (!sotStatsCurrent) throw new Error(`No SOT stats found for ${neftyCodenameId}`); - Object.entries(dataAdv).forEach(([statName, percentage]) => { - const key = `${statName}Computed` as keyof ParseDataComputed; - const minK = `${statName}Min` as keyof AdvStatsJSONValue; - const min = sotStatsCurrent[minK]; - const maxK = `${statName}Max` as keyof AdvStatsJSONValue; - const max = sotStatsCurrent[maxK]; - const value = (percentage / 100) * (max - min) + min; - computed[key] = Math.round(value); - }); - return computed; + const stats = newSotStats ?? dataV1.dataAdv; + dnaData.dataAdv = this.createDataAdvFromV1(stats); + + return this.getDna(dnaSchema.version, dnaData); } } diff --git a/ts/src/eggs_factory_v2.ts b/ts/src/eggs_factory_v2.ts index 3110b94..aac866e 100644 --- a/ts/src/eggs_factory_v2.ts +++ b/ts/src/eggs_factory_v2.ts @@ -32,7 +32,7 @@ export class EggsFactoryV2 { } getDroppableStandardNefties(): DroppableNeftyInfoV2[] { - return this.eggInfo.archetypes.map((neftyCodeName) => { + return this.standardEggInfo.archetypes.map((neftyCodeName) => { const r = {} as DroppableNeftyInfoV2; r.neftyCodeName = neftyCodeName; r.displayName = neftiesInfo.code_to_displayName[neftyCodeName] as string; @@ -43,14 +43,14 @@ export class EggsFactoryV2 { hatch(): { archetypeKey: string; neftyCodeName: NeftyCodeName } { const droppableArchetypes = this.eggInfo.archetypes; const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; - const archetypeKey = neftyCodeName; + const archetypeKey = this.df.getArchetypeKeyByNeftyCodeName(neftyCodeName); return { archetypeKey, neftyCodeName }; } hatchStandard(): { archetypeKey: string; neftyCodeName: NeftyCodeName } { const droppableArchetypes = this.standardEggInfo.archetypes; const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; - const archetypeKey = neftyCodeName; + const archetypeKey = this.df.getArchetypeKeyByNeftyCodeName(neftyCodeName); return { archetypeKey, neftyCodeName }; } } diff --git a/ts/src/interfaces/types.ts b/ts/src/interfaces/types.ts index 83161e5..04edd96 100644 --- a/ts/src/interfaces/types.ts +++ b/ts/src/interfaces/types.ts @@ -214,7 +214,7 @@ export interface DroppableNeftyInfo { } export interface DroppableNeftyInfoV2 { - neftyCodeName: string; + neftyCodeName: NeftyCodeName; displayName: string; } @@ -233,6 +233,10 @@ export interface DnaDataData { neftyCodeName: NeftyCodeName; } +export interface DnaDataDataParsed extends DnaDataData { + displayName: string; +} + export interface DnaDataV2 { version: string; data: DnaDataData; @@ -241,6 +245,7 @@ export interface DnaDataV2 { export interface ParseV2 { version: string; - data: DnaDataData; + dataRaw: DnaDataV2; + data: DnaDataDataParsed; dataAdv: ParseDataAdv; } diff --git a/ts/tests/dna.v2.spec.ts b/ts/tests/dna.v2.spec.ts index 4751a02..5c46520 100644 --- a/ts/tests/dna.v2.spec.ts +++ b/ts/tests/dna.v2.spec.ts @@ -1,11 +1,190 @@ -import { DNAFactory } from '../src'; +import { DNAFactory, EggsFactory, Rarity, utils } from '../src'; +import nefties_info from '../src/deps/nefties_info.json'; +import assert from 'assert'; +import raritiesGeneration from '../src/deps/rarities_generation.json'; +import { readdirSync, readFileSync } from 'fs'; +import { DNASchema, DNASchemaV4, NeftyCodeName, ParseDataPerc } from '../src/interfaces/types'; +import { LAST_SUPPORTED_VERSION_BY_V1, TACTICS_ADV_NAMES_MAP } from '../src/constants'; +import { LATEST_VERSION } from '../src/deps/schemas/latest'; + +const displayNamesProd = [ + 'Axobubble', + 'Bitebit', + 'Dipking', + 'Dinobit', + 'Shiba Ignite', + 'Zzoo', + 'Block Choy', + 'Number 9', + 'Unika', + 'Chocomint', + 'Cybertooth', + 'Wassie', + 'Dracurve', + 'Raccoin', + 'Shibark', + 'Unikirin', + 'Beeblock', + 'Chocorex', + 'Keybab', + 'Bloomtail', +]; + +const allSchemaVersions = readdirSync('./src/deps/schemas') + .filter((v) => v.endsWith('json')) + .map((v) => { + const index = v.indexOf('_v'); + return v.slice(index + 2, index + 7); + }) + .filter((v) => parseInt(v.split('.')[0]) > 3); describe('Basic', () => { - it('should work', () => { + it('DNA should parse', () => { + const df = new DNAFactory(); + Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk, eggInfo]) => { + const ef = new EggsFactory(eggPk, df); + const droppableNefties = ef.getDroppableNefties(); + const schema = df.getDNASchema(LATEST_VERSION); + const archetypes = Object.entries(schema.archetypes); + droppableNefties.forEach(({ neftyCodeName, displayName }) => { + assert.ok(displayName); + assert.ok(neftyCodeName); + assert.ok(TACTICS_ADV_NAMES_MAP[neftyCodeName]); + const archetypeKey = df.getArchetypeKeyByNeftyCodeName(neftyCodeName); + assert.ok(archetypeKey); + assert.ok(nefties_info.code_to_displayName[neftyCodeName]); + assert.ok( + (nefties_info.family_to_description as any)[ + nefties_info.code_to_displayName[neftyCodeName].replace(/\s+/g, '') + ], + `Family description not found for ${nefties_info.code_to_displayName[neftyCodeName].replace(/\s+/g, '')}` + ); + const dna = df.generateNeftyDNA(archetypeKey, 'prime'); + const data = df.parse(dna); + assert.ok(data.data); + assert.ok(data.data.grade); + assert.ok(data.data.neftyCodeName); + assert.ok(data.data.rarity); + assert.ok(data.dataAdv); + assert.ok(Number.isInteger(data.dataAdv.atk)); + assert.ok(Number.isInteger(data.dataAdv.def)); + assert.ok(Number.isInteger(data.dataAdv.hp)); + assert.ok(Number.isInteger(data.dataAdv.speed)); + assert.ok(Number.isInteger(data.dataAdv.atkComputed)); + assert.ok(Number.isInteger(data.dataAdv.defComputed)); + assert.ok(Number.isInteger(data.dataAdv.hpComputed)); + assert.ok(Number.isInteger(data.dataAdv.speedComputed)); + assert.ok(data.version); + }); + }); + }); + + // Display names are used to compute image URLs + it('Ensure display names never change', () => { + Object.values(nefties_info.code_to_displayName).forEach((displayName) => { + assert(displayNamesProd.includes(displayName), `${displayName} is not in displayNamesProd`); + }); + }); + + it('Generated Nefty DNA version matches forced version', () => { const df = new DNAFactory(); - const dna = df.generateNeftyDNA('0', 'prime'); - console.log(dna); - const parsed = df.parse(dna); - console.log(parsed); + const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); + for (let index = 0; index < allSchemaVersions.length; index++) { + const version = allSchemaVersions[index]; + const majorVersion = version.split('.')[0]; + const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime', version); + const parsed = df.parse(dna); + const parsedMajorVersion = parsed.version.split('.')[0]; + assert.equal(majorVersion, parsedMajorVersion); + } + }); +}); + +describe('Rarity', () => { + it('Rarity matches the average stats for latest version', () => { + const df = new DNAFactory(); + const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); + const rarityStats: (keyof ParseDataPerc)[] = ['hp', 'atk', 'def', 'speed']; + Object.entries(raritiesGeneration.prime).forEach(([rarity, rarityInfo]) => { + // for (let i = 0; i < 1e3; i++) { + const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime', undefined, rarity as Rarity); + const parsed = df.parse(dna); + assert.deepEqual(parsed.data.rarity, rarity); + const statsAvg = + utils.getAverageFromRaw( + rarityStats.map((v) => parsed.dataAdv[v]), + rarityStats.map((v) => 100) + ) * 100; + assert.deepEqual(df.getRarityFromStatsAvg(statsAvg), rarity); + assert.ok(statsAvg >= rarityInfo.average_stats_range[0]); + if (statsAvg === 100) assert.ok(statsAvg === rarityInfo.average_stats_range[1]); + else assert.ok(statsAvg < rarityInfo.average_stats_range[1]); + }); + }); +}); + +describe('starter eggs', () => { + it('Starter egg should be hatched with constant stats and rarity', () => { + const standardEggs = EggsFactory.getAllStandardEggs(); + const eggPk = Object.keys(standardEggs)[0]; + const df = new DNAFactory(); + const ef = new EggsFactory(eggPk, df); + for (let index = 0; index < 10; index++) { + const dna = df.generateStarterNeftyDNA(ef.hatchStandard().archetypeKey); + assert(dna); + const data = df.parse(dna); + const expectedRawStatValue = 30; + assert.equal(['Dipking', 'Block Choy', 'Number 9'].includes(data.data.displayName), true); + assert.equal(data.dataAdv.hp, expectedRawStatValue); + assert.equal(data.dataAdv.atk, expectedRawStatValue); + assert.equal(data.dataAdv.def, expectedRawStatValue); + assert.equal(data.dataAdv.speed, expectedRawStatValue); + } + }); +}); + +describe('standard eggs', () => { + it('all standard eggs should be hatchable', () => { + const df = new DNAFactory(); + const standardEggs = EggsFactory.getAllStandardEggs(); + Object.keys(standardEggs).forEach((eggName) => { + const ef = new EggsFactory(eggName, df); + const archetypeKey = ef.hatchStandard().archetypeKey; + if (eggName === 'starter_egg') { + const dna = df.generateStarterNeftyDNA(archetypeKey); + assert(dna); + const parsedDna = df.parse(dna); + assert.equal(standardEggs[eggName].archetypes.includes(parsedDna.data.neftyCodeName), true); + } else { + const dna = df.generateNeftyDNA(archetypeKey, 'standard'); + assert(dna); + const parsedDna = df.parse(dna); + assert.equal(standardEggs[eggName].archetypes.includes(parsedDna.data.neftyCodeName), true); + } + }); + }); +}); + +describe('droppable nefties', () => { + const df = new DNAFactory(); + it('all standard eggs archetypes are droppable', () => { + const standardEggs = EggsFactory.getAllStandardEggs(); + Object.keys(standardEggs).forEach((eggName) => { + const ef = new EggsFactory(eggName, df); + const droppableStandardNefties = ef.getDroppableStandardNefties(); + + assert(droppableStandardNefties); + assert.equal(droppableStandardNefties.length, standardEggs[eggName].archetypes.length); + }); + }); + it('all prime eggs neftie archetypes are droppable', () => { + const primeEggs = EggsFactory.getAllEggs(); + Object.keys(primeEggs).forEach((eggName) => { + const ef = new EggsFactory(eggName, df); + const droppableNefties = ef.getDroppableNefties(); + + assert(droppableNefties); + assert.equal(droppableNefties.length, primeEggs[eggName].archetypes.length); + }); }); }); diff --git a/ts/yarn.lock b/ts/yarn.lock index 9280e3e..83384b0 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -608,11 +608,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -compress-json@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/compress-json/-/compress-json-3.1.0.tgz#337e0a5b28b180fb849bcb6913bf361a5b3c0d57" - integrity sha512-Zcq4jRC5ZpfaOY3mbBWOANtGuMHJ/hsTENcwN1/lEkrogcoAF7HBma1RLe/CICZO6IquK1U0EaPzmnlDIFRNjA== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1645,6 +1640,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -1737,11 +1737,6 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -nanoid@^5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6" - integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" From 80568b0d6f5a540d6524ed9327192142aac8dd92 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:57:05 +0200 Subject: [PATCH 06/22] Remove unused code --- ts/src/dna_factory_v2.ts | 92 +--------------------------------------- 1 file changed, 1 insertion(+), 91 deletions(-) diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index c1c1c2d..b88cf7e 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -15,18 +15,8 @@ import { import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from './deps/schemas/latest'; import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; import dnaSchemaV4_0 from './deps/schemas/aurory_dna_v4.0.0.json'; -import { - getAverageFromRaw, - getCategoryKeyFromName, - getLatestSubversion, - randomInt, - randomNormal, - toPaddedHexa, - toUnPaddedHexa, - unpad, -} from './utils'; +import { getAverageFromRaw, getLatestSubversion, randomInt, randomNormal, toPaddedHexa, toUnPaddedHexa } from './utils'; import raritiesGeneration from './deps/rarities_generation.json'; -import zlib from 'zlib'; import { N_STATS_SOT, TACTICS_ADV_NAMES_MAP, VERSION_LENGTH } from './constants'; import { DNAFactoryV1 } from './dna_factory_v1'; import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; @@ -259,86 +249,6 @@ export class DNAFactoryV2 { return rarity[0] as Rarity; } - // generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { - // if (!Number.isInteger(parseInt(archetypeIndex))) { - // throw new Error(`Invalid archetype index: ${archetypeIndex}`); - // } - // const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); - // const dnaData = {} as DnaDataV2; - // dnaData.version = dnaSchema.version; - - // const dataData = {} as DnaDataData; - // dataData.grade = grade; - // dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); - // dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; - // if (!dataData.neftyCodeName) { - // throw new Error(`No archetype found for index ${archetypeIndex}`); - // } - // dnaData.data = dataData; - - // const dataAdv = {} as ParseDataPerc; - // const [hp, atk, def, speed] = this._generateStatsForRarity(N_STATS_SOT, grade, dataData.rarity); - // Object.assign(dataAdv, { hp, atk, def, speed }); - // dnaData.dataAdv = dataAdv; - - // const dna = this.getDna(dnaSchema.version, dnaData); - // return dna; - // } - - // generateStarterNeftyDNA(archetypeIndex: string, version?: string) { - // if (!Number.isInteger(parseInt(archetypeIndex))) { - // throw new Error(`Invalid archetype index: ${archetypeIndex}`); - // } - // const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); - // const dnaData = {} as DnaDataV2; - // dnaData.version = dnaSchema.version; - - // const dataData = {} as DnaDataData; - // dataData.grade = 'standard'; - // dataData.rarity = 'Uncommon'; - // dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; - // if (!dataData.neftyCodeName) { - // throw new Error(`No archetype found for index ${archetypeIndex}`); - // } - // dnaData.data = dataData; - - // const dataAdv = {} as ParseDataPerc; - // const [hp, atk, def, speed] = Array(N_STATS_SOT).fill(30); - // Object.assign(dataAdv, { hp, atk, def, speed }); - // dnaData.dataAdv = dataAdv; - - // const dna = this.getDna(dnaSchema.version, dnaData); - // return dna; - // } - - // generateNeftyDNAFromV1Dna( - // dnaFactoryV1: DNAFactoryV1, - // v1Dna: string, - // newSotStats?: ParseDataPerc, - // newVersion?: version - // ) { - // const dataV1 = dnaFactoryV1.parse(v1Dna); - // const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); - // const dnaData = {} as DnaDataV2; - // const dataAdv = {} as ParseDataPerc; - // if (newSotStats) { - // const { hp: hpP, atk: atkP, def: defP, speed: speedP } = newSotStats; - // Object.assign(dataAdv, { hpP, atkP, defP, speedP }); - // } else { - // const { hp: hpP, atk: atkP, def: defP, speed: speedP } = dataV1.dataAdv; - // Object.assign(dataAdv, { hpP, atkP, defP, speedP }); - // } - // dnaData.dataAdv = dataAdv; - // const dataData = {} as DnaDataData; - // dataData.grade = dataV1.data.grade; - // dataData.rarity = dataV1.data.rarity; - // dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; - // dnaData.data = dataData; - // dnaData.version = dnaSchema.version; - // const dna = this.getDna(dnaSchema.version, dnaData); - // return dna; - // } - parse(dnaString: string): ParseV2 { const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); From ecc9ae7c4abc6fdc64b15daa96609a529b6d9bcf Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:03:54 +0200 Subject: [PATCH 07/22] Remove unused code --- ts/src/adventure_stats.ts | 96 ------------------------ ts/src/deps/nefties_info_deprecated.json | 46 ++++++++++++ ts/src/dna_factory_v1.ts | 16 ++-- ts/src/dna_factory_v2.ts | 26 ++++--- ts/tests/distribution.spec.ts | 12 +-- ts/tests/dna.spec.ts | 20 +++-- ts/tests/dna.v2.spec.ts | 14 ++-- ts/tests/workers/distribution-worker.ts | 12 +-- ts/tests/workers/stats-mean-worker.ts | 14 ++-- 9 files changed, 105 insertions(+), 151 deletions(-) create mode 100644 ts/src/deps/nefties_info_deprecated.json diff --git a/ts/src/adventure_stats.ts b/ts/src/adventure_stats.ts index 205737a..4d5785d 100644 --- a/ts/src/adventure_stats.ts +++ b/ts/src/adventure_stats.ts @@ -7,105 +7,9 @@ import { AdvStatsJSON, ParseDataPerc, AdvStatsJSONValue, - NeftyCodeNameId, NeftyCodeName, } from './interfaces/types'; -/** - * Allow to pick a random number from an array, but in a deterministic way. - * The same input array will produce the same output number. - */ -function deterministicRandomPicker(arr: number[]): number { - const deterministicRandoms = arr.map((value, index, array) => { - const prevIndex = index === 0 ? array.length - 1 : index - 1; - const nextIndex = index === array.length - 1 ? 0 : index + 1; - return (array[prevIndex] + array[nextIndex] + value) % array.length; - }); - arr.sort((a, b) => { - const indexA = arr.indexOf(a); - const indexB = arr.indexOf(b); - return deterministicRandoms[indexA] - deterministicRandoms[indexB]; - }); - return arr[0]; -} - -function removeGlitched(targetAverage: number, adventuresStatsOriginal: number[]): number[] { - const adventuresStats = [...adventuresStatsOriginal]; - const maxNum = Math.max(...adventuresStats); - const maxIndex = adventuresStats.indexOf(maxNum); - adventuresStats[maxIndex] = 6; - - while (floorAverage(adventuresStats) !== targetAverage) { - const validIndices = adventuresStats.reduce((indices, stat, index) => { - if (stat !== 0 && index !== maxIndex) { - indices.push(index); - } - return indices; - }, [] as number[]); - - const indexToModify = deterministicRandomPicker(validIndices); - adventuresStats[indexToModify] -= 1; - } - - return adventuresStats; -} - -function makeGlitched(targetAverage: number, adventuresStatsOriginal: number[]): number[] { - const adventuresStats = adventuresStatsOriginal.map((num) => (num > 5 ? 5 : num)); - - while (floorAverage(adventuresStats) !== targetAverage) { - const validIndices = adventuresStats.reduce((indices, stat, index) => { - if (stat !== 5) { - indices.push(index); - } - return indices; - }, [] as number[]); - - const indexToModify = deterministicRandomPicker(validIndices); - adventuresStats[indexToModify] += 1; - } - - return adventuresStats; -} - -function makeSchimmering(targetAverage: number, adventuresStatsOriginal: number[]): number[] { - const adventuresStats = adventuresStatsOriginal.map((num) => (num < 95 ? 95 : num)); - while (floorAverage(adventuresStats) !== targetAverage) { - const validIndices = adventuresStats.reduce((indices, stat, index) => { - if (stat !== 95) { - indices.push(index); - } - return indices; - }, [] as number[]); - - const indexToModify = deterministicRandomPicker(validIndices); - adventuresStats[indexToModify] -= 1; - } - - return adventuresStats; -} - -function removeSchimmering(targetAverage: number, adventuresStatsOriginal: number[]): number[] { - const adventuresStats = [...adventuresStatsOriginal]; - const min = Math.min(...adventuresStats); - const minIndex = adventuresStats.indexOf(min); - adventuresStats[minIndex] = 94; - - while (floorAverage(adventuresStats) !== targetAverage) { - const validIndices = adventuresStats.reduce((indices, stat, index) => { - if (stat !== 100 && index !== minIndex) { - indices.push(index); - } - return indices; - }, [] as number[]); - - const indexToModify = deterministicRandomPicker(validIndices); - adventuresStats[indexToModify] += 1; - } - - return adventuresStats; -} - function floorAverage(stats: number[]): number { return Math.floor(stats.reduce((sum, stat) => sum + Math.round(stat), 0) / stats.length); } diff --git a/ts/src/deps/nefties_info_deprecated.json b/ts/src/deps/nefties_info_deprecated.json new file mode 100644 index 0000000..270addb --- /dev/null +++ b/ts/src/deps/nefties_info_deprecated.json @@ -0,0 +1,46 @@ +{ + "code_to_displayName": { + "Nefty_Bitebit": "Bitebit", + "Nefty_Dipking": "Dipking", + "Nefty_Dinobit": "Dinobit", + "Nefty_ShibaIgnite": "Shiba Ignite", + "Nefty_Zzoo": "Zzoo", + "Nefty_Blockchoy": "Block Choy", + "Nefty_Number9": "Number 9", + "Nefty_Axobubble": "Axobubble", + "Nefty_Unika": "Unika", + "Nefty_Chocomint": "Chocomint", + "Nefty_Cybertooth": "Cybertooth", + "Nefty_Wassie": "Wassie", + "Nefty_Dracurve": "Dracurve", + "Nefty_Raccoin": "Raccoin", + "Nefty_Shibark": "Shibark", + "Nefty_Unikirin": "Unikirin", + "Nefty_Beeblock": "Beeblock", + "Nefty_Chocorex": "Chocorex", + "Nefty_Keybab": "Keybab", + "Nefty_Bloomtail": "Bloomtail" + }, + "family_to_description": { + "Bitebit": "Bitebit may look cuddly, but don't be fooled by its charm - those aren't paper hands... they're DIAMOND CLAWS!", + "Dipking": "Dipking is bursting with personality and magical power, but handle with care - all that pent up energy may have EXPLOSIVE results!", + "Dinobit": "Dinobit is big, strong and tough, but careful of that temper - Make it mad and watch out for a charge that can PLOW THROUGH EVERYTHING in its path!", + "Shiba": "Shiba Ignite is always ready to jump into the heat of battle, but while it may not be the fastest - a helping paw is always there to DEFEND ITS ALLIES.", + "Zzoo": "Zzoo has a big beak and a bad attitude, but if it's on your side, both can be an asset - SWIFT STRIKES make its mean streak your advantage!", + "Blockchoy": "Block Choy may look tasty, but its real gift is far more delicious - a menu of healing powers is standing by to RE-FUEL its allies.", + "Number9": "Number 9 is a solid choice despite appearances, but remember - it has to FLY THROUGH ENEMIES before it can attack them.", + "Axobubble": "Axobubble is a born defender, but its magic is sneaky - buffs, curses and other mischief are sure to bubble up to HELP THE TEAM.", + "Unika": "Unika is cool under pressure, but don't let its delicate appearance confuse you - it's ready with an ICE COLD STRIKE!", + "Chocomint": "Chocomints are born with a remnant of their shells on their heads. To pass into adulthood, a joust between two young Chocomints takes place. The victor is the one who cracks its shell to unveil its cherry.", + "Cybertooth": "Cybertooth uses its thick hide to fortify its defenses, skillfully weakening foes with its abilities to gain an advantage and deal significant damage.", + "Wassie": "Wassie is a nimble Neftie known for harnessing its abilities to enhance its speed and inflict substantial damage.", + "Dracurve": "Dracurve dictates the course of battle with shifts in stats and status effects. Exercise caution, for a few strategic choices can swiftly pave the way to devastation.", + "Raccoin": "Raccoin's a sly, laid-back Neftie, known for its nimbleness and love for naps.", + "Shibark": "Shibark is known to roam vast landscapes, unearthing treasures and marking its territory with resounding howls.", + "Unikirin": "Each step Unikirin takes resonates with the crackle of electric energy, a testament to its wild and untamed spirit.", + "Beeblock": "Beeblock, known for its calm nature, is tamed for its production of a deliciously sweet jelly named Nury. But when threatened, it delivers powerful stings, warding off any attacker.", + "Chocorex": "Praised for its loyalty, Chocorex stands as a favored companion and trusted mount within Tokane. However, due to its limited intelligence, it requires skilled riders.", + "Keybab": "When activating its self-defense mechanism, Keybab changes color to boiling red, and its spicy vapor makes anyone who inhales it shed tears.", + "Bloomtail": "Bloomtail is a spirited Neftie fueled by the flower adorning its back. It is said that the flower occasionally affects its movements, adding an intriguing twist to its nature!" + } +} diff --git a/ts/src/dna_factory_v1.ts b/ts/src/dna_factory_v1.ts index 7644569..ba7584c 100644 --- a/ts/src/dna_factory_v1.ts +++ b/ts/src/dna_factory_v1.ts @@ -36,7 +36,7 @@ import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; import { LATEST_VERSION as LATEST_ABILTIIES_VERSION } from './deps/dictionaries/latest'; import abiltiesDictionaryV4 from './deps/dictionaries/abilities_dictionary_v0.4.0.json'; -import neftiesInfo from './deps/nefties_info.json'; +import neftiesInfo from './deps/nefties_info_deprecated.json'; import raritiesGeneration from './deps/rarities_generation.json'; import raritiesRead from './deps/rarities_read.json'; import { DNA } from './dna'; @@ -282,7 +282,7 @@ export class DNAFactoryV1 { rarityPreset?: Rarity ) { const rarity = rarityPreset ?? this._getRandomRarity(grade); - const rarityIndex = Object.entries(dnaSchema.rarities).find(([_, rarityName]) => rarityName === rarity)?.[0]; + const rarityIndex = Object.entries(dnaSchema.rarities).find(([, rarityName]) => rarityName === rarity)?.[0]; if (!rarityIndex) throw new Error('Rarity not found'); const categoryKey = getCategoryKeyFromName('nefties', dnaSchema.categories); @@ -351,7 +351,7 @@ export class DNAFactoryV1 { `Archetype index not found. archetypeIndex ${archetypeIndex} schemaVersion ${schemaVersion} categoryKey ${categoryKey}` ); const rarity = 'Uncommon'; - const rarityIndex = Object.entries(dnaSchema.rarities).find(([_, rarityName]) => rarityName === rarity)?.[0]; + const rarityIndex = Object.entries(dnaSchema.rarities).find(([, rarityName]) => rarityName === rarity)?.[0]; if (!rarityIndex) throw new Error('Rarity not found'); const versionGeneInfo = dnaSchema.global_genes_header.find((gene_header) => gene_header.name === 'version'); @@ -400,7 +400,7 @@ export class DNAFactoryV1 { const abilityKeywords = this.getAbilitiesDictionary(version ?? this.latestAbilitiesVersion).keywords; const info = {} as AbilityInfo; for (const keyword in abilityKeywords) { - const [_, abilityName, infoType] = keyword.split('.'); + const [, abilityName, infoType] = keyword.split('.'); if (abilityName !== ability) continue; info[infoType as keyof AbilityInfo] = abilityKeywords[keyword as KeywordsKey]; if (info.name && info.description) return info; @@ -420,8 +420,8 @@ export class DNAFactoryV1 { * Returns rarity from stats average * @param statsAverage average of all stats, from 0 to 100; */ - getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true, grade: Grade = 'prime'): Rarity | null { - const rarity = Object.entries(this.raritiesRead).find(([rarity, rarityInfo]) => { + getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true): Rarity | null { + const rarity = Object.entries(this.raritiesRead).find(([, rarityInfo]) => { return ( statsAverage >= rarityInfo.average_stats_range[0] && ((statsAverage === 100 && statsAverage === rarityInfo.average_stats_range[1]) || @@ -461,7 +461,7 @@ export class DNAFactoryV1 { const genes = dnaSchema.categories[dnaSchemaReader.categoryKey].genes; const data: ParseData = Object.assign({} as ParseData, archetype.fixed_attributes); this._setRarity(data, dnaSchemaReader, dnaSchema); - this._setGrade(data, dnaSchemaReader, dnaSchema); + this._setGrade(data, dnaSchemaReader); const neftyNameCode = archetype.fixed_attributes.name as string; data['displayName'] = this.getDisplayNameFromCodeName(neftyNameCode); data['description'] = this.getFamilyDescription(archetype.fixed_attributes.family as string); @@ -512,7 +512,7 @@ export class DNAFactoryV1 { } } - private _setGrade(data: ParseData, dnaSchemaReader: DNASchemaReader, dnaSchema: DNASchema) { + private _setGrade(data: ParseData, dnaSchemaReader: DNASchemaReader) { if (dnaSchemaReader.categoryGenesHeader.grade) { // grade needs to be added to the dna generation process to support this // data.grade = (dnaSchema as DNASchemaV3).grades[dnaSchemaReader.categoryGenesHeader.grade]; diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index b88cf7e..0a1ecb5 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -11,11 +11,12 @@ import { ParseDataComputed, AdvStatsJSON, AdvStatsJSONValue, + Parse, } from './interfaces/types'; import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from './deps/schemas/latest'; import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; import dnaSchemaV4_0 from './deps/schemas/aurory_dna_v4.0.0.json'; -import { getAverageFromRaw, getLatestSubversion, randomInt, randomNormal, toPaddedHexa, toUnPaddedHexa } from './utils'; +import { getAverageFromRaw, getLatestSubversion, randomInt, randomNormal, toPaddedHexa } from './utils'; import raritiesGeneration from './deps/rarities_generation.json'; import { N_STATS_SOT, TACTICS_ADV_NAMES_MAP, VERSION_LENGTH } from './constants'; import { DNAFactoryV1 } from './dna_factory_v1'; @@ -37,7 +38,7 @@ export class DNAFactoryV2 { private codeNameToKey: Record; constructor() { this.latestSchemasSubversions = {}; - this.codeNameToKey = {} as any; + this.codeNameToKey = {} as Record; Object.entries(this.getDNASchema(LATEST_SCHEMA_VERSION).archetypes).forEach(([key, codename]) => { this.codeNameToKey[codename as NeftyCodeName] = key; }); @@ -139,12 +140,12 @@ export class DNAFactoryV2 { while (pointsLeft) { distributePoints(); - raw = stats.map((stat, index) => Math.round((stat / 100) * maxValuePerStat)); + raw = stats.map((stat) => Math.round((stat / 100) * maxValuePerStat)); average = Math.floor( getAverageFromRaw( raw, - stats.map((_, index) => maxValuePerStat) + stats.map(() => maxValuePerStat) ) * 100 ); @@ -153,7 +154,7 @@ export class DNAFactoryV2 { } // if average = 1 for a non glitched or 95 for a schimmering, we may end up not enterring in the previous loop - if (!raw.length) raw = stats.map((stat, index) => Math.round((stat / 100) * maxValuePerStat)); + if (!raw.length) raw = stats.map((stat) => Math.round((stat / 100) * maxValuePerStat)); return raw; } @@ -189,7 +190,12 @@ export class DNAFactoryV2 { return dnaData; } - private createDataData(dnaSchema: any, archetypeIndex: string, grade: Grade, rarityPreset?: Rarity): DnaDataData { + private createDataData( + dnaSchema: DNASchemaV4, + archetypeIndex: string, + grade: Grade, + rarityPreset?: Rarity + ): DnaDataData { const dataData = {} as DnaDataData; dataData.grade = grade; dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); @@ -200,7 +206,7 @@ export class DNAFactoryV2 { return dataData; } - private createDataDataFromV1(dataV1: any): DnaDataData { + private createDataDataFromV1(dataV1: Parse): DnaDataData { const dataData = {} as DnaDataData; dataData.grade = dataV1.data.grade; dataData.rarity = dataV1.data.rarity; @@ -234,8 +240,8 @@ export class DNAFactoryV2 { * Returns rarity from stats average * @param statsAverage average of all stats, from 0 to 100; */ - getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true, grade: Grade = 'prime'): Rarity | null { - const rarity = Object.entries(raritiesRead).find(([rarity, rarityInfo]) => { + getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true): Rarity | null { + const rarity = Object.entries(raritiesRead).find(([, rarityInfo]) => { return ( statsAverage >= rarityInfo.average_stats_range[0] && ((statsAverage === 100 && statsAverage === rarityInfo.average_stats_range[1]) || @@ -250,7 +256,7 @@ export class DNAFactoryV2 { } parse(dnaString: string): ParseV2 { - const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); + // const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); debugger; const dataAdv = Object.assign( diff --git a/ts/tests/distribution.spec.ts b/ts/tests/distribution.spec.ts index 219274e..ecc5bf0 100644 --- a/ts/tests/distribution.spec.ts +++ b/ts/tests/distribution.spec.ts @@ -22,7 +22,7 @@ describe('Distribution', () => { * then checks if the deviation from the expected distribution is less than 20%. */ it('Means are evenly distributed for prime', (done) => { - const statMeans = {} as any; + const statMeans = {} as Record; const loopCount = 15000; let loopDone = 0; const workers = [] as { id: number; worker: Worker }[]; @@ -77,7 +77,7 @@ describe('Distribution', () => { }); it('Means are fixed for standard', (done) => { - const statMeans = {} as any; + const statMeans = {} as Record; const loopCount = 150; let loopDone = 0; const workers = [] as { id: number; worker: Worker }[]; @@ -115,7 +115,7 @@ describe('Distribution', () => { const expectedLegendary = loopCount / 20; console.log(statMeans); console.log(expectedCommon, expectedLegendary); - const expectedFull = loopCount / 19; + // const expectedFull = loopCount / 19; // Check less than 10% deviation from expected Object.entries(statMeans).forEach(([meanStr, count]) => { const mean = parseInt(meanStr); @@ -136,9 +136,9 @@ describe('Distribution', () => { const df = new DNAFactory(undefined, undefined); const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); const rarityStats = ['hp', 'initiative', 'atk', 'def', 'eatk', 'edef']; - const statsCount = {} as any; + const statsCount = {} as Record; const loopCount = 1000; - Object.entries(raritiesGeneration.prime).forEach(([rarity, rarityInfo]) => { + Object.entries(raritiesGeneration.prime).forEach(([rarity]) => { for (let i = 0; i < loopCount; i++) { const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime', undefined, rarity as Rarity); const parsed = df.parse(dna); @@ -156,7 +156,7 @@ describe('Distribution', () => { // this may fail sometimes as we only do 300k iterations it('Rarity & Glitched & Schimmering distribution rates are within a specific range of the targets', (done) => { - const rarityCount: Record = {} as any; + const rarityCount = {} as Record; const loopCount = 300000; let loopDone = 0; const workers = [] as { id: number; worker: Worker }[]; diff --git a/ts/tests/dna.spec.ts b/ts/tests/dna.spec.ts index f3e9922..0477c87 100644 --- a/ts/tests/dna.spec.ts +++ b/ts/tests/dna.spec.ts @@ -150,7 +150,7 @@ const allSchemaVersions = readdirSync('./src/deps/schemas') describe('Basic', () => { it('DNA should parse', () => { const df = new DNAFactory(); - Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk, eggInfo]) => { + Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk]) => { const ef = new EggsFactory(eggPk, df); const droppableNefties = ef.getDroppableNefties(); droppableNefties.forEach(({ archetypeKey, archetype, displayName, description }) => { @@ -169,10 +169,10 @@ describe('Basic', () => { assert.ok(data.data.description); assert.ok(data.data.rarity); assert.ok(data.data.defaultImage); - assert.ok(data.data.imageByGame); - assert.ok(data.data.imageByGame.tactics); - assert.ok(data.data.imageByGame.tactics.medium); - assert.ok(data.data.imageByGame.tactics.small); + // assert.ok(data.data.imageByGame); + // assert.ok(data.data.imageByGame.tactics); + // assert.ok(data.data.imageByGame.tactics.medium); + // assert.ok(data.data.imageByGame.tactics.small); assert.ok(Number.isInteger(data.data.hp)); assert.ok(Number.isInteger(data.data.initiative)); assert.ok(Number.isInteger(data.data.atk)); @@ -235,7 +235,6 @@ describe('Compute possible names, families and abilities', () => { const df = new DNAFactory(); const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); const neftyIndex = ef.hatch().archetypeKey; - debugger; const dna = df.generateNeftyDNA(neftyIndex, 'prime'); const category = df.getCategory('nefties', df.getDnaVersion(dna)); const neftyNames = new Set(); @@ -264,11 +263,10 @@ describe('Using previous schema 0.2.0', () => { it('Parsed stats should reflect the schema parameter as an input', () => { const forceVersion = '0.2.0'; const df = new DNAFactory(undefined, undefined); - const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); assert.throws(() => { // 7 does not exist on schema 0.2.0 const dna = df.generateNeftyDNA('7', 'prime', forceVersion); - const p = df.parse(dna, forceVersion); + df.parse(dna, forceVersion); }); const dinobitArchetypeIndex = '2'; const dna = df.generateNeftyDNA(dinobitArchetypeIndex, 'prime', forceVersion); @@ -301,7 +299,7 @@ describe('Rarity', () => { const statsAvg = utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100; assert.deepEqual(df.getRarityFromStatsAvg(statsAvg), rarity); assert.ok(statsAvg >= rarityInfo.average_stats_range[0]); @@ -323,11 +321,11 @@ describe('Rarity', () => { const statsAvg = utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100; assert.deepEqual(df.getRarityFromStatsAvg(statsAvg), rarity); assert.ok(statsAvg >= rarityInfo.average_stats_range[0]); - if (statsAvg === 100) assert.ok(statsAvg === rarityInfo.average_stats_range[1]); + if (statsAvg === 100) assert.ok(statsAvg === rarityInfo.average_stats_range[1] - 1); else assert.ok(statsAvg < rarityInfo.average_stats_range[1]); // } }); diff --git a/ts/tests/dna.v2.spec.ts b/ts/tests/dna.v2.spec.ts index 5c46520..336d921 100644 --- a/ts/tests/dna.v2.spec.ts +++ b/ts/tests/dna.v2.spec.ts @@ -2,10 +2,9 @@ import { DNAFactory, EggsFactory, Rarity, utils } from '../src'; import nefties_info from '../src/deps/nefties_info.json'; import assert from 'assert'; import raritiesGeneration from '../src/deps/rarities_generation.json'; -import { readdirSync, readFileSync } from 'fs'; -import { DNASchema, DNASchemaV4, NeftyCodeName, ParseDataPerc } from '../src/interfaces/types'; -import { LAST_SUPPORTED_VERSION_BY_V1, TACTICS_ADV_NAMES_MAP } from '../src/constants'; -import { LATEST_VERSION } from '../src/deps/schemas/latest'; +import { readdirSync } from 'fs'; +import { ParseDataPerc } from '../src/interfaces/types'; +import { TACTICS_ADV_NAMES_MAP } from '../src/constants'; const displayNamesProd = [ 'Axobubble', @@ -41,11 +40,9 @@ const allSchemaVersions = readdirSync('./src/deps/schemas') describe('Basic', () => { it('DNA should parse', () => { const df = new DNAFactory(); - Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk, eggInfo]) => { + Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk]) => { const ef = new EggsFactory(eggPk, df); const droppableNefties = ef.getDroppableNefties(); - const schema = df.getDNASchema(LATEST_VERSION); - const archetypes = Object.entries(schema.archetypes); droppableNefties.forEach(({ neftyCodeName, displayName }) => { assert.ok(displayName); assert.ok(neftyCodeName); @@ -54,6 +51,7 @@ describe('Basic', () => { assert.ok(archetypeKey); assert.ok(nefties_info.code_to_displayName[neftyCodeName]); assert.ok( + // eslint-disable-next-line @typescript-eslint/no-explicit-any (nefties_info.family_to_description as any)[ nefties_info.code_to_displayName[neftyCodeName].replace(/\s+/g, '') ], @@ -113,7 +111,7 @@ describe('Rarity', () => { const statsAvg = utils.getAverageFromRaw( rarityStats.map((v) => parsed.dataAdv[v]), - rarityStats.map((v) => 100) + rarityStats.map(() => 100) ) * 100; assert.deepEqual(df.getRarityFromStatsAvg(statsAvg), rarity); assert.ok(statsAvg >= rarityInfo.average_stats_range[0]); diff --git a/ts/tests/workers/distribution-worker.ts b/ts/tests/workers/distribution-worker.ts index bedaf02..d83dc80 100644 --- a/ts/tests/workers/distribution-worker.ts +++ b/ts/tests/workers/distribution-worker.ts @@ -1,6 +1,6 @@ import { parentPort, workerData } from 'worker_threads'; -import { DNAFactoryV1 as DNAFactory } from '../../src/index'; -import { EggsFactoryV1 as EggsFactory } from '../../src/index'; +import { DNAFactoryV1 as DNAFactory } from '../../src/dna_factory_v1'; +import { EggsFactoryV1 as EggsFactory } from '../../src/eggs_factory_v1'; import { Rarity } from '../../src/interfaces/types'; import { utils } from '../../src'; @@ -16,14 +16,14 @@ async function run(loopCount: number) { const rarityStats = ['hp', 'initiative', 'atk', 'def', 'eatk', 'edef']; let glitchedCount = 0; let schimmeringCount = 0; - const rarityCount: Record = {} as any; + const rarityCount = {} as Record; for (let i = 0; i < loopCount; i++) { const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime'); const parsed = df.parse(dna); const statsAvg = utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100; if (statsAvg < 6) { const stats = rarityStats.map((v) => Math.round((parsed.raw[v] / 255) * 100)); @@ -36,7 +36,9 @@ async function run(loopCount: number) { schimmeringCount += 1; } } - const rarity = df.getRarityFromStatsAvg(statsAvg, true, 'prime')!; + const rarity = df.getRarityFromStatsAvg(statsAvg, true); + if (!rarity) throw new Error('Rarity not found'); + if (rarityCount[rarity]) rarityCount[rarity] += 1; else rarityCount[rarity] = 1; } diff --git a/ts/tests/workers/stats-mean-worker.ts b/ts/tests/workers/stats-mean-worker.ts index 178dc61..e4c69b4 100644 --- a/ts/tests/workers/stats-mean-worker.ts +++ b/ts/tests/workers/stats-mean-worker.ts @@ -1,8 +1,8 @@ import { parentPort, workerData } from 'worker_threads'; -import { DNAFactoryV1 as DNAFactory } from '../../src/index'; -import { EggsFactoryV1 as EggsFactory } from '../../src/index'; +import { DNAFactoryV1 as DNAFactory } from '../../src/dna_factory_v1'; +import { EggsFactoryV1 as EggsFactory } from '../../src/eggs_factory_v1'; import { Grade, Rarity } from '../../src/interfaces/types'; -import { utils } from '../../src'; +import { utils } from '../../src/'; import raritiesGeneration from '../../src/deps/rarities_generation.json'; export interface StatsMeanWorker { @@ -18,7 +18,7 @@ async function run({ loopCount, grade }: Params): Promise { const df = new DNAFactory(undefined, undefined); const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); const rarityStats = ['hp', 'initiative', 'atk', 'def', 'eatk', 'edef']; - const statMeans = {} as any; + const statMeans = {} as Record; Object.entries(raritiesGeneration[grade]).forEach(([rarity]) => { for (let i = 0; i < loopCount; i++) { const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, grade, undefined, rarity as Rarity); @@ -26,13 +26,13 @@ async function run({ loopCount, grade }: Params): Promise { const statsMean = Math.floor( utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100 ); statMeans[statsMean.toString()] = statMeans[statsMean.toString()] ? statMeans[statsMean.toString()] + 1 : 1; } }); - const standardStatMeans = {} as any; + const standardStatMeans = {} as Record; Object.entries(raritiesGeneration.standard).forEach(([rarity]) => { for (let i = 0; i < loopCount; i++) { const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, grade, undefined, rarity as Rarity); @@ -40,7 +40,7 @@ async function run({ loopCount, grade }: Params): Promise { const statsMean = Math.floor( utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100 ); standardStatMeans[statsMean.toString()] = standardStatMeans[statsMean.toString()] From 6669babfa5eb6941ee090bce7054cb152a3edb33 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:20:53 +0200 Subject: [PATCH 08/22] Fix wrong adv stats names --- ts/src/dna_factory_v2.ts | 6 ++---- ts/tests/dna.v2.spec.ts | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index 0a1ecb5..2b84ae2 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -163,7 +163,6 @@ export class DNAFactoryV2 { const computed = {} as ParseDataComputed; const sotStatsCurrent = adventuresStats[LATEST_ADVENTURES_STATS_VERSION].nefties[neftyCodenameId]; if (!sotStatsCurrent) { - debugger; throw new Error(`No SOT stats found for ${neftyCodenameId}`); } Object.entries(dataAdv).forEach(([statName, percentage]) => { @@ -222,9 +221,9 @@ export class DNAFactoryV2 { } private createDataAdvFromV1(stats: ParseDataPerc): ParseDataPerc { - const { hp: hpP, atk: atkP, def: defP, speed: speedP } = stats; + const { hp, atk, def, speed } = stats; const dataAdv = {} as ParseDataPerc; - Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + Object.assign(dataAdv, { hp, atk, def, speed }); return dataAdv; } @@ -258,7 +257,6 @@ export class DNAFactoryV2 { parse(dnaString: string): ParseV2 { // const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); - debugger; const dataAdv = Object.assign( {}, dataRaw.dataAdv, diff --git a/ts/tests/dna.v2.spec.ts b/ts/tests/dna.v2.spec.ts index 336d921..f2451dd 100644 --- a/ts/tests/dna.v2.spec.ts +++ b/ts/tests/dna.v2.spec.ts @@ -1,4 +1,4 @@ -import { DNAFactory, EggsFactory, Rarity, utils } from '../src'; +import { DNAFactory, DNAFactoryV1, EggsFactory, Rarity, utils } from '../src'; import nefties_info from '../src/deps/nefties_info.json'; import assert from 'assert'; import raritiesGeneration from '../src/deps/rarities_generation.json'; @@ -186,3 +186,27 @@ describe('droppable nefties', () => { }); }); }); + +describe('From V1 DNA', () => { + const df = new DNAFactory(); + const dfV1 = new DNAFactoryV1(); + const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); + it('From V1 DNA', () => { + const advStatKeys: (keyof ParseDataPerc)[] = ['hp', 'atk', 'def', 'speed']; + const dnaV1 = dfV1.generateNeftyDNA(ef.hatch().archetypeKey, 'prime'); + const parsedV1 = dfV1.parse(dnaV1); + const newStats = {} as Record; + advStatKeys.forEach((key) => { + newStats[key] = Math.min(parsedV1.dataAdv[key] + 1, 100); + }); + const newDna = df.generateNeftyDNAFromV1Dna(dfV1, dnaV1, newStats); + const newParsed = df.parse(newDna); + assert.deepEqual(newParsed.dataAdv.hp, newStats.hp); + assert.deepEqual(newParsed.dataAdv.atk, newStats.atk); + assert.deepEqual(newParsed.dataAdv.def, newStats.def); + assert.deepEqual(newParsed.dataAdv.speed, newStats.speed); + assert.deepEqual(newParsed.data.rarity, parsedV1.data.rarity); + assert.deepEqual(newParsed.data.displayName, parsedV1.data.displayName); + assert.deepEqual(newParsed.data.neftyCodeName, parsedV1.archetype.fixed_attributes.name); + }); +}); From 04abc29c6aed3a3f512d9925d3410ad514c852c1 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:21:42 +0200 Subject: [PATCH 09/22] Update package.json version --- ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/package.json b/ts/package.json index 68c8958..b342665 100644 --- a/ts/package.json +++ b/ts/package.json @@ -1,6 +1,6 @@ { "name": "@aurory/dnajs", - "version": "0.8.1", + "version": "1.0.0", "repository": { "type": "git", "url": "git+https://github.com/Aurory-Game/dna.git" From ad9c8913b4bc5236c3bb6298ac8e990a107d238a Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:32:50 +0400 Subject: [PATCH 10/22] Add read-dna script --- ts/scripts/read-dna.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 ts/scripts/read-dna.ts diff --git a/ts/scripts/read-dna.ts b/ts/scripts/read-dna.ts new file mode 100644 index 0000000..58b10c0 --- /dev/null +++ b/ts/scripts/read-dna.ts @@ -0,0 +1,11 @@ +import { DNAFactory } from '../src'; + +function run() { + const dna = process.argv[2]; + const df = new DNAFactory(); + const data = df.parse(dna, '3.2.0'); + debugger; + console.log(data); +} + +run(); From 13af21152e00f6854ed66c07989d02d733837c2d Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:33:30 +0400 Subject: [PATCH 11/22] Update read dna script --- ts/scripts/read-dna.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/scripts/read-dna.ts b/ts/scripts/read-dna.ts index 58b10c0..2ecf34a 100644 --- a/ts/scripts/read-dna.ts +++ b/ts/scripts/read-dna.ts @@ -3,7 +3,7 @@ import { DNAFactory } from '../src'; function run() { const dna = process.argv[2]; const df = new DNAFactory(); - const data = df.parse(dna, '3.2.0'); + const data = df.parse(dna); debugger; console.log(data); } From f3e06fb9ff49154e52776b81de4e429abe9f44b5 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:17:52 +0400 Subject: [PATCH 12/22] Remove unused code --- ts/scripts/convert-adv-schema.ts | 28 ++++++++++++++++++++++++++++ ts/scripts/generate-dna.ts | 11 +++++++++++ ts/src/adventure_stats.ts | 25 ------------------------- 3 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 ts/scripts/convert-adv-schema.ts create mode 100644 ts/scripts/generate-dna.ts diff --git a/ts/scripts/convert-adv-schema.ts b/ts/scripts/convert-adv-schema.ts new file mode 100644 index 0000000..c2bbe94 --- /dev/null +++ b/ts/scripts/convert-adv-schema.ts @@ -0,0 +1,28 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +function getCurrentDateFormatted() { + const date = new Date(); + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based, so add 1 + const year = date.getFullYear(); + + return `${day}/${month}/${year}`; +} + +// Define the input and output file paths +const inputFilePath = './src/deps/schemas/adventures/v0.0.6.json'; +function run() { + const advSchema = JSON.parse(fs.readFileSync(inputFilePath, 'utf8').toString()); + const nefties = advSchema.nefties; + const version = '4.0.0'; + const date = getCurrentDateFormatted(); + const newSchema = { + version, + date, + nefties, + }; + console.log(advSchema); +} + +run(); diff --git a/ts/scripts/generate-dna.ts b/ts/scripts/generate-dna.ts new file mode 100644 index 0000000..b15e075 --- /dev/null +++ b/ts/scripts/generate-dna.ts @@ -0,0 +1,11 @@ +import { DNAFactory, EggsFactory } from '../src'; + +function run() { + const df = new DNAFactory(); + const eggs = EggsFactory.getAllEggs(); + const dna = df.generateNeftyDNA('0', 'prime'); + const data = df.parse(dna); + console.log(data); +} + +run(); diff --git a/ts/src/adventure_stats.ts b/ts/src/adventure_stats.ts index c72f4bc..951ac18 100644 --- a/ts/src/adventure_stats.ts +++ b/ts/src/adventure_stats.ts @@ -144,31 +144,6 @@ function convertStats(tacticsStats: ParseDataRangeCompleteness): ParseDataPerc { return advArrToObj(adventuresStats); } -function fixGlitchedSchimmering( - tacticsStatsObj: ParseDataRangeCompleteness, - adventuresStatsObj: ParseDataPerc -): ParseDataPerc { - const tacticsStats = tacticsStatsObjToArr(tacticsStatsObj); - const floorAvgGame1 = floorAverage(tacticsStats); - const adventuresStats = Object.values(adventuresStatsObj); - const isGlitched1 = tacticsStats.every((stat) => stat <= 5); - const isSchimmering1 = tacticsStats.every((stat) => stat >= 95); - const isGlitched2 = adventuresStats.every((stat) => stat <= 5); - const isSchimmering2 = adventuresStats.every((stat) => stat >= 95); - - let adventuresStatsCorrected; - if (isGlitched1 === isGlitched2 && isSchimmering1 === isSchimmering2) { - return adventuresStatsObj; - } else if (isGlitched1 !== isGlitched2) { - if (isGlitched1) { - adventuresStatsCorrected = makeGlitched(floorAvgGame1, adventuresStats); - } - adventuresStatsCorrected = removeGlitched(floorAvgGame1, adventuresStats); - } else if (isSchimmering1) adventuresStatsCorrected = makeSchimmering(floorAvgGame1, adventuresStats); - else adventuresStatsCorrected = removeSchimmering(floorAvgGame1, adventuresStats); - return advArrToObj(adventuresStatsCorrected); -} - export function getAdventuresStats(dnaSchemaReader: DNASchemaReader, adventuresStats: AdvStatsJSON): ParseDataAdv { const tacticsStats: Partial = {}; dnaSchemaReader.getCompletenessGenes().forEach((gene: GeneWithValues) => { From a6622ad61af935e12a0bb379c9f66ac07d16ec5f Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:55:50 +0200 Subject: [PATCH 13/22] Wip --- ts/package.json | 3 + ts/src/adventure_stats.ts | 4 +- ts/src/constants.ts | 10 +- ts/src/deps/schemas/aurory_dna_v4.0.0.json | 48 ++++ ts/src/deps/schemas/latest.ts | 2 +- ts/src/{dna_factory.ts => dna_factory_v1.ts} | 6 +- ts/src/dna_factory_v2.ts | 232 ++++++++++++++++++ .../{eggs_factory.ts => eggs_factory_v1.ts} | 17 +- ts/src/eggs_factory_v2.ts | 56 +++++ ts/src/index.ts | 11 +- ts/src/interfaces/types.ts | 43 +++- ts/src/utils.ts | 22 +- ts/tests/distribution.spec.ts | 2 +- ts/tests/dna.spec.ts | 20 +- ts/tests/dna.v2.spec.ts | 11 + ts/tests/workers/distribution-worker.ts | 4 +- ts/tests/workers/stats-mean-worker.ts | 6 +- ts/tsconfig.build.json | 2 +- ts/yarn.lock | 10 + 19 files changed, 470 insertions(+), 39 deletions(-) create mode 100644 ts/src/deps/schemas/aurory_dna_v4.0.0.json rename ts/src/{dna_factory.ts => dna_factory_v1.ts} (99%) create mode 100644 ts/src/dna_factory_v2.ts rename ts/src/{eggs_factory.ts => eggs_factory_v1.ts} (78%) create mode 100644 ts/src/eggs_factory_v2.ts create mode 100644 ts/tests/dna.v2.spec.ts diff --git a/ts/package.json b/ts/package.json index 339f878..b0369a5 100644 --- a/ts/package.json +++ b/ts/package.json @@ -12,6 +12,7 @@ "sp": "ts-node $1", "test": "ts-mocha -p ./tsconfig.json -t 2500000 --exit tests/**/*.spec.ts", "test:dna": "ts-mocha -p ./tsconfig.json -t 2500000 --exit tests/dna.spec.ts", + "test:dna2": "ts-mocha -p ./tsconfig.json -t 2500000 --exit tests/dna.v2.spec.ts", "test:distribution": "ts-mocha -p ./tsconfig.json -t 2500000 --exit tests/distribution.spec.ts", "build": "tsc -p tsconfig.build.json && cp -r src/deps lib/", "prepare": "cd .. && husky install && cd ts && yarn build", @@ -41,6 +42,7 @@ "eslint-plugin-prettier": "^4.2.1", "husky": "^8.0.1", "mocha": "^10.0.0", + "nanoid": "^5.0.7", "prettier": "^2.6.2", "ts-mocha": "^10.0.0", "ts-node": "^10.2.1", @@ -53,6 +55,7 @@ "dependencies": { "@types/randombytes": "^2.0.0", "camel-case": "^4.1.2", + "compress-json": "^3.1.0", "dotenv": "^16.3.1", "google-spreadsheet": "^3.3.0", "randombytes": "^2.1.0", diff --git a/ts/src/adventure_stats.ts b/ts/src/adventure_stats.ts index 951ac18..205737a 100644 --- a/ts/src/adventure_stats.ts +++ b/ts/src/adventure_stats.ts @@ -7,6 +7,8 @@ import { AdvStatsJSON, ParseDataPerc, AdvStatsJSONValue, + NeftyCodeNameId, + NeftyCodeName, } from './interfaces/types'; /** @@ -150,7 +152,7 @@ export function getAdventuresStats(dnaSchemaReader: DNASchemaReader, adventuresS tacticsStats[gene.name as keyof ParseDataRangeCompleteness] = Math.round((gene.completeness as number) * 100); }); const fixedStats = convertStats(tacticsStats as ParseDataRangeCompleteness); - const neftieName = TACTICS_ADV_NAMES_MAP[dnaSchemaReader.archetype.fixed_attributes.name]; + const neftieName = TACTICS_ADV_NAMES_MAP[dnaSchemaReader.archetype.fixed_attributes.name as NeftyCodeName]; const advStatsRanges = adventuresStats.nefties[neftieName]; Object.keys(fixedStats).forEach((key) => { const min = advStatsRanges[`${key}Min` as keyof AdvStatsJSONValue]; diff --git a/ts/src/constants.ts b/ts/src/constants.ts index 7923c06..396276f 100644 --- a/ts/src/constants.ts +++ b/ts/src/constants.ts @@ -3,7 +3,7 @@ export const GLITCHED_PERIOD = 1500; export const GLITCHED_RANGE_START = 5; export const SCHIMMERING_RANGE_START = 95; -export const TACTICS_ADV_NAMES_MAP: Record = { +export const TACTICS_ADV_NAMES_MAP = { Nefty_Bitebit: 'id_bitebit', Nefty_Dipking: 'id_dipking', Nefty_Dinobit: 'id_dinobit', @@ -29,4 +29,10 @@ export const TACTICS_ADV_NAMES_MAP: Record = { Nefty_Whiskube: 'id_whiskube', Nefty_Walpuff: 'id_walpuff', Nefty_Dinotusk: 'id_dinotusk', -}; +} as const; + +export const VERSION_LENGTH = 4; +export const LAST_SUPPORTED_VERSION_BY_V1 = '3.2.0'; + +// hp, atk, def, speed +export const N_STATS_SOT = 4; diff --git a/ts/src/deps/schemas/aurory_dna_v4.0.0.json b/ts/src/deps/schemas/aurory_dna_v4.0.0.json new file mode 100644 index 0000000..b2e094b --- /dev/null +++ b/ts/src/deps/schemas/aurory_dna_v4.0.0.json @@ -0,0 +1,48 @@ +{ + "version": "4.0.0", + "version_date": "05/07/2024", + "global_genes_header": [ + { + "name": "version", + "base": 2 + }, + { + "name": "data_end", + "base": 2 + } + ], + "archetypes": { + "0": "Nefty_Bitebit", + "1": "Nefty_Dipking", + "2": "Nefty_Dinobit", + "3": "Nefty_ShibaIgnite", + "4": "Nefty_Zzoo", + "5": "Nefty_Blockchoy", + "6": "Nefty_Number9", + "7": "Nefty_Axobubble", + "8": "Nefty_Unika", + "9": "Nefty_Chocomint", + "10": "Nefty_Cybertooth", + "11": "Nefty_Wassie", + "12": "Nefty_Dracurve", + "13": "Nefty_Raccoin", + "14": "Nefty_Shibark", + "15": "Nefty_Unikirin", + "16": "Nefty_Beeblock", + "17": "Nefty_Chocorex", + "18": "Nefty_Keybab", + "19": "Nefty_Bloomtail", + "20": "Nefty_Tokoma", + "21": "Nefty_Ghouliath", + "22": "Nefty_Whiskube", + "23": "Nefty_Walpuff", + "24": "Nefty_Dinotusk" + }, + "rarities": { + "0": "Common", + "1": "Uncommon", + "2": "Rare", + "3": "Epic", + "4": "Legendary" + } +} diff --git a/ts/src/deps/schemas/latest.ts b/ts/src/deps/schemas/latest.ts index c4bb975..c45cff9 100644 --- a/ts/src/deps/schemas/latest.ts +++ b/ts/src/deps/schemas/latest.ts @@ -1 +1 @@ -export const LATEST_VERSION = '3.2.0'; +export const LATEST_VERSION = '4.0.0'; diff --git a/ts/src/dna_factory.ts b/ts/src/dna_factory_v1.ts similarity index 99% rename from ts/src/dna_factory.ts rename to ts/src/dna_factory_v1.ts index 5203b39..0386c8b 100644 --- a/ts/src/dna_factory.ts +++ b/ts/src/dna_factory_v1.ts @@ -34,7 +34,6 @@ import dnaSchemaV3_1 from './deps/schemas/aurory_dna_v3.1.0.json'; import dnaSchemaV3_2 from './deps/schemas/aurory_dna_v3.2.0.json'; import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; -import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from './deps/schemas/latest'; import { LATEST_VERSION as LATEST_ABILTIIES_VERSION } from './deps/dictionaries/latest'; import abiltiesDictionaryV4 from './deps/dictionaries/abilities_dictionary_v0.4.0.json'; import neftiesInfo from './deps/nefties_info.json'; @@ -50,6 +49,7 @@ import { } from './utils'; import { DNASchemaReader } from './dna_schema_reader'; import { getAdventuresStats } from './adventure_stats'; +import { LAST_SUPPORTED_VERSION_BY_V1 } from './constants'; const dnaSchemas: Record = { '0.2.0': dnaSchemaV0_2 as DNASchema, @@ -71,7 +71,7 @@ const abilitiesDictionaries: Record = { '0.4.0': abiltiesDictionaryV4 as AbilityDictionary, }; -export class DNAFactory { +export class DNAFactoryV1 { dnaSchemas: Record; abilitiesDictionary: Record; neftiesInfo: NeftiesInfo; @@ -89,7 +89,7 @@ export class DNAFactory { this.dnaBytes = dnaBytes ?? 64; this.encodingBase = encodingBase ?? 16; this.baseSize = this.encodingBase / 8; - this.latestSchemaVersion = LATEST_SCHEMA_VERSION; + this.latestSchemaVersion = LAST_SUPPORTED_VERSION_BY_V1; this.latestAbilitiesVersion = LATEST_ABILTIIES_VERSION; this.dnaSchemas = dnaSchemas; this.abilitiesDictionary = abilitiesDictionaries; diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts new file mode 100644 index 0000000..9e77d06 --- /dev/null +++ b/ts/src/dna_factory_v2.ts @@ -0,0 +1,232 @@ +import { + Grade, + Rarity, + version, + DNASchemaV4, + DnaDataV2, + ParseDataPerc, + NeftyCodeName, + ParseV2, + DnaDataData, + ParseDataComputed, + AdvStatsJSON, + AdvStatsJSONValue, +} from './interfaces/types'; +import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from './deps/schemas/latest'; +import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; +import dnaSchemaV4_0 from './deps/schemas/aurory_dna_v4.0.0.json'; +import { + getAverageFromRaw, + getCategoryKeyFromName, + getLatestSubversion, + randomInt, + randomNormal, + toPaddedHexa, + toUnPaddedHexa, + unpad, +} from './utils'; +import raritiesGeneration from './deps/rarities_generation.json'; +import zlib from 'zlib'; +import { N_STATS_SOT, TACTICS_ADV_NAMES_MAP, VERSION_LENGTH } from './constants'; +import { DNAFactoryV1 } from './dna_factory_v1'; +import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; + +const dnaSchemas: Record = { + '4.0.0': dnaSchemaV4_0 as DNASchemaV4, +}; + +const adventuresStats: Record = { + '0.0.6': adventuresStatsV0_0_6, +}; + +export class DNAFactoryV2 { + latestSchemasSubversions: Record; + constructor() { + this.latestSchemasSubversions = {}; + return; + } + + // get latest minor version from a major version + private getLatestSchemaSubversion(schemaVersion?: string): string { + if (!schemaVersion) return LATEST_SCHEMA_VERSION; + else if (schemaVersion?.includes('.')) return schemaVersion; + else if (schemaVersion && this.latestSchemasSubversions[schemaVersion]) + return this.latestSchemasSubversions[schemaVersion]; + const completeVersion = getLatestSubversion(dnaSchemas, schemaVersion); + this.latestSchemasSubversions[schemaVersion] = completeVersion; + return completeVersion; + } + + private _getRandomRarity(grade: Grade): Rarity { + const rarities = raritiesGeneration[grade]; + if (!rarities) { + throw new Error(`No rarity found for input ${grade}`); + } + const precision = 3; + const multiplier = Math.pow(10, precision); + const weightsSum = Object.values(rarities).reduce((acc, rarity) => acc + rarity.probability * multiplier, 0); + const random = Math.random() * weightsSum; + let total = 0; + for (const [rarity, rarityInfo] of Object.entries(rarities)) { + total += rarityInfo.probability * multiplier; + if (random <= total) return rarity as Rarity; + } + throw new Error(`No rarity found: ${weightsSum}, ${random}`); + } + + private getDNASchema(version?: version): DNASchemaV4 { + if (!version) return dnaSchemas[LATEST_SCHEMA_VERSION]; + else if (dnaSchemas[version]) return dnaSchemas[version]; + else if (this.latestSchemasSubversions[version]) return dnaSchemas[this.latestSchemasSubversions[version]]; + + const completeVersion = version.includes('.') ? version : this.getLatestSchemaSubversion(version); + + if (!completeVersion) throw new Error(`No schema found for ${version}`); + + const dnaSchema: DNASchemaV4 = dnaSchemas[completeVersion]; + if (completeVersion !== dnaSchema.version) + throw new Error(`Versions mismatch: ${completeVersion} (filename) vs ${dnaSchema.version} (schema)`); + return dnaSchemas[completeVersion]; + } + + private deserializeDna(dna: string): DnaDataV2 { + return JSON.parse(Buffer.from(dna, 'base64').toString()); + } + + private getDna(version: version, data: DnaDataV2): string { + const versionDNAFormat = toPaddedHexa(version, 4); + const serializedData = Buffer.from(JSON.stringify(data)).toString('base64'); + const dna = versionDNAFormat + serializedData; + return dna; + } + + /** + * Generate statsCount number of stats with a mean value in the rarity range. + * @param rarity Rarity + * @param statsCount Number of stats to generate + */ + private _generateStatsForRarity(nStats: number, grade: Grade, rarity: Rarity): number[] { + const [minStatAvg, maxStatAvg] = raritiesGeneration[grade][rarity].average_stats_range; + const stats = Array.from(Array(nStats).keys()).map(() => 0); + + const mean = randomInt(minStatAvg, maxStatAvg, true); + // adding up to 5 will still result in the same mean as we are rounding down + const totalPoints = mean * nStats; + const maxValuePerStat = 100; + + // As 100 is the upper limit, this value is over represented in the distribution + // This makes the max random stat randomly set to a value between mean + 1 and 100 + const maxStatValue = randomInt(Math.min(mean + 1, maxValuePerStat), maxValuePerStat, false); + + const distributePoints = () => { + while (pointsLeft) { + const statIndex = randomInt(0, stats.length, true); + const statValue = stats[statIndex]; + + const maxPoints = Math.min(pointsLeft, maxStatValue - statValue); + if (!maxPoints) continue; + if (pointsLeft < 0) throw new Error('pointsLeft < 0'); + const points = randomNormal(1, Math.ceil(maxPoints / stats.length), -100, 200); + stats[statIndex] += points; + pointsLeft -= points; + } + }; + + let pointsLeft = totalPoints; + let raw = [] as number[]; + let average; + + while (pointsLeft) { + distributePoints(); + raw = stats.map((stat, index) => Math.round((stat / 100) * maxValuePerStat)); + + average = Math.floor( + getAverageFromRaw( + raw, + stats.map((_, index) => maxValuePerStat) + ) * 100 + ); + + // the average is done on raw stats but points are distributed on the % stats. It may happen the means are not the same. + if (Math.floor(average) !== mean) pointsLeft += 1; + } + + // if average = 1 for a non glitched or 95 for a schimmering, we may end up not enterring in the previous loop + if (!raw.length) raw = stats.map((stat, index) => Math.round((stat / 100) * maxValuePerStat)); + return raw; + } + + generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { + const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + console.log(dnaSchema.version); + const dnaData = {} as DnaDataV2; + dnaData.version = dnaSchema.version; + + const dataData = {} as DnaDataData; + dataData.grade = grade; + dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); + dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; + dnaData.data = dataData; + + const dataAdv = {} as ParseDataPerc; + const [hp, atk, def, speed] = this._generateStatsForRarity(N_STATS_SOT, grade, dataData.rarity); + Object.assign(dataAdv, { hp, atk, def, speed }); + dnaData.dataAdv = dataAdv; + + const dna = this.getDna(dnaSchema.version, dnaData); + return dna; + } + + generateNeftyDNAFromV1Dna( + dnaFactoryV1: DNAFactoryV1, + v1Dna: string, + newSotStats?: ParseDataPerc, + newVersion?: version + ) { + const dataV1 = dnaFactoryV1.parse(v1Dna); + const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); + const dnaData = {} as DnaDataV2; + const dataAdv = {} as ParseDataPerc; + if (newSotStats) { + const { hp: hpP, atk: atkP, def: defP, speed: speedP } = newSotStats; + Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + } else { + const { hp: hpP, atk: atkP, def: defP, speed: speedP } = dataV1.dataAdv; + Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + } + dnaData.dataAdv = dataAdv; + const dataData = {} as DnaDataData; + dataData.grade = dataV1.data.grade; + dataData.rarity = dataV1.data.rarity; + dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; + dnaData.data = dataData; + dnaData.version = dnaSchema.version; + const dna = this.getDna(dnaSchema.version, dnaData); + return dna; + } + + parse(dnaString: string): ParseV2 { + const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); + const data = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); + const computedStats: ParseDataComputed = this.computeSOTStats(data.data.neftyCodeName, data.dataAdv); + Object.assign(data.dataAdv, computedStats); + return data as ParseV2; + } + + private computeSOTStats(neftyCodeName: NeftyCodeName, dataAdv: ParseDataPerc): ParseDataComputed { + const neftyCodenameId = TACTICS_ADV_NAMES_MAP[neftyCodeName]; + const computed = {} as ParseDataComputed; + const sotStatsCurrent = adventuresStats[LATEST_ADVENTURES_STATS_VERSION].nefties[neftyCodenameId]; + if (!sotStatsCurrent) throw new Error(`No SOT stats found for ${neftyCodenameId}`); + Object.entries(dataAdv).forEach(([statName, percentage]) => { + const key = `${statName}Computed` as keyof ParseDataComputed; + const minK = `${statName}Min` as keyof AdvStatsJSONValue; + const min = sotStatsCurrent[minK]; + const maxK = `${statName}Max` as keyof AdvStatsJSONValue; + const max = sotStatsCurrent[maxK]; + const value = (percentage / 100) * (max - min) + min; + computed[key] = Math.round(value); + }); + return computed; + } +} diff --git a/ts/src/eggs_factory.ts b/ts/src/eggs_factory_v1.ts similarity index 78% rename from ts/src/eggs_factory.ts rename to ts/src/eggs_factory_v1.ts index f822063..d9e2058 100644 --- a/ts/src/eggs_factory.ts +++ b/ts/src/eggs_factory_v1.ts @@ -1,9 +1,10 @@ import { EggInfo, Archetype, DroppableNeftyInfo } from './interfaces/types'; import eggsInfo from './deps/eggs_info.json'; import standardEggsInfo from './deps/standard_eggs_info.json'; -import { DNAFactory } from './dna_factory'; +import { DNAFactoryV1 as DNAFactory } from './dna_factory_v1'; +import { LAST_SUPPORTED_VERSION_BY_V1 } from './constants'; -export class EggsFactory { +export class EggsFactoryV1 { eggInfo: EggInfo; standardEggInfo: EggInfo; df: DNAFactory; @@ -14,17 +15,17 @@ export class EggsFactory { } static getAllEggs(): Record { - return eggsInfo; + return eggsInfo as Record; } static getAllStandardEggs(): Record { - return standardEggsInfo; + return standardEggsInfo as Record; } getDroppableNefties(): DroppableNeftyInfo[] { return this.eggInfo.archetypes.map((neftyCodeName) => { const r: DroppableNeftyInfo = {} as DroppableNeftyInfo; - Object.assign(r, this.df.getArchetypeByNeftyCodeName(neftyCodeName)); + Object.assign(r, this.df.getArchetypeByNeftyCodeName(neftyCodeName, LAST_SUPPORTED_VERSION_BY_V1)); r.displayName = this.df.getDisplayNameFromCodeName(neftyCodeName); r.description = this.df.getFamilyDescription(r.archetype.fixed_attributes.family as string); return r; @@ -34,7 +35,7 @@ export class EggsFactory { getDroppableStandardNefties(): DroppableNeftyInfo[] { return this.standardEggInfo.archetypes.map((neftyCodeName) => { const r: DroppableNeftyInfo = {} as DroppableNeftyInfo; - Object.assign(r, this.df.getArchetypeByNeftyCodeName(neftyCodeName)); + Object.assign(r, this.df.getArchetypeByNeftyCodeName(neftyCodeName, LAST_SUPPORTED_VERSION_BY_V1)); r.displayName = this.df.getDisplayNameFromCodeName(neftyCodeName); r.description = this.df.getFamilyDescription(r.archetype.fixed_attributes.family as string); return r; @@ -44,12 +45,12 @@ export class EggsFactory { hatch(): { archetypeKey: string; archetype: Archetype } { const droppableArchetypes = this.eggInfo.archetypes; const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; - return this.df.getArchetypeByNeftyCodeName(neftyCodeName); + return this.df.getArchetypeByNeftyCodeName(neftyCodeName, LAST_SUPPORTED_VERSION_BY_V1); } hatchStandard(): { archetypeKey: string; archetype: Archetype } { const droppableArchetypes = this.standardEggInfo.archetypes; const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; - return this.df.getArchetypeByNeftyCodeName(neftyCodeName); + return this.df.getArchetypeByNeftyCodeName(neftyCodeName, LAST_SUPPORTED_VERSION_BY_V1); } } diff --git a/ts/src/eggs_factory_v2.ts b/ts/src/eggs_factory_v2.ts new file mode 100644 index 0000000..3110b94 --- /dev/null +++ b/ts/src/eggs_factory_v2.ts @@ -0,0 +1,56 @@ +import { EggInfo, DroppableNeftyInfoV2, NeftyCodeName } from './interfaces/types'; +import eggsInfo from './deps/eggs_info.json'; +import standardEggsInfo from './deps/standard_eggs_info.json'; +import { DNAFactoryV2 as DNAFactory } from './dna_factory_v2'; +import neftiesInfo from './deps/nefties_info.json'; + +export class EggsFactoryV2 { + eggInfo: EggInfo; + standardEggInfo: EggInfo; + df: DNAFactory; + constructor(eggPk: string, df: DNAFactory) { + this.eggInfo = (eggsInfo as Record)[eggPk]; + this.standardEggInfo = (standardEggsInfo as Record)[eggPk]; + this.df = df; + } + + static getAllEggs(): Record { + return eggsInfo as Record; + } + + static getAllStandardEggs(): Record { + return standardEggsInfo as Record; + } + + getDroppableNefties(): DroppableNeftyInfoV2[] { + return this.eggInfo.archetypes.map((neftyCodeName) => { + const r = {} as DroppableNeftyInfoV2; + r.neftyCodeName = neftyCodeName; + r.displayName = neftiesInfo.code_to_displayName[neftyCodeName] as string; + return r; + }); + } + + getDroppableStandardNefties(): DroppableNeftyInfoV2[] { + return this.eggInfo.archetypes.map((neftyCodeName) => { + const r = {} as DroppableNeftyInfoV2; + r.neftyCodeName = neftyCodeName; + r.displayName = neftiesInfo.code_to_displayName[neftyCodeName] as string; + return r; + }); + } + + hatch(): { archetypeKey: string; neftyCodeName: NeftyCodeName } { + const droppableArchetypes = this.eggInfo.archetypes; + const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; + const archetypeKey = neftyCodeName; + return { archetypeKey, neftyCodeName }; + } + + hatchStandard(): { archetypeKey: string; neftyCodeName: NeftyCodeName } { + const droppableArchetypes = this.standardEggInfo.archetypes; + const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; + const archetypeKey = neftyCodeName; + return { archetypeKey, neftyCodeName }; + } +} diff --git a/ts/src/index.ts b/ts/src/index.ts index 4a24fc3..7c61e79 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -1,5 +1,12 @@ -export * from './dna_factory'; -export * from './eggs_factory'; +import { DNAFactoryV2 as DNAFactory } from './dna_factory_v2'; +export { DNAFactory }; +import { EggsFactoryV2 as EggsFactory } from './eggs_factory_v2'; +export { EggsFactory }; + +export * from './dna_factory_v1'; +export * from './dna_factory_v2'; +export * from './eggs_factory_v1'; +export * from './eggs_factory_v2'; export * from './dna_schema_reader'; export * from './interfaces/types'; export * from './constants'; diff --git a/ts/src/interfaces/types.ts b/ts/src/interfaces/types.ts index 1cd9cb5..8f6e1a1 100644 --- a/ts/src/interfaces/types.ts +++ b/ts/src/interfaces/types.ts @@ -1,4 +1,5 @@ import abiltiesDictionaryV4 from '../deps/dictionaries/abilities_dictionary_v0.4.0.json'; +import { TACTICS_ADV_NAMES_MAP } from '../constants'; export type GeneType = 'index' | 'range_completeness'; @@ -56,6 +57,19 @@ export interface DNASchemaV2 { export type DNASchemaV3 = DNASchemaV2; +// eg: Nefty_Bitebit +export type NeftyCodeName = keyof typeof TACTICS_ADV_NAMES_MAP; +// eg: id_bitebit +export type NeftyCodeNameId = typeof TACTICS_ADV_NAMES_MAP[keyof typeof TACTICS_ADV_NAMES_MAP]; + +export interface DNASchemaV4 { + version: string; + version_date: string; + global_genes_header: Gene[]; + archetypes: Record; + rarities: Record; +} + export type DNASchema = DNASchemaV0 | DNASchemaV2 | DNASchemaV3; export type Rarity = 'Common' | 'Uncommon' | 'Rare' | 'Epic' | 'Legendary'; @@ -83,7 +97,7 @@ export interface ImageNeftyByGame { export type NeftyImageFormat = keyof ImageNeftyByGame; export interface ParseDataNefty { - name: string; + name: NeftyCodeName; displayName: string; family: string; passiveSkill: string; @@ -161,7 +175,6 @@ export interface Parse { metadata: { version: string }; genes: Gene[]; } - export interface AbilityLocalizedValue { EN: string; FR: string; @@ -188,9 +201,6 @@ export interface NeftiesInfo { family_to_description: Record; } -// eg: Nefty_Bitebit -type NeftyCodeName = string; - export interface EggInfo { name: string; description: string; @@ -204,6 +214,11 @@ export interface DroppableNeftyInfo { description: string; } +export interface DroppableNeftyInfoV2 { + neftyCodeName: string; + displayName: string; +} + export type version = string; export type GeneWithValues = Gene & { @@ -212,3 +227,21 @@ export type GeneWithValues = Gene & { completeness?: number; skill_info?: AbilityInfo; }; + +export interface DnaDataData { + grade: Grade; + rarity: Rarity; + neftyCodeName: NeftyCodeName; +} + +export interface DnaDataV2 { + version: string; + data: DnaDataData; + dataAdv: ParseDataPerc; +} + +export interface ParseV2 { + version: string; + data: DnaDataData; + dataAdv: ParseDataAdv; +} diff --git a/ts/src/utils.ts b/ts/src/utils.ts index 3d018c9..6b255b6 100644 --- a/ts/src/utils.ts +++ b/ts/src/utils.ts @@ -1,4 +1,4 @@ -import { AbilityDictionary, Category, DNASchema } from './interfaces/types'; +import { AbilityDictionary, Category, DNASchema, DNASchemaV4 } from './interfaces/types'; // you should multiply by 100 to get in % export function getAverageFromRaw(numbers: number[], maxValuePerStat: number[]): number { @@ -23,7 +23,7 @@ export function getCategoryKeyFromName(name: string, categories: Record, + completeVersionsDict: Record, schemaVersionInput?: string ): string { const schemaVersion = schemaVersionInput @@ -87,3 +87,21 @@ export function randomNormal(min: number, max: number, leftLimit: number, rightL export function unpad(v: string, encodingBase: number | undefined): string { return parseInt(v, encodingBase).toString(); } + +export function toPadded(n: string, maxLength: number, radix?: number): string { + return parseInt(n).toString(radix).padStart(maxLength, '0'); +} + +/** + * Converts a string representation of a number to a hexadecimal string + * and pads it with leading zeros to ensure it has the specified length. + * @example + * _toPaddedHexa('255', 4); / Returns '00ff' + */ +export function toPaddedHexa(n: string, maxLength: number): string { + return toPadded(n, maxLength, 16); +} + +export function toUnPaddedHexa(n: string): string { + return unpad(n, 16); +} diff --git a/ts/tests/distribution.spec.ts b/ts/tests/distribution.spec.ts index 8dcbbe5..13d5053 100644 --- a/ts/tests/distribution.spec.ts +++ b/ts/tests/distribution.spec.ts @@ -1,4 +1,4 @@ -import { DNAFactory, EggsFactory, GLITCHED_PERIOD, Rarity, SCHIMMERING_PERIOD, utils } from '../src'; +import { DNAFactoryV1 as DNAFactory, EggsFactoryV1 as EggsFactory, Rarity } from '../src'; import assert from 'assert'; import rarities from '../src/deps/rarities.json'; diff --git a/ts/tests/dna.spec.ts b/ts/tests/dna.spec.ts index 285db63..b992778 100644 --- a/ts/tests/dna.spec.ts +++ b/ts/tests/dna.spec.ts @@ -1,11 +1,10 @@ -import { DNAFactory, EggsFactory, Rarity, utils } from '../src'; +import { DNAFactoryV1 as DNAFactory, EggsFactoryV1 as EggsFactory, Rarity, utils } from '../src'; import nefties_info from '../src/deps/nefties_info.json'; import assert from 'assert'; import rarities from '../src/deps/rarities.json'; import { readdirSync, readFileSync } from 'fs'; -import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from '../src/deps/schemas/latest'; -import { DNASchema } from '../src/interfaces/types'; -import { TACTICS_ADV_NAMES_MAP } from '../src/constants'; +import { DNASchema, NeftyCodeName } from '../src/interfaces/types'; +import { LAST_SUPPORTED_VERSION_BY_V1, TACTICS_ADV_NAMES_MAP } from '../src/constants'; const displayNamesProd = [ 'Axobubble', @@ -153,12 +152,15 @@ const abilitiesProd = new Set([ 'N/A', ]); +const LAST_FACTORY_V1_SUPPORTED_VERSION = '3.2.0'; + const allSchemaVersions = readdirSync('./src/deps/schemas') .filter((v) => v.endsWith('json')) .map((v) => { const index = v.indexOf('_v'); return v.slice(index + 2, index + 7); - }); + }) + .filter((v) => parseInt(v.split('.')[0]) <= 3); describe('Basic', () => { it('DNA should parse', () => { @@ -170,7 +172,7 @@ describe('Basic', () => { try { assert.ok(displayName); assert.ok(description); - const dna = df.generateNeftyDNA(archetypeKey, 'prime'); + const dna = df.generateNeftyDNA(archetypeKey, 'prime', LAST_FACTORY_V1_SUPPORTED_VERSION); const data = df.parse(dna); assert.ok(data.data); assert.ok(data.data.name); @@ -249,6 +251,7 @@ describe('Compute possible names, families and abilities', () => { const df = new DNAFactory(); const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); const neftyIndex = ef.hatch().archetypeKey; + debugger; const dna = df.generateNeftyDNA(neftyIndex, 'prime'); const category = df.getCategory('nefties', df.getDnaVersion(dna)); const neftyNames = new Set(); @@ -350,11 +353,12 @@ describe('Rarity', () => { describe('Adventures', () => { it('All archetypes have adventure stats', () => { const schema: DNASchema = JSON.parse( - readFileSync(`./src/deps/schemas/aurory_dna_v${LATEST_SCHEMA_VERSION}.json`, 'utf8') + readFileSync(`./src/deps/schemas/aurory_dna_v${LAST_SUPPORTED_VERSION_BY_V1}.json`, 'utf8') ); + Object.values(schema.categories['0'].archetypes).forEach((archetype) => { assert.ok( - TACTICS_ADV_NAMES_MAP[archetype.fixed_attributes.name], + TACTICS_ADV_NAMES_MAP[archetype.fixed_attributes.name as NeftyCodeName], `${archetype.fixed_attributes.name} not found in TACTICS_ADV_NAMES_MAP: ${JSON.stringify( TACTICS_ADV_NAMES_MAP )}` diff --git a/ts/tests/dna.v2.spec.ts b/ts/tests/dna.v2.spec.ts new file mode 100644 index 0000000..4751a02 --- /dev/null +++ b/ts/tests/dna.v2.spec.ts @@ -0,0 +1,11 @@ +import { DNAFactory } from '../src'; + +describe('Basic', () => { + it('should work', () => { + const df = new DNAFactory(); + const dna = df.generateNeftyDNA('0', 'prime'); + console.log(dna); + const parsed = df.parse(dna); + console.log(parsed); + }); +}); diff --git a/ts/tests/workers/distribution-worker.ts b/ts/tests/workers/distribution-worker.ts index 8d299a6..bedaf02 100644 --- a/ts/tests/workers/distribution-worker.ts +++ b/ts/tests/workers/distribution-worker.ts @@ -1,6 +1,6 @@ import { parentPort, workerData } from 'worker_threads'; -import { DNAFactory } from '../../src/dna_factory'; -import { EggsFactory } from '../../src/eggs_factory'; +import { DNAFactoryV1 as DNAFactory } from '../../src/index'; +import { EggsFactoryV1 as EggsFactory } from '../../src/index'; import { Rarity } from '../../src/interfaces/types'; import { utils } from '../../src'; diff --git a/ts/tests/workers/stats-mean-worker.ts b/ts/tests/workers/stats-mean-worker.ts index b014eb8..5bed6bb 100644 --- a/ts/tests/workers/stats-mean-worker.ts +++ b/ts/tests/workers/stats-mean-worker.ts @@ -1,7 +1,7 @@ import { parentPort, workerData } from 'worker_threads'; -import { DNAFactory } from '../../src/dna_factory'; -import { EggsFactory } from '../../src/eggs_factory'; -import { Rarity } from '../../src/interfaces/types'; +import { DNAFactoryV1 as DNAFactory } from '../../src/index'; +import { EggsFactoryV1 as EggsFactory } from '../../src/index'; +import { Grade, Rarity } from '../../src/interfaces/types'; import { utils } from '../../src'; import rarities from '../../src/deps/rarities.json'; diff --git a/ts/tsconfig.build.json b/ts/tsconfig.build.json index 9865daa..25d4201 100644 --- a/ts/tsconfig.build.json +++ b/ts/tsconfig.build.json @@ -3,5 +3,5 @@ "rootDir": "./src" }, "extends": "./tsconfig.json", - "exclude": ["node_modules", "**/tests/*"] + "exclude": ["node_modules", "**/tests/*", "scripts"] } diff --git a/ts/yarn.lock b/ts/yarn.lock index 58800f1..9280e3e 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -608,6 +608,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +compress-json@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/compress-json/-/compress-json-3.1.0.tgz#337e0a5b28b180fb849bcb6913bf361a5b3c0d57" + integrity sha512-Zcq4jRC5ZpfaOY3mbBWOANtGuMHJ/hsTENcwN1/lEkrogcoAF7HBma1RLe/CICZO6IquK1U0EaPzmnlDIFRNjA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1732,6 +1737,11 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== +nanoid@^5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6" + integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" From bcb0c27dbc18943b6a8518f108f052f704c39382 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:55:43 +0200 Subject: [PATCH 14/22] Functional --- README.md | 2 +- ts/package.json | 3 +- ts/scripts/convert-adv-schema.ts | 28 ---- ts/scripts/generate-dna.ts | 11 -- ts/scripts/read-dna.ts | 11 -- ts/src/deps/nefties_info.json | 4 +- ts/src/dna_factory_v2.ts | 280 ++++++++++++++++++++++++------- ts/src/eggs_factory_v2.ts | 6 +- ts/src/interfaces/types.ts | 9 +- ts/tests/dna.v2.spec.ts | 191 ++++++++++++++++++++- ts/yarn.lock | 15 +- 11 files changed, 428 insertions(+), 132 deletions(-) delete mode 100644 ts/scripts/convert-adv-schema.ts delete mode 100644 ts/scripts/generate-dna.ts delete mode 100644 ts/scripts/read-dna.ts diff --git a/README.md b/README.md index bee2c0e..7b09eac 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ const parsed = df.parse(dna); console.log(parsed); ``` -> See `Parse` interface for more details on [df.parse](./ts/src/interfaces/types.ts)'s output. +> See `ParseV2` interface for more details on [df.parse](./ts/src/interfaces/types.ts)'s output. ### Get all eggs diff --git a/ts/package.json b/ts/package.json index b0369a5..023182f 100644 --- a/ts/package.json +++ b/ts/package.json @@ -42,7 +42,6 @@ "eslint-plugin-prettier": "^4.2.1", "husky": "^8.0.1", "mocha": "^10.0.0", - "nanoid": "^5.0.7", "prettier": "^2.6.2", "ts-mocha": "^10.0.0", "ts-node": "^10.2.1", @@ -55,9 +54,9 @@ "dependencies": { "@types/randombytes": "^2.0.0", "camel-case": "^4.1.2", - "compress-json": "^3.1.0", "dotenv": "^16.3.1", "google-spreadsheet": "^3.3.0", + "lz-string": "^1.5.0", "randombytes": "^2.1.0", "snake-case": "^3.0.4" } diff --git a/ts/scripts/convert-adv-schema.ts b/ts/scripts/convert-adv-schema.ts deleted file mode 100644 index c2bbe94..0000000 --- a/ts/scripts/convert-adv-schema.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -function getCurrentDateFormatted() { - const date = new Date(); - const day = String(date.getDate()).padStart(2, '0'); - const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based, so add 1 - const year = date.getFullYear(); - - return `${day}/${month}/${year}`; -} - -// Define the input and output file paths -const inputFilePath = './src/deps/schemas/adventures/v0.0.6.json'; -function run() { - const advSchema = JSON.parse(fs.readFileSync(inputFilePath, 'utf8').toString()); - const nefties = advSchema.nefties; - const version = '4.0.0'; - const date = getCurrentDateFormatted(); - const newSchema = { - version, - date, - nefties, - }; - console.log(advSchema); -} - -run(); diff --git a/ts/scripts/generate-dna.ts b/ts/scripts/generate-dna.ts deleted file mode 100644 index b15e075..0000000 --- a/ts/scripts/generate-dna.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DNAFactory, EggsFactory } from '../src'; - -function run() { - const df = new DNAFactory(); - const eggs = EggsFactory.getAllEggs(); - const dna = df.generateNeftyDNA('0', 'prime'); - const data = df.parse(dna); - console.log(data); -} - -run(); diff --git a/ts/scripts/read-dna.ts b/ts/scripts/read-dna.ts deleted file mode 100644 index 2ecf34a..0000000 --- a/ts/scripts/read-dna.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DNAFactory } from '../src'; - -function run() { - const dna = process.argv[2]; - const df = new DNAFactory(); - const data = df.parse(dna); - debugger; - console.log(data); -} - -run(); diff --git a/ts/src/deps/nefties_info.json b/ts/src/deps/nefties_info.json index 9157d49..daec3d7 100644 --- a/ts/src/deps/nefties_info.json +++ b/ts/src/deps/nefties_info.json @@ -30,9 +30,9 @@ "Bitebit": "Bitebit may look cuddly, but don't be fooled by its charm - those aren't paper hands... they're DIAMOND CLAWS!", "Dipking": "Dipking is bursting with personality and magical power, but handle with care - all that pent up energy may have EXPLOSIVE results!", "Dinobit": "Dinobit is big, strong and tough, but careful of that temper - Make it mad and watch out for a charge that can PLOW THROUGH EVERYTHING in its path!", - "Shiba": "Shiba Ignite is always ready to jump into the heat of battle, but while it may not be the fastest - a helping paw is always there to DEFEND ITS ALLIES.", + "ShibaIgnite": "Shiba Ignite is always ready to jump into the heat of battle, but while it may not be the fastest - a helping paw is always there to DEFEND ITS ALLIES.", "Zzoo": "Zzoo has a big beak and a bad attitude, but if it's on your side, both can be an asset - SWIFT STRIKES make its mean streak your advantage!", - "Blockchoy": "Block Choy may look tasty, but its real gift is far more delicious - a menu of healing powers is standing by to RE-FUEL its allies.", + "BlockChoy": "Block Choy may look tasty, but its real gift is far more delicious - a menu of healing powers is standing by to RE-FUEL its allies.", "Number9": "Number 9 is a solid choice despite appearances, but remember - it has to FLY THROUGH ENEMIES before it can attack them.", "Axobubble": "Axobubble is a born defender, but its magic is sneaky - buffs, curses and other mischief are sure to bubble up to HELP THE TEAM.", "Unika": "Unika is cool under pressure, but don't let its delicate appearance confuse you - it's ready with an ICE COLD STRIKE!", diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index 9e77d06..c1c1c2d 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -30,6 +30,9 @@ import zlib from 'zlib'; import { N_STATS_SOT, TACTICS_ADV_NAMES_MAP, VERSION_LENGTH } from './constants'; import { DNAFactoryV1 } from './dna_factory_v1'; import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; +import { compressToBase64, decompressFromBase64 } from 'lz-string'; +import raritiesRead from './deps/rarities_read.json'; +import neftiesInfo from './deps/nefties_info.json'; const dnaSchemas: Record = { '4.0.0': dnaSchemaV4_0 as DNASchemaV4, @@ -40,10 +43,14 @@ const adventuresStats: Record = { }; export class DNAFactoryV2 { - latestSchemasSubversions: Record; + private latestSchemasSubversions: Record; + private codeNameToKey: Record; constructor() { this.latestSchemasSubversions = {}; - return; + this.codeNameToKey = {} as any; + Object.entries(this.getDNASchema(LATEST_SCHEMA_VERSION).archetypes).forEach(([key, codename]) => { + this.codeNameToKey[codename as NeftyCodeName] = key; + }); } // get latest minor version from a major version @@ -74,7 +81,7 @@ export class DNAFactoryV2 { throw new Error(`No rarity found: ${weightsSum}, ${random}`); } - private getDNASchema(version?: version): DNASchemaV4 { + getDNASchema(version?: version): DNASchemaV4 { if (!version) return dnaSchemas[LATEST_SCHEMA_VERSION]; else if (dnaSchemas[version]) return dnaSchemas[version]; else if (this.latestSchemasSubversions[version]) return dnaSchemas[this.latestSchemasSubversions[version]]; @@ -89,13 +96,17 @@ export class DNAFactoryV2 { return dnaSchemas[completeVersion]; } + private serializeDna(data: DnaDataV2): string { + return compressToBase64(JSON.stringify(data)); + } + private deserializeDna(dna: string): DnaDataV2 { - return JSON.parse(Buffer.from(dna, 'base64').toString()); + return JSON.parse(decompressFromBase64(dna)) as DnaDataV2; } private getDna(version: version, data: DnaDataV2): string { const versionDNAFormat = toPaddedHexa(version, 4); - const serializedData = Buffer.from(JSON.stringify(data)).toString('base64'); + const serializedData = this.serializeDna(data); const dna = versionDNAFormat + serializedData; return dna; } @@ -156,77 +167,234 @@ export class DNAFactoryV2 { return raw; } - generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { - const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); - console.log(dnaSchema.version); + private computeSOTStats(neftyCodeName: NeftyCodeName, dataAdv: ParseDataPerc): ParseDataComputed { + const neftyCodenameId = TACTICS_ADV_NAMES_MAP[neftyCodeName]; + const computed = {} as ParseDataComputed; + const sotStatsCurrent = adventuresStats[LATEST_ADVENTURES_STATS_VERSION].nefties[neftyCodenameId]; + if (!sotStatsCurrent) { + debugger; + throw new Error(`No SOT stats found for ${neftyCodenameId}`); + } + Object.entries(dataAdv).forEach(([statName, percentage]) => { + const key = `${statName}Computed` as keyof ParseDataComputed; + const minK = `${statName}Min` as keyof AdvStatsJSONValue; + const min = sotStatsCurrent[minK]; + const maxK = `${statName}Max` as keyof AdvStatsJSONValue; + const max = sotStatsCurrent[maxK]; + const value = (percentage / 100) * (max - min) + min; + computed[key] = Math.round(value); + }); + return computed; + } + + private validateArchetypeIndex(archetypeIndex: string) { + if (!Number.isInteger(parseInt(archetypeIndex))) { + throw new Error(`Invalid archetype index: ${archetypeIndex}`); + } + } + + private initializeDnaData(version: string): DnaDataV2 { const dnaData = {} as DnaDataV2; - dnaData.version = dnaSchema.version; + dnaData.version = version; + return dnaData; + } + private createDataData(dnaSchema: any, archetypeIndex: string, grade: Grade, rarityPreset?: Rarity): DnaDataData { const dataData = {} as DnaDataData; dataData.grade = grade; dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; - dnaData.data = dataData; + if (!dataData.neftyCodeName) { + throw new Error(`No archetype found for index ${archetypeIndex}`); + } + return dataData; + } + private createDataDataFromV1(dataV1: any): DnaDataData { + const dataData = {} as DnaDataData; + dataData.grade = dataV1.data.grade; + dataData.rarity = dataV1.data.rarity; + dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; + return dataData; + } + + private createDataAdv(stats: number[]): ParseDataPerc { + const [hp, atk, def, speed] = stats; const dataAdv = {} as ParseDataPerc; - const [hp, atk, def, speed] = this._generateStatsForRarity(N_STATS_SOT, grade, dataData.rarity); Object.assign(dataAdv, { hp, atk, def, speed }); - dnaData.dataAdv = dataAdv; + return dataAdv; + } - const dna = this.getDna(dnaSchema.version, dnaData); - return dna; + private createDataAdvFromV1(stats: ParseDataPerc): ParseDataPerc { + const { hp: hpP, atk: atkP, def: defP, speed: speedP } = stats; + const dataAdv = {} as ParseDataPerc; + Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + return dataAdv; + } + + getArchetypeKeyByNeftyCodeName(neftyCodeName: NeftyCodeName): string { + const archetypeKey = this.codeNameToKey[neftyCodeName]; + if (!archetypeKey) { + throw new Error(`No archetype found for ${neftyCodeName}`); + } + return archetypeKey; + } + + /** + * Returns rarity from stats average + * @param statsAverage average of all stats, from 0 to 100; + */ + getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true, grade: Grade = 'prime'): Rarity | null { + const rarity = Object.entries(raritiesRead).find(([rarity, rarityInfo]) => { + return ( + statsAverage >= rarityInfo.average_stats_range[0] && + ((statsAverage === 100 && statsAverage === rarityInfo.average_stats_range[1]) || + statsAverage < rarityInfo.average_stats_range[1]) + ); + }); + if (!rarity) { + if (raiseErrorOnNotFound) throw new Error(`Rarity not found for stats average ${statsAverage}`); + else return null; + } + return rarity[0] as Rarity; + } + + // generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { + // if (!Number.isInteger(parseInt(archetypeIndex))) { + // throw new Error(`Invalid archetype index: ${archetypeIndex}`); + // } + // const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + // const dnaData = {} as DnaDataV2; + // dnaData.version = dnaSchema.version; + + // const dataData = {} as DnaDataData; + // dataData.grade = grade; + // dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); + // dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; + // if (!dataData.neftyCodeName) { + // throw new Error(`No archetype found for index ${archetypeIndex}`); + // } + // dnaData.data = dataData; + + // const dataAdv = {} as ParseDataPerc; + // const [hp, atk, def, speed] = this._generateStatsForRarity(N_STATS_SOT, grade, dataData.rarity); + // Object.assign(dataAdv, { hp, atk, def, speed }); + // dnaData.dataAdv = dataAdv; + + // const dna = this.getDna(dnaSchema.version, dnaData); + // return dna; + // } + + // generateStarterNeftyDNA(archetypeIndex: string, version?: string) { + // if (!Number.isInteger(parseInt(archetypeIndex))) { + // throw new Error(`Invalid archetype index: ${archetypeIndex}`); + // } + // const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + // const dnaData = {} as DnaDataV2; + // dnaData.version = dnaSchema.version; + + // const dataData = {} as DnaDataData; + // dataData.grade = 'standard'; + // dataData.rarity = 'Uncommon'; + // dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; + // if (!dataData.neftyCodeName) { + // throw new Error(`No archetype found for index ${archetypeIndex}`); + // } + // dnaData.data = dataData; + + // const dataAdv = {} as ParseDataPerc; + // const [hp, atk, def, speed] = Array(N_STATS_SOT).fill(30); + // Object.assign(dataAdv, { hp, atk, def, speed }); + // dnaData.dataAdv = dataAdv; + + // const dna = this.getDna(dnaSchema.version, dnaData); + // return dna; + // } + + // generateNeftyDNAFromV1Dna( + // dnaFactoryV1: DNAFactoryV1, + // v1Dna: string, + // newSotStats?: ParseDataPerc, + // newVersion?: version + // ) { + // const dataV1 = dnaFactoryV1.parse(v1Dna); + // const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); + // const dnaData = {} as DnaDataV2; + // const dataAdv = {} as ParseDataPerc; + // if (newSotStats) { + // const { hp: hpP, atk: atkP, def: defP, speed: speedP } = newSotStats; + // Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + // } else { + // const { hp: hpP, atk: atkP, def: defP, speed: speedP } = dataV1.dataAdv; + // Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + // } + // dnaData.dataAdv = dataAdv; + // const dataData = {} as DnaDataData; + // dataData.grade = dataV1.data.grade; + // dataData.rarity = dataV1.data.rarity; + // dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; + // dnaData.data = dataData; + // dnaData.version = dnaSchema.version; + // const dna = this.getDna(dnaSchema.version, dnaData); + // return dna; + // } + + parse(dnaString: string): ParseV2 { + const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); + const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); + debugger; + const dataAdv = Object.assign( + {}, + dataRaw.dataAdv, + this.computeSOTStats(dataRaw.data.neftyCodeName, dataRaw.dataAdv) + ); + const displayName = neftiesInfo.code_to_displayName[dataRaw.data.neftyCodeName]; + const data = Object.assign(dataRaw.data, { displayName }); + const parsed: ParseV2 = Object.assign({ version: dataRaw.version }, { dataAdv, dataRaw, data }); + return parsed; + } + + generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { + this.validateArchetypeIndex(archetypeIndex); + const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + + const dnaData = this.initializeDnaData(dnaSchema.version); + dnaData.data = this.createDataData(dnaSchema, archetypeIndex, grade, rarityPreset); + + const stats = this._generateStatsForRarity(N_STATS_SOT, grade, dnaData.data.rarity); + dnaData.dataAdv = this.createDataAdv(stats); + + return this.getDna(dnaSchema.version, dnaData); + } + + generateStarterNeftyDNA(archetypeIndex: string, version?: string) { + this.validateArchetypeIndex(archetypeIndex); + const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + + const dnaData = this.initializeDnaData(dnaSchema.version); + dnaData.data = this.createDataData(dnaSchema, archetypeIndex, 'standard', 'Uncommon'); + + const stats = Array(N_STATS_SOT).fill(30); + dnaData.dataAdv = this.createDataAdv(stats); + + return this.getDna(dnaSchema.version, dnaData); } generateNeftyDNAFromV1Dna( dnaFactoryV1: DNAFactoryV1, v1Dna: string, newSotStats?: ParseDataPerc, - newVersion?: version + newVersion?: string ) { const dataV1 = dnaFactoryV1.parse(v1Dna); const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); - const dnaData = {} as DnaDataV2; - const dataAdv = {} as ParseDataPerc; - if (newSotStats) { - const { hp: hpP, atk: atkP, def: defP, speed: speedP } = newSotStats; - Object.assign(dataAdv, { hpP, atkP, defP, speedP }); - } else { - const { hp: hpP, atk: atkP, def: defP, speed: speedP } = dataV1.dataAdv; - Object.assign(dataAdv, { hpP, atkP, defP, speedP }); - } - dnaData.dataAdv = dataAdv; - const dataData = {} as DnaDataData; - dataData.grade = dataV1.data.grade; - dataData.rarity = dataV1.data.rarity; - dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; - dnaData.data = dataData; - dnaData.version = dnaSchema.version; - const dna = this.getDna(dnaSchema.version, dnaData); - return dna; - } - parse(dnaString: string): ParseV2 { - const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); - const data = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); - const computedStats: ParseDataComputed = this.computeSOTStats(data.data.neftyCodeName, data.dataAdv); - Object.assign(data.dataAdv, computedStats); - return data as ParseV2; - } + const dnaData = this.initializeDnaData(dnaSchema.version); + dnaData.data = this.createDataDataFromV1(dataV1); - private computeSOTStats(neftyCodeName: NeftyCodeName, dataAdv: ParseDataPerc): ParseDataComputed { - const neftyCodenameId = TACTICS_ADV_NAMES_MAP[neftyCodeName]; - const computed = {} as ParseDataComputed; - const sotStatsCurrent = adventuresStats[LATEST_ADVENTURES_STATS_VERSION].nefties[neftyCodenameId]; - if (!sotStatsCurrent) throw new Error(`No SOT stats found for ${neftyCodenameId}`); - Object.entries(dataAdv).forEach(([statName, percentage]) => { - const key = `${statName}Computed` as keyof ParseDataComputed; - const minK = `${statName}Min` as keyof AdvStatsJSONValue; - const min = sotStatsCurrent[minK]; - const maxK = `${statName}Max` as keyof AdvStatsJSONValue; - const max = sotStatsCurrent[maxK]; - const value = (percentage / 100) * (max - min) + min; - computed[key] = Math.round(value); - }); - return computed; + const stats = newSotStats ?? dataV1.dataAdv; + dnaData.dataAdv = this.createDataAdvFromV1(stats); + + return this.getDna(dnaSchema.version, dnaData); } } diff --git a/ts/src/eggs_factory_v2.ts b/ts/src/eggs_factory_v2.ts index 3110b94..aac866e 100644 --- a/ts/src/eggs_factory_v2.ts +++ b/ts/src/eggs_factory_v2.ts @@ -32,7 +32,7 @@ export class EggsFactoryV2 { } getDroppableStandardNefties(): DroppableNeftyInfoV2[] { - return this.eggInfo.archetypes.map((neftyCodeName) => { + return this.standardEggInfo.archetypes.map((neftyCodeName) => { const r = {} as DroppableNeftyInfoV2; r.neftyCodeName = neftyCodeName; r.displayName = neftiesInfo.code_to_displayName[neftyCodeName] as string; @@ -43,14 +43,14 @@ export class EggsFactoryV2 { hatch(): { archetypeKey: string; neftyCodeName: NeftyCodeName } { const droppableArchetypes = this.eggInfo.archetypes; const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; - const archetypeKey = neftyCodeName; + const archetypeKey = this.df.getArchetypeKeyByNeftyCodeName(neftyCodeName); return { archetypeKey, neftyCodeName }; } hatchStandard(): { archetypeKey: string; neftyCodeName: NeftyCodeName } { const droppableArchetypes = this.standardEggInfo.archetypes; const neftyCodeName = droppableArchetypes[Math.floor(Math.random() * droppableArchetypes.length)]; - const archetypeKey = neftyCodeName; + const archetypeKey = this.df.getArchetypeKeyByNeftyCodeName(neftyCodeName); return { archetypeKey, neftyCodeName }; } } diff --git a/ts/src/interfaces/types.ts b/ts/src/interfaces/types.ts index 8f6e1a1..a783e08 100644 --- a/ts/src/interfaces/types.ts +++ b/ts/src/interfaces/types.ts @@ -215,7 +215,7 @@ export interface DroppableNeftyInfo { } export interface DroppableNeftyInfoV2 { - neftyCodeName: string; + neftyCodeName: NeftyCodeName; displayName: string; } @@ -234,6 +234,10 @@ export interface DnaDataData { neftyCodeName: NeftyCodeName; } +export interface DnaDataDataParsed extends DnaDataData { + displayName: string; +} + export interface DnaDataV2 { version: string; data: DnaDataData; @@ -242,6 +246,7 @@ export interface DnaDataV2 { export interface ParseV2 { version: string; - data: DnaDataData; + dataRaw: DnaDataV2; + data: DnaDataDataParsed; dataAdv: ParseDataAdv; } diff --git a/ts/tests/dna.v2.spec.ts b/ts/tests/dna.v2.spec.ts index 4751a02..5c46520 100644 --- a/ts/tests/dna.v2.spec.ts +++ b/ts/tests/dna.v2.spec.ts @@ -1,11 +1,190 @@ -import { DNAFactory } from '../src'; +import { DNAFactory, EggsFactory, Rarity, utils } from '../src'; +import nefties_info from '../src/deps/nefties_info.json'; +import assert from 'assert'; +import raritiesGeneration from '../src/deps/rarities_generation.json'; +import { readdirSync, readFileSync } from 'fs'; +import { DNASchema, DNASchemaV4, NeftyCodeName, ParseDataPerc } from '../src/interfaces/types'; +import { LAST_SUPPORTED_VERSION_BY_V1, TACTICS_ADV_NAMES_MAP } from '../src/constants'; +import { LATEST_VERSION } from '../src/deps/schemas/latest'; + +const displayNamesProd = [ + 'Axobubble', + 'Bitebit', + 'Dipking', + 'Dinobit', + 'Shiba Ignite', + 'Zzoo', + 'Block Choy', + 'Number 9', + 'Unika', + 'Chocomint', + 'Cybertooth', + 'Wassie', + 'Dracurve', + 'Raccoin', + 'Shibark', + 'Unikirin', + 'Beeblock', + 'Chocorex', + 'Keybab', + 'Bloomtail', +]; + +const allSchemaVersions = readdirSync('./src/deps/schemas') + .filter((v) => v.endsWith('json')) + .map((v) => { + const index = v.indexOf('_v'); + return v.slice(index + 2, index + 7); + }) + .filter((v) => parseInt(v.split('.')[0]) > 3); describe('Basic', () => { - it('should work', () => { + it('DNA should parse', () => { + const df = new DNAFactory(); + Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk, eggInfo]) => { + const ef = new EggsFactory(eggPk, df); + const droppableNefties = ef.getDroppableNefties(); + const schema = df.getDNASchema(LATEST_VERSION); + const archetypes = Object.entries(schema.archetypes); + droppableNefties.forEach(({ neftyCodeName, displayName }) => { + assert.ok(displayName); + assert.ok(neftyCodeName); + assert.ok(TACTICS_ADV_NAMES_MAP[neftyCodeName]); + const archetypeKey = df.getArchetypeKeyByNeftyCodeName(neftyCodeName); + assert.ok(archetypeKey); + assert.ok(nefties_info.code_to_displayName[neftyCodeName]); + assert.ok( + (nefties_info.family_to_description as any)[ + nefties_info.code_to_displayName[neftyCodeName].replace(/\s+/g, '') + ], + `Family description not found for ${nefties_info.code_to_displayName[neftyCodeName].replace(/\s+/g, '')}` + ); + const dna = df.generateNeftyDNA(archetypeKey, 'prime'); + const data = df.parse(dna); + assert.ok(data.data); + assert.ok(data.data.grade); + assert.ok(data.data.neftyCodeName); + assert.ok(data.data.rarity); + assert.ok(data.dataAdv); + assert.ok(Number.isInteger(data.dataAdv.atk)); + assert.ok(Number.isInteger(data.dataAdv.def)); + assert.ok(Number.isInteger(data.dataAdv.hp)); + assert.ok(Number.isInteger(data.dataAdv.speed)); + assert.ok(Number.isInteger(data.dataAdv.atkComputed)); + assert.ok(Number.isInteger(data.dataAdv.defComputed)); + assert.ok(Number.isInteger(data.dataAdv.hpComputed)); + assert.ok(Number.isInteger(data.dataAdv.speedComputed)); + assert.ok(data.version); + }); + }); + }); + + // Display names are used to compute image URLs + it('Ensure display names never change', () => { + Object.values(nefties_info.code_to_displayName).forEach((displayName) => { + assert(displayNamesProd.includes(displayName), `${displayName} is not in displayNamesProd`); + }); + }); + + it('Generated Nefty DNA version matches forced version', () => { const df = new DNAFactory(); - const dna = df.generateNeftyDNA('0', 'prime'); - console.log(dna); - const parsed = df.parse(dna); - console.log(parsed); + const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); + for (let index = 0; index < allSchemaVersions.length; index++) { + const version = allSchemaVersions[index]; + const majorVersion = version.split('.')[0]; + const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime', version); + const parsed = df.parse(dna); + const parsedMajorVersion = parsed.version.split('.')[0]; + assert.equal(majorVersion, parsedMajorVersion); + } + }); +}); + +describe('Rarity', () => { + it('Rarity matches the average stats for latest version', () => { + const df = new DNAFactory(); + const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); + const rarityStats: (keyof ParseDataPerc)[] = ['hp', 'atk', 'def', 'speed']; + Object.entries(raritiesGeneration.prime).forEach(([rarity, rarityInfo]) => { + // for (let i = 0; i < 1e3; i++) { + const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime', undefined, rarity as Rarity); + const parsed = df.parse(dna); + assert.deepEqual(parsed.data.rarity, rarity); + const statsAvg = + utils.getAverageFromRaw( + rarityStats.map((v) => parsed.dataAdv[v]), + rarityStats.map((v) => 100) + ) * 100; + assert.deepEqual(df.getRarityFromStatsAvg(statsAvg), rarity); + assert.ok(statsAvg >= rarityInfo.average_stats_range[0]); + if (statsAvg === 100) assert.ok(statsAvg === rarityInfo.average_stats_range[1]); + else assert.ok(statsAvg < rarityInfo.average_stats_range[1]); + }); + }); +}); + +describe('starter eggs', () => { + it('Starter egg should be hatched with constant stats and rarity', () => { + const standardEggs = EggsFactory.getAllStandardEggs(); + const eggPk = Object.keys(standardEggs)[0]; + const df = new DNAFactory(); + const ef = new EggsFactory(eggPk, df); + for (let index = 0; index < 10; index++) { + const dna = df.generateStarterNeftyDNA(ef.hatchStandard().archetypeKey); + assert(dna); + const data = df.parse(dna); + const expectedRawStatValue = 30; + assert.equal(['Dipking', 'Block Choy', 'Number 9'].includes(data.data.displayName), true); + assert.equal(data.dataAdv.hp, expectedRawStatValue); + assert.equal(data.dataAdv.atk, expectedRawStatValue); + assert.equal(data.dataAdv.def, expectedRawStatValue); + assert.equal(data.dataAdv.speed, expectedRawStatValue); + } + }); +}); + +describe('standard eggs', () => { + it('all standard eggs should be hatchable', () => { + const df = new DNAFactory(); + const standardEggs = EggsFactory.getAllStandardEggs(); + Object.keys(standardEggs).forEach((eggName) => { + const ef = new EggsFactory(eggName, df); + const archetypeKey = ef.hatchStandard().archetypeKey; + if (eggName === 'starter_egg') { + const dna = df.generateStarterNeftyDNA(archetypeKey); + assert(dna); + const parsedDna = df.parse(dna); + assert.equal(standardEggs[eggName].archetypes.includes(parsedDna.data.neftyCodeName), true); + } else { + const dna = df.generateNeftyDNA(archetypeKey, 'standard'); + assert(dna); + const parsedDna = df.parse(dna); + assert.equal(standardEggs[eggName].archetypes.includes(parsedDna.data.neftyCodeName), true); + } + }); + }); +}); + +describe('droppable nefties', () => { + const df = new DNAFactory(); + it('all standard eggs archetypes are droppable', () => { + const standardEggs = EggsFactory.getAllStandardEggs(); + Object.keys(standardEggs).forEach((eggName) => { + const ef = new EggsFactory(eggName, df); + const droppableStandardNefties = ef.getDroppableStandardNefties(); + + assert(droppableStandardNefties); + assert.equal(droppableStandardNefties.length, standardEggs[eggName].archetypes.length); + }); + }); + it('all prime eggs neftie archetypes are droppable', () => { + const primeEggs = EggsFactory.getAllEggs(); + Object.keys(primeEggs).forEach((eggName) => { + const ef = new EggsFactory(eggName, df); + const droppableNefties = ef.getDroppableNefties(); + + assert(droppableNefties); + assert.equal(droppableNefties.length, primeEggs[eggName].archetypes.length); + }); }); }); diff --git a/ts/yarn.lock b/ts/yarn.lock index 9280e3e..83384b0 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -608,11 +608,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -compress-json@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/compress-json/-/compress-json-3.1.0.tgz#337e0a5b28b180fb849bcb6913bf361a5b3c0d57" - integrity sha512-Zcq4jRC5ZpfaOY3mbBWOANtGuMHJ/hsTENcwN1/lEkrogcoAF7HBma1RLe/CICZO6IquK1U0EaPzmnlDIFRNjA== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1645,6 +1640,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -1737,11 +1737,6 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -nanoid@^5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6" - integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" From 3f58dc19a996dafcd86bdc2808f9c62fd130ce30 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:57:05 +0200 Subject: [PATCH 15/22] Remove unused code --- ts/src/dna_factory_v2.ts | 92 +--------------------------------------- 1 file changed, 1 insertion(+), 91 deletions(-) diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index c1c1c2d..b88cf7e 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -15,18 +15,8 @@ import { import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from './deps/schemas/latest'; import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; import dnaSchemaV4_0 from './deps/schemas/aurory_dna_v4.0.0.json'; -import { - getAverageFromRaw, - getCategoryKeyFromName, - getLatestSubversion, - randomInt, - randomNormal, - toPaddedHexa, - toUnPaddedHexa, - unpad, -} from './utils'; +import { getAverageFromRaw, getLatestSubversion, randomInt, randomNormal, toPaddedHexa, toUnPaddedHexa } from './utils'; import raritiesGeneration from './deps/rarities_generation.json'; -import zlib from 'zlib'; import { N_STATS_SOT, TACTICS_ADV_NAMES_MAP, VERSION_LENGTH } from './constants'; import { DNAFactoryV1 } from './dna_factory_v1'; import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; @@ -259,86 +249,6 @@ export class DNAFactoryV2 { return rarity[0] as Rarity; } - // generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { - // if (!Number.isInteger(parseInt(archetypeIndex))) { - // throw new Error(`Invalid archetype index: ${archetypeIndex}`); - // } - // const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); - // const dnaData = {} as DnaDataV2; - // dnaData.version = dnaSchema.version; - - // const dataData = {} as DnaDataData; - // dataData.grade = grade; - // dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); - // dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; - // if (!dataData.neftyCodeName) { - // throw new Error(`No archetype found for index ${archetypeIndex}`); - // } - // dnaData.data = dataData; - - // const dataAdv = {} as ParseDataPerc; - // const [hp, atk, def, speed] = this._generateStatsForRarity(N_STATS_SOT, grade, dataData.rarity); - // Object.assign(dataAdv, { hp, atk, def, speed }); - // dnaData.dataAdv = dataAdv; - - // const dna = this.getDna(dnaSchema.version, dnaData); - // return dna; - // } - - // generateStarterNeftyDNA(archetypeIndex: string, version?: string) { - // if (!Number.isInteger(parseInt(archetypeIndex))) { - // throw new Error(`Invalid archetype index: ${archetypeIndex}`); - // } - // const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); - // const dnaData = {} as DnaDataV2; - // dnaData.version = dnaSchema.version; - - // const dataData = {} as DnaDataData; - // dataData.grade = 'standard'; - // dataData.rarity = 'Uncommon'; - // dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; - // if (!dataData.neftyCodeName) { - // throw new Error(`No archetype found for index ${archetypeIndex}`); - // } - // dnaData.data = dataData; - - // const dataAdv = {} as ParseDataPerc; - // const [hp, atk, def, speed] = Array(N_STATS_SOT).fill(30); - // Object.assign(dataAdv, { hp, atk, def, speed }); - // dnaData.dataAdv = dataAdv; - - // const dna = this.getDna(dnaSchema.version, dnaData); - // return dna; - // } - - // generateNeftyDNAFromV1Dna( - // dnaFactoryV1: DNAFactoryV1, - // v1Dna: string, - // newSotStats?: ParseDataPerc, - // newVersion?: version - // ) { - // const dataV1 = dnaFactoryV1.parse(v1Dna); - // const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); - // const dnaData = {} as DnaDataV2; - // const dataAdv = {} as ParseDataPerc; - // if (newSotStats) { - // const { hp: hpP, atk: atkP, def: defP, speed: speedP } = newSotStats; - // Object.assign(dataAdv, { hpP, atkP, defP, speedP }); - // } else { - // const { hp: hpP, atk: atkP, def: defP, speed: speedP } = dataV1.dataAdv; - // Object.assign(dataAdv, { hpP, atkP, defP, speedP }); - // } - // dnaData.dataAdv = dataAdv; - // const dataData = {} as DnaDataData; - // dataData.grade = dataV1.data.grade; - // dataData.rarity = dataV1.data.rarity; - // dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; - // dnaData.data = dataData; - // dnaData.version = dnaSchema.version; - // const dna = this.getDna(dnaSchema.version, dnaData); - // return dna; - // } - parse(dnaString: string): ParseV2 { const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); From e69403f0c11bf7c48300f3edcdb68deb853ae5e6 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:03:54 +0200 Subject: [PATCH 16/22] Remove unused code --- ts/src/adventure_stats.ts | 96 ------------------------ ts/src/deps/nefties_info_deprecated.json | 46 ++++++++++++ ts/src/dna_factory_v1.ts | 14 ++-- ts/src/dna_factory_v2.ts | 26 ++++--- ts/tests/distribution.spec.ts | 8 +- ts/tests/dna.spec.ts | 21 +++--- ts/tests/dna.v2.spec.ts | 14 ++-- ts/tests/workers/distribution-worker.ts | 12 +-- ts/tests/workers/stats-mean-worker.ts | 14 ++-- 9 files changed, 102 insertions(+), 149 deletions(-) create mode 100644 ts/src/deps/nefties_info_deprecated.json diff --git a/ts/src/adventure_stats.ts b/ts/src/adventure_stats.ts index 205737a..4d5785d 100644 --- a/ts/src/adventure_stats.ts +++ b/ts/src/adventure_stats.ts @@ -7,105 +7,9 @@ import { AdvStatsJSON, ParseDataPerc, AdvStatsJSONValue, - NeftyCodeNameId, NeftyCodeName, } from './interfaces/types'; -/** - * Allow to pick a random number from an array, but in a deterministic way. - * The same input array will produce the same output number. - */ -function deterministicRandomPicker(arr: number[]): number { - const deterministicRandoms = arr.map((value, index, array) => { - const prevIndex = index === 0 ? array.length - 1 : index - 1; - const nextIndex = index === array.length - 1 ? 0 : index + 1; - return (array[prevIndex] + array[nextIndex] + value) % array.length; - }); - arr.sort((a, b) => { - const indexA = arr.indexOf(a); - const indexB = arr.indexOf(b); - return deterministicRandoms[indexA] - deterministicRandoms[indexB]; - }); - return arr[0]; -} - -function removeGlitched(targetAverage: number, adventuresStatsOriginal: number[]): number[] { - const adventuresStats = [...adventuresStatsOriginal]; - const maxNum = Math.max(...adventuresStats); - const maxIndex = adventuresStats.indexOf(maxNum); - adventuresStats[maxIndex] = 6; - - while (floorAverage(adventuresStats) !== targetAverage) { - const validIndices = adventuresStats.reduce((indices, stat, index) => { - if (stat !== 0 && index !== maxIndex) { - indices.push(index); - } - return indices; - }, [] as number[]); - - const indexToModify = deterministicRandomPicker(validIndices); - adventuresStats[indexToModify] -= 1; - } - - return adventuresStats; -} - -function makeGlitched(targetAverage: number, adventuresStatsOriginal: number[]): number[] { - const adventuresStats = adventuresStatsOriginal.map((num) => (num > 5 ? 5 : num)); - - while (floorAverage(adventuresStats) !== targetAverage) { - const validIndices = adventuresStats.reduce((indices, stat, index) => { - if (stat !== 5) { - indices.push(index); - } - return indices; - }, [] as number[]); - - const indexToModify = deterministicRandomPicker(validIndices); - adventuresStats[indexToModify] += 1; - } - - return adventuresStats; -} - -function makeSchimmering(targetAverage: number, adventuresStatsOriginal: number[]): number[] { - const adventuresStats = adventuresStatsOriginal.map((num) => (num < 95 ? 95 : num)); - while (floorAverage(adventuresStats) !== targetAverage) { - const validIndices = adventuresStats.reduce((indices, stat, index) => { - if (stat !== 95) { - indices.push(index); - } - return indices; - }, [] as number[]); - - const indexToModify = deterministicRandomPicker(validIndices); - adventuresStats[indexToModify] -= 1; - } - - return adventuresStats; -} - -function removeSchimmering(targetAverage: number, adventuresStatsOriginal: number[]): number[] { - const adventuresStats = [...adventuresStatsOriginal]; - const min = Math.min(...adventuresStats); - const minIndex = adventuresStats.indexOf(min); - adventuresStats[minIndex] = 94; - - while (floorAverage(adventuresStats) !== targetAverage) { - const validIndices = adventuresStats.reduce((indices, stat, index) => { - if (stat !== 100 && index !== minIndex) { - indices.push(index); - } - return indices; - }, [] as number[]); - - const indexToModify = deterministicRandomPicker(validIndices); - adventuresStats[indexToModify] += 1; - } - - return adventuresStats; -} - function floorAverage(stats: number[]): number { return Math.floor(stats.reduce((sum, stat) => sum + Math.round(stat), 0) / stats.length); } diff --git a/ts/src/deps/nefties_info_deprecated.json b/ts/src/deps/nefties_info_deprecated.json new file mode 100644 index 0000000..270addb --- /dev/null +++ b/ts/src/deps/nefties_info_deprecated.json @@ -0,0 +1,46 @@ +{ + "code_to_displayName": { + "Nefty_Bitebit": "Bitebit", + "Nefty_Dipking": "Dipking", + "Nefty_Dinobit": "Dinobit", + "Nefty_ShibaIgnite": "Shiba Ignite", + "Nefty_Zzoo": "Zzoo", + "Nefty_Blockchoy": "Block Choy", + "Nefty_Number9": "Number 9", + "Nefty_Axobubble": "Axobubble", + "Nefty_Unika": "Unika", + "Nefty_Chocomint": "Chocomint", + "Nefty_Cybertooth": "Cybertooth", + "Nefty_Wassie": "Wassie", + "Nefty_Dracurve": "Dracurve", + "Nefty_Raccoin": "Raccoin", + "Nefty_Shibark": "Shibark", + "Nefty_Unikirin": "Unikirin", + "Nefty_Beeblock": "Beeblock", + "Nefty_Chocorex": "Chocorex", + "Nefty_Keybab": "Keybab", + "Nefty_Bloomtail": "Bloomtail" + }, + "family_to_description": { + "Bitebit": "Bitebit may look cuddly, but don't be fooled by its charm - those aren't paper hands... they're DIAMOND CLAWS!", + "Dipking": "Dipking is bursting with personality and magical power, but handle with care - all that pent up energy may have EXPLOSIVE results!", + "Dinobit": "Dinobit is big, strong and tough, but careful of that temper - Make it mad and watch out for a charge that can PLOW THROUGH EVERYTHING in its path!", + "Shiba": "Shiba Ignite is always ready to jump into the heat of battle, but while it may not be the fastest - a helping paw is always there to DEFEND ITS ALLIES.", + "Zzoo": "Zzoo has a big beak and a bad attitude, but if it's on your side, both can be an asset - SWIFT STRIKES make its mean streak your advantage!", + "Blockchoy": "Block Choy may look tasty, but its real gift is far more delicious - a menu of healing powers is standing by to RE-FUEL its allies.", + "Number9": "Number 9 is a solid choice despite appearances, but remember - it has to FLY THROUGH ENEMIES before it can attack them.", + "Axobubble": "Axobubble is a born defender, but its magic is sneaky - buffs, curses and other mischief are sure to bubble up to HELP THE TEAM.", + "Unika": "Unika is cool under pressure, but don't let its delicate appearance confuse you - it's ready with an ICE COLD STRIKE!", + "Chocomint": "Chocomints are born with a remnant of their shells on their heads. To pass into adulthood, a joust between two young Chocomints takes place. The victor is the one who cracks its shell to unveil its cherry.", + "Cybertooth": "Cybertooth uses its thick hide to fortify its defenses, skillfully weakening foes with its abilities to gain an advantage and deal significant damage.", + "Wassie": "Wassie is a nimble Neftie known for harnessing its abilities to enhance its speed and inflict substantial damage.", + "Dracurve": "Dracurve dictates the course of battle with shifts in stats and status effects. Exercise caution, for a few strategic choices can swiftly pave the way to devastation.", + "Raccoin": "Raccoin's a sly, laid-back Neftie, known for its nimbleness and love for naps.", + "Shibark": "Shibark is known to roam vast landscapes, unearthing treasures and marking its territory with resounding howls.", + "Unikirin": "Each step Unikirin takes resonates with the crackle of electric energy, a testament to its wild and untamed spirit.", + "Beeblock": "Beeblock, known for its calm nature, is tamed for its production of a deliciously sweet jelly named Nury. But when threatened, it delivers powerful stings, warding off any attacker.", + "Chocorex": "Praised for its loyalty, Chocorex stands as a favored companion and trusted mount within Tokane. However, due to its limited intelligence, it requires skilled riders.", + "Keybab": "When activating its self-defense mechanism, Keybab changes color to boiling red, and its spicy vapor makes anyone who inhales it shed tears.", + "Bloomtail": "Bloomtail is a spirited Neftie fueled by the flower adorning its back. It is said that the flower occasionally affects its movements, adding an intriguing twist to its nature!" + } +} diff --git a/ts/src/dna_factory_v1.ts b/ts/src/dna_factory_v1.ts index 0386c8b..8e9e66c 100644 --- a/ts/src/dna_factory_v1.ts +++ b/ts/src/dna_factory_v1.ts @@ -36,8 +36,8 @@ import adventuresStatsV0_0_6 from './deps/schemas/adventures/v0.0.6.json'; import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; import { LATEST_VERSION as LATEST_ABILTIIES_VERSION } from './deps/dictionaries/latest'; import abiltiesDictionaryV4 from './deps/dictionaries/abilities_dictionary_v0.4.0.json'; -import neftiesInfo from './deps/nefties_info.json'; import rarities from './deps/rarities.json'; +import neftiesInfo from './deps/nefties_info_deprecated.json'; import { DNA } from './dna'; import { getAverageFromRaw, @@ -275,7 +275,7 @@ export class DNAFactoryV1 { rarityPreset?: Rarity ) { const rarity = rarityPreset ?? this._getRandomRarity(grade); - const rarityIndex = Object.entries(dnaSchema.rarities).find(([_, rarityName]) => rarityName === rarity)?.[0]; + const rarityIndex = Object.entries(dnaSchema.rarities).find(([, rarityName]) => rarityName === rarity)?.[0]; if (!rarityIndex) throw new Error('Rarity not found'); const categoryKey = getCategoryKeyFromName('nefties', dnaSchema.categories); @@ -344,7 +344,7 @@ export class DNAFactoryV1 { `Archetype index not found. archetypeIndex ${archetypeIndex} schemaVersion ${schemaVersion} categoryKey ${categoryKey}` ); const rarity = 'Uncommon'; - const rarityIndex = Object.entries(dnaSchema.rarities).find(([_, rarityName]) => rarityName === rarity)?.[0]; + const rarityIndex = Object.entries(dnaSchema.rarities).find(([, rarityName]) => rarityName === rarity)?.[0]; if (!rarityIndex) throw new Error('Rarity not found'); const versionGeneInfo = dnaSchema.global_genes_header.find((gene_header) => gene_header.name === 'version'); @@ -393,7 +393,7 @@ export class DNAFactoryV1 { const abilityKeywords = this.getAbilitiesDictionary(version ?? this.latestAbilitiesVersion).keywords; const info = {} as AbilityInfo; for (const keyword in abilityKeywords) { - const [_, abilityName, infoType] = keyword.split('.'); + const [, abilityName, infoType] = keyword.split('.'); if (abilityName !== ability) continue; info[infoType as keyof AbilityInfo] = abilityKeywords[keyword as KeywordsKey]; if (info.name && info.description) return info; @@ -414,7 +414,7 @@ export class DNAFactoryV1 { * @param statsAverage average of all stats, from 0 to 100; */ getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true, grade: Grade = 'prime'): Rarity | null { - const rarity = Object.entries(this.rarities[grade]).find(([rarity, rarityInfo]) => { + const rarity = Object.entries(this.rarities[grade]).find(([, rarityInfo]) => { return ( statsAverage >= rarityInfo.average_stats_range[0] && ((statsAverage === 100 && statsAverage === rarityInfo.average_stats_range[1]) || @@ -454,7 +454,7 @@ export class DNAFactoryV1 { const genes = dnaSchema.categories[dnaSchemaReader.categoryKey].genes; const data: ParseData = Object.assign({} as ParseData, archetype.fixed_attributes); this._setRarity(data, dnaSchemaReader, dnaSchema); - this._setGrade(data, dnaSchemaReader, dnaSchema); + this._setGrade(data, dnaSchemaReader); const neftyNameCode = archetype.fixed_attributes.name as string; data['displayName'] = this.getDisplayNameFromCodeName(neftyNameCode); data['description'] = this.getFamilyDescription(archetype.fixed_attributes.family as string); @@ -506,7 +506,7 @@ export class DNAFactoryV1 { } } - private _setGrade(data: ParseData, dnaSchemaReader: DNASchemaReader, dnaSchema: DNASchema) { + private _setGrade(data: ParseData, dnaSchemaReader: DNASchemaReader) { if (dnaSchemaReader.categoryGenesHeader.grade) { // grade needs to be added to the dna generation process to support this // data.grade = (dnaSchema as DNASchemaV3).grades[dnaSchemaReader.categoryGenesHeader.grade]; diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index b88cf7e..0a1ecb5 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -11,11 +11,12 @@ import { ParseDataComputed, AdvStatsJSON, AdvStatsJSONValue, + Parse, } from './interfaces/types'; import { LATEST_VERSION as LATEST_SCHEMA_VERSION } from './deps/schemas/latest'; import { LATEST_VERSION as LATEST_ADVENTURES_STATS_VERSION } from './deps/schemas/adventures/latest'; import dnaSchemaV4_0 from './deps/schemas/aurory_dna_v4.0.0.json'; -import { getAverageFromRaw, getLatestSubversion, randomInt, randomNormal, toPaddedHexa, toUnPaddedHexa } from './utils'; +import { getAverageFromRaw, getLatestSubversion, randomInt, randomNormal, toPaddedHexa } from './utils'; import raritiesGeneration from './deps/rarities_generation.json'; import { N_STATS_SOT, TACTICS_ADV_NAMES_MAP, VERSION_LENGTH } from './constants'; import { DNAFactoryV1 } from './dna_factory_v1'; @@ -37,7 +38,7 @@ export class DNAFactoryV2 { private codeNameToKey: Record; constructor() { this.latestSchemasSubversions = {}; - this.codeNameToKey = {} as any; + this.codeNameToKey = {} as Record; Object.entries(this.getDNASchema(LATEST_SCHEMA_VERSION).archetypes).forEach(([key, codename]) => { this.codeNameToKey[codename as NeftyCodeName] = key; }); @@ -139,12 +140,12 @@ export class DNAFactoryV2 { while (pointsLeft) { distributePoints(); - raw = stats.map((stat, index) => Math.round((stat / 100) * maxValuePerStat)); + raw = stats.map((stat) => Math.round((stat / 100) * maxValuePerStat)); average = Math.floor( getAverageFromRaw( raw, - stats.map((_, index) => maxValuePerStat) + stats.map(() => maxValuePerStat) ) * 100 ); @@ -153,7 +154,7 @@ export class DNAFactoryV2 { } // if average = 1 for a non glitched or 95 for a schimmering, we may end up not enterring in the previous loop - if (!raw.length) raw = stats.map((stat, index) => Math.round((stat / 100) * maxValuePerStat)); + if (!raw.length) raw = stats.map((stat) => Math.round((stat / 100) * maxValuePerStat)); return raw; } @@ -189,7 +190,12 @@ export class DNAFactoryV2 { return dnaData; } - private createDataData(dnaSchema: any, archetypeIndex: string, grade: Grade, rarityPreset?: Rarity): DnaDataData { + private createDataData( + dnaSchema: DNASchemaV4, + archetypeIndex: string, + grade: Grade, + rarityPreset?: Rarity + ): DnaDataData { const dataData = {} as DnaDataData; dataData.grade = grade; dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); @@ -200,7 +206,7 @@ export class DNAFactoryV2 { return dataData; } - private createDataDataFromV1(dataV1: any): DnaDataData { + private createDataDataFromV1(dataV1: Parse): DnaDataData { const dataData = {} as DnaDataData; dataData.grade = dataV1.data.grade; dataData.rarity = dataV1.data.rarity; @@ -234,8 +240,8 @@ export class DNAFactoryV2 { * Returns rarity from stats average * @param statsAverage average of all stats, from 0 to 100; */ - getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true, grade: Grade = 'prime'): Rarity | null { - const rarity = Object.entries(raritiesRead).find(([rarity, rarityInfo]) => { + getRarityFromStatsAvg(statsAverage: number, raiseErrorOnNotFound = true): Rarity | null { + const rarity = Object.entries(raritiesRead).find(([, rarityInfo]) => { return ( statsAverage >= rarityInfo.average_stats_range[0] && ((statsAverage === 100 && statsAverage === rarityInfo.average_stats_range[1]) || @@ -250,7 +256,7 @@ export class DNAFactoryV2 { } parse(dnaString: string): ParseV2 { - const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); + // const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); debugger; const dataAdv = Object.assign( diff --git a/ts/tests/distribution.spec.ts b/ts/tests/distribution.spec.ts index 13d5053..f9b55a7 100644 --- a/ts/tests/distribution.spec.ts +++ b/ts/tests/distribution.spec.ts @@ -22,7 +22,7 @@ describe('Distribution', () => { * then checks if the deviation from the expected distribution is less than 20%. */ it('Means are evenly distributed', (done) => { - const statMeans = {} as any; + const statMeans = {} as Record; const loopCount = 15000; let loopDone = 0; const workers = [] as { id: number; worker: Worker }[]; @@ -83,9 +83,9 @@ describe('Distribution', () => { const df = new DNAFactory(undefined, undefined); const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); const rarityStats = ['hp', 'initiative', 'atk', 'def', 'eatk', 'edef']; - const statsCount = {} as any; + const statsCount = {} as Record; const loopCount = 1000; - Object.entries(rarities.prime).forEach(([rarity, rarityInfo]) => { + Object.entries(rarities.prime).forEach(([rarity]) => { for (let i = 0; i < loopCount; i++) { const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime', undefined, rarity as Rarity); const parsed = df.parse(dna); @@ -103,7 +103,7 @@ describe('Distribution', () => { // this may fail sometimes as we only do 300k iterations it('Rarity & Glitched & Schimmering distribution rates are within a specific range of the targets', (done) => { - const rarityCount: Record = {} as any; + const rarityCount = {} as Record; const loopCount = 300000; let loopDone = 0; const workers = [] as { id: number; worker: Worker }[]; diff --git a/ts/tests/dna.spec.ts b/ts/tests/dna.spec.ts index b992778..b22c491 100644 --- a/ts/tests/dna.spec.ts +++ b/ts/tests/dna.spec.ts @@ -165,7 +165,7 @@ const allSchemaVersions = readdirSync('./src/deps/schemas') describe('Basic', () => { it('DNA should parse', () => { const df = new DNAFactory(); - Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk, eggInfo]) => { + Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk]) => { const ef = new EggsFactory(eggPk, df); const droppableNefties = ef.getDroppableNefties(); droppableNefties.forEach(({ archetypeKey, archetype, displayName, description }) => { @@ -184,11 +184,10 @@ describe('Basic', () => { assert.ok(data.data.description); assert.ok(data.data.rarity); assert.ok(data.data.defaultImage); - assert.ok(data.data.imageByGame); - assert.ok(data.data.imageByGame.tactics); - assert.ok(data.data.imageByGame.tactics.medium); - assert.ok(data.data.imageByGame.tactics.small); - assert.ok(data.data.element); + // assert.ok(data.data.imageByGame); + // assert.ok(data.data.imageByGame.tactics); + // assert.ok(data.data.imageByGame.tactics.medium); + // assert.ok(data.data.imageByGame.tactics.small); assert.ok(Number.isInteger(data.data.hp)); assert.ok(Number.isInteger(data.data.initiative)); assert.ok(Number.isInteger(data.data.atk)); @@ -251,7 +250,6 @@ describe('Compute possible names, families and abilities', () => { const df = new DNAFactory(); const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); const neftyIndex = ef.hatch().archetypeKey; - debugger; const dna = df.generateNeftyDNA(neftyIndex, 'prime'); const category = df.getCategory('nefties', df.getDnaVersion(dna)); const neftyNames = new Set(); @@ -280,11 +278,10 @@ describe('Using previous schema 0.2.0', () => { it('Parsed stats should reflect the schema parameter as an input', () => { const forceVersion = '0.2.0'; const df = new DNAFactory(undefined, undefined); - const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); assert.throws(() => { // 7 does not exist on schema 0.2.0 const dna = df.generateNeftyDNA('7', 'prime', forceVersion); - const p = df.parse(dna, forceVersion); + df.parse(dna, forceVersion); }); const dinobitArchetypeIndex = '2'; const dna = df.generateNeftyDNA(dinobitArchetypeIndex, 'prime', forceVersion); @@ -317,7 +314,7 @@ describe('Rarity', () => { const statsAvg = utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100; assert.deepEqual(df.getRarityFromStatsAvg(statsAvg), rarity); assert.ok(statsAvg >= rarityInfo.average_stats_range[0]); @@ -339,11 +336,11 @@ describe('Rarity', () => { const statsAvg = utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100; assert.deepEqual(df.getRarityFromStatsAvg(statsAvg), rarity); assert.ok(statsAvg >= rarityInfo.average_stats_range[0]); - if (statsAvg === 100) assert.ok(statsAvg === rarityInfo.average_stats_range[1]); + if (statsAvg === 100) assert.ok(statsAvg === rarityInfo.average_stats_range[1] - 1); else assert.ok(statsAvg < rarityInfo.average_stats_range[1]); // } }); diff --git a/ts/tests/dna.v2.spec.ts b/ts/tests/dna.v2.spec.ts index 5c46520..336d921 100644 --- a/ts/tests/dna.v2.spec.ts +++ b/ts/tests/dna.v2.spec.ts @@ -2,10 +2,9 @@ import { DNAFactory, EggsFactory, Rarity, utils } from '../src'; import nefties_info from '../src/deps/nefties_info.json'; import assert from 'assert'; import raritiesGeneration from '../src/deps/rarities_generation.json'; -import { readdirSync, readFileSync } from 'fs'; -import { DNASchema, DNASchemaV4, NeftyCodeName, ParseDataPerc } from '../src/interfaces/types'; -import { LAST_SUPPORTED_VERSION_BY_V1, TACTICS_ADV_NAMES_MAP } from '../src/constants'; -import { LATEST_VERSION } from '../src/deps/schemas/latest'; +import { readdirSync } from 'fs'; +import { ParseDataPerc } from '../src/interfaces/types'; +import { TACTICS_ADV_NAMES_MAP } from '../src/constants'; const displayNamesProd = [ 'Axobubble', @@ -41,11 +40,9 @@ const allSchemaVersions = readdirSync('./src/deps/schemas') describe('Basic', () => { it('DNA should parse', () => { const df = new DNAFactory(); - Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk, eggInfo]) => { + Object.entries(EggsFactory.getAllEggs()).forEach(([eggPk]) => { const ef = new EggsFactory(eggPk, df); const droppableNefties = ef.getDroppableNefties(); - const schema = df.getDNASchema(LATEST_VERSION); - const archetypes = Object.entries(schema.archetypes); droppableNefties.forEach(({ neftyCodeName, displayName }) => { assert.ok(displayName); assert.ok(neftyCodeName); @@ -54,6 +51,7 @@ describe('Basic', () => { assert.ok(archetypeKey); assert.ok(nefties_info.code_to_displayName[neftyCodeName]); assert.ok( + // eslint-disable-next-line @typescript-eslint/no-explicit-any (nefties_info.family_to_description as any)[ nefties_info.code_to_displayName[neftyCodeName].replace(/\s+/g, '') ], @@ -113,7 +111,7 @@ describe('Rarity', () => { const statsAvg = utils.getAverageFromRaw( rarityStats.map((v) => parsed.dataAdv[v]), - rarityStats.map((v) => 100) + rarityStats.map(() => 100) ) * 100; assert.deepEqual(df.getRarityFromStatsAvg(statsAvg), rarity); assert.ok(statsAvg >= rarityInfo.average_stats_range[0]); diff --git a/ts/tests/workers/distribution-worker.ts b/ts/tests/workers/distribution-worker.ts index bedaf02..d83dc80 100644 --- a/ts/tests/workers/distribution-worker.ts +++ b/ts/tests/workers/distribution-worker.ts @@ -1,6 +1,6 @@ import { parentPort, workerData } from 'worker_threads'; -import { DNAFactoryV1 as DNAFactory } from '../../src/index'; -import { EggsFactoryV1 as EggsFactory } from '../../src/index'; +import { DNAFactoryV1 as DNAFactory } from '../../src/dna_factory_v1'; +import { EggsFactoryV1 as EggsFactory } from '../../src/eggs_factory_v1'; import { Rarity } from '../../src/interfaces/types'; import { utils } from '../../src'; @@ -16,14 +16,14 @@ async function run(loopCount: number) { const rarityStats = ['hp', 'initiative', 'atk', 'def', 'eatk', 'edef']; let glitchedCount = 0; let schimmeringCount = 0; - const rarityCount: Record = {} as any; + const rarityCount = {} as Record; for (let i = 0; i < loopCount; i++) { const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime'); const parsed = df.parse(dna); const statsAvg = utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100; if (statsAvg < 6) { const stats = rarityStats.map((v) => Math.round((parsed.raw[v] / 255) * 100)); @@ -36,7 +36,9 @@ async function run(loopCount: number) { schimmeringCount += 1; } } - const rarity = df.getRarityFromStatsAvg(statsAvg, true, 'prime')!; + const rarity = df.getRarityFromStatsAvg(statsAvg, true); + if (!rarity) throw new Error('Rarity not found'); + if (rarityCount[rarity]) rarityCount[rarity] += 1; else rarityCount[rarity] = 1; } diff --git a/ts/tests/workers/stats-mean-worker.ts b/ts/tests/workers/stats-mean-worker.ts index 5bed6bb..7deb7bb 100644 --- a/ts/tests/workers/stats-mean-worker.ts +++ b/ts/tests/workers/stats-mean-worker.ts @@ -1,9 +1,9 @@ import { parentPort, workerData } from 'worker_threads'; -import { DNAFactoryV1 as DNAFactory } from '../../src/index'; -import { EggsFactoryV1 as EggsFactory } from '../../src/index'; +import { DNAFactoryV1 as DNAFactory } from '../../src/dna_factory_v1'; +import { EggsFactoryV1 as EggsFactory } from '../../src/eggs_factory_v1'; import { Grade, Rarity } from '../../src/interfaces/types'; -import { utils } from '../../src'; import rarities from '../../src/deps/rarities.json'; +import { utils } from '../../src/'; export interface StatsMeanWorker { statMeans: Record; @@ -14,7 +14,7 @@ async function run(loopCount: number): Promise { const df = new DNAFactory(undefined, undefined); const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); const rarityStats = ['hp', 'initiative', 'atk', 'def', 'eatk', 'edef']; - const statMeans = {} as any; + const statMeans = {} as Record; Object.entries(rarities.prime).forEach(([rarity]) => { for (let i = 0; i < loopCount; i++) { const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime', undefined, rarity as Rarity); @@ -22,13 +22,13 @@ async function run(loopCount: number): Promise { const statsMean = Math.floor( utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100 ); statMeans[statsMean.toString()] = statMeans[statsMean.toString()] ? statMeans[statsMean.toString()] + 1 : 1; } }); - const standardStatMeans = {} as any; + const standardStatMeans = {} as Record; Object.entries(rarities.standard).forEach(([rarity]) => { for (let i = 0; i < loopCount; i++) { const dna = df.generateNeftyDNA(ef.hatch().archetypeKey, 'prime', undefined, rarity as Rarity); @@ -36,7 +36,7 @@ async function run(loopCount: number): Promise { const statsMean = Math.floor( utils.getAverageFromRaw( rarityStats.map((v) => parsed.raw[v]), - rarityStats.map((v) => 255) + rarityStats.map(() => 255) ) * 100 ); standardStatMeans[statsMean.toString()] = standardStatMeans[statsMean.toString()] From 10a1d5179a1bf6d87fbee116ab304e0b3a7b745a Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:20:53 +0200 Subject: [PATCH 17/22] Fix wrong adv stats names --- ts/src/dna_factory_v2.ts | 6 ++---- ts/tests/dna.v2.spec.ts | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index 0a1ecb5..2b84ae2 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -163,7 +163,6 @@ export class DNAFactoryV2 { const computed = {} as ParseDataComputed; const sotStatsCurrent = adventuresStats[LATEST_ADVENTURES_STATS_VERSION].nefties[neftyCodenameId]; if (!sotStatsCurrent) { - debugger; throw new Error(`No SOT stats found for ${neftyCodenameId}`); } Object.entries(dataAdv).forEach(([statName, percentage]) => { @@ -222,9 +221,9 @@ export class DNAFactoryV2 { } private createDataAdvFromV1(stats: ParseDataPerc): ParseDataPerc { - const { hp: hpP, atk: atkP, def: defP, speed: speedP } = stats; + const { hp, atk, def, speed } = stats; const dataAdv = {} as ParseDataPerc; - Object.assign(dataAdv, { hpP, atkP, defP, speedP }); + Object.assign(dataAdv, { hp, atk, def, speed }); return dataAdv; } @@ -258,7 +257,6 @@ export class DNAFactoryV2 { parse(dnaString: string): ParseV2 { // const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); - debugger; const dataAdv = Object.assign( {}, dataRaw.dataAdv, diff --git a/ts/tests/dna.v2.spec.ts b/ts/tests/dna.v2.spec.ts index 336d921..f2451dd 100644 --- a/ts/tests/dna.v2.spec.ts +++ b/ts/tests/dna.v2.spec.ts @@ -1,4 +1,4 @@ -import { DNAFactory, EggsFactory, Rarity, utils } from '../src'; +import { DNAFactory, DNAFactoryV1, EggsFactory, Rarity, utils } from '../src'; import nefties_info from '../src/deps/nefties_info.json'; import assert from 'assert'; import raritiesGeneration from '../src/deps/rarities_generation.json'; @@ -186,3 +186,27 @@ describe('droppable nefties', () => { }); }); }); + +describe('From V1 DNA', () => { + const df = new DNAFactory(); + const dfV1 = new DNAFactoryV1(); + const ef = new EggsFactory('8XaR7cPaMZoMXWBWgeRcyjWRpKYpvGsPF6dMwxnV4nzK', df); + it('From V1 DNA', () => { + const advStatKeys: (keyof ParseDataPerc)[] = ['hp', 'atk', 'def', 'speed']; + const dnaV1 = dfV1.generateNeftyDNA(ef.hatch().archetypeKey, 'prime'); + const parsedV1 = dfV1.parse(dnaV1); + const newStats = {} as Record; + advStatKeys.forEach((key) => { + newStats[key] = Math.min(parsedV1.dataAdv[key] + 1, 100); + }); + const newDna = df.generateNeftyDNAFromV1Dna(dfV1, dnaV1, newStats); + const newParsed = df.parse(newDna); + assert.deepEqual(newParsed.dataAdv.hp, newStats.hp); + assert.deepEqual(newParsed.dataAdv.atk, newStats.atk); + assert.deepEqual(newParsed.dataAdv.def, newStats.def); + assert.deepEqual(newParsed.dataAdv.speed, newStats.speed); + assert.deepEqual(newParsed.data.rarity, parsedV1.data.rarity); + assert.deepEqual(newParsed.data.displayName, parsedV1.data.displayName); + assert.deepEqual(newParsed.data.neftyCodeName, parsedV1.archetype.fixed_attributes.name); + }); +}); From 39c2bcec5de918152eeeae1d3197f36efd47763c Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:21:42 +0200 Subject: [PATCH 18/22] Update package.json version --- ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/package.json b/ts/package.json index 023182f..b342665 100644 --- a/ts/package.json +++ b/ts/package.json @@ -1,6 +1,6 @@ { "name": "@aurory/dnajs", - "version": "0.7.11", + "version": "1.0.0", "repository": { "type": "git", "url": "git+https://github.com/Aurory-Game/dna.git" From 2250993399709e194ae820dff584e50d754a935d Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:20:44 +0400 Subject: [PATCH 19/22] name updates --- ts/src/deps/schemas/aurory_dna_v4.0.0.json | 4 -- ts/src/dna_factory_v2.ts | 56 ++++++++++------------ ts/src/interfaces/types.ts | 10 ++-- 3 files changed, 29 insertions(+), 41 deletions(-) diff --git a/ts/src/deps/schemas/aurory_dna_v4.0.0.json b/ts/src/deps/schemas/aurory_dna_v4.0.0.json index b2e094b..822e2ba 100644 --- a/ts/src/deps/schemas/aurory_dna_v4.0.0.json +++ b/ts/src/deps/schemas/aurory_dna_v4.0.0.json @@ -5,10 +5,6 @@ { "name": "version", "base": 2 - }, - { - "name": "data_end", - "base": 2 } ], "archetypes": { diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index 778a5ba..4130cb0 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -7,7 +7,7 @@ import { ParseDataPerc, NeftyCodeName, ParseV2, - DnaDataData, + DnaData, ParseDataComputed, AdvStatsJSON, AdvStatsJSONValue, @@ -96,6 +96,7 @@ export class DNAFactoryV2 { private getDna(version: version, data: DnaDataV2): string { const versionDNAFormat = toPaddedHexa(version, 4); + const serializedData = this.serializeDna(data); const dna = versionDNAFormat + serializedData; return dna; @@ -111,7 +112,6 @@ export class DNAFactoryV2 { const stats = Array.from(Array(nStats).keys()).map(() => 0); const mean = randomInt(minStatAvg, maxStatAvg, true); - // adding up to 5 will still result in the same mean as we are rounding down const totalPoints = mean * nStats; const maxValuePerStat = 100; @@ -149,6 +149,7 @@ export class DNAFactoryV2 { ); // the average is done on raw stats but points are distributed on the % stats. It may happen the means are not the same. + // adding up to "number of stats of used to compute the average" will still result in the same mean as we are rounding down if (Math.floor(average) !== mean) pointsLeft += 1; } @@ -182,19 +183,8 @@ export class DNAFactoryV2 { } } - private initializeDnaData(version: string): DnaDataV2 { - const dnaData = {} as DnaDataV2; - dnaData.version = version; - return dnaData; - } - - private createDataData( - dnaSchema: DNASchemaV4, - archetypeIndex: string, - grade: Grade, - rarityPreset?: Rarity - ): DnaDataData { - const dataData = {} as DnaDataData; + private createDataData(dnaSchema: DNASchemaV4, archetypeIndex: string, grade: Grade, rarityPreset?: Rarity): DnaData { + const dataData = {} as DnaData; dataData.grade = grade; dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; @@ -204,8 +194,8 @@ export class DNAFactoryV2 { return dataData; } - private createDataDataFromV1(dataV1: Parse): DnaDataData { - const dataData = {} as DnaDataData; + private createDnaDataFromV1(dataV1: Parse): DnaData { + const dataData = {} as DnaData; dataData.grade = dataV1.data.grade; dataData.rarity = dataV1.data.rarity; dataData.neftyCodeName = dataV1.archetype.fixed_attributes.name as NeftyCodeName; @@ -254,7 +244,6 @@ export class DNAFactoryV2 { } parse(dnaString: string): ParseV2 { - // const majorVersion = toUnPaddedHexa(dnaString.slice(0, VERSION_LENGTH)); const dataRaw = this.deserializeDna(dnaString.slice(VERSION_LENGTH)); const dataAdv = Object.assign( {}, @@ -267,28 +256,30 @@ export class DNAFactoryV2 { return parsed; } - generateNeftyDNA(archetypeIndex: string, grade: Grade, version?: string, rarityPreset?: Rarity) { + generateNeftyDNA(archetypeIndex: string, grade: Grade, forcedVersion?: string, rarityPreset?: Rarity) { this.validateArchetypeIndex(archetypeIndex); - const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + const dnaSchema = this.getDNASchema(forcedVersion ?? LATEST_SCHEMA_VERSION); - const dnaData = this.initializeDnaData(dnaSchema.version); - dnaData.data = this.createDataData(dnaSchema, archetypeIndex, grade, rarityPreset); + const version = dnaSchema.version; + const data = Object.assign({}, this.createDataData(dnaSchema, archetypeIndex, grade, rarityPreset)); - const stats = this._generateStatsForRarity(N_STATS_SOT, grade, dnaData.data.rarity); - dnaData.dataAdv = this.createDataAdv(stats); + const stats = this._generateStatsForRarity(N_STATS_SOT, grade, data.rarity); + const dataAdv = this.createDataAdv(stats); + const dnaData = Object.assign({}, { version, data, dataAdv }); return this.getDna(dnaSchema.version, dnaData); } - generateStarterNeftyDNA(archetypeIndex: string, version?: string) { + generateStarterNeftyDNA(archetypeIndex: string, forcedVersion?: string) { this.validateArchetypeIndex(archetypeIndex); - const dnaSchema = this.getDNASchema(version ?? LATEST_SCHEMA_VERSION); + const dnaSchema = this.getDNASchema(forcedVersion ?? LATEST_SCHEMA_VERSION); + const version = dnaSchema.version; - const dnaData = this.initializeDnaData(dnaSchema.version); - dnaData.data = this.createDataData(dnaSchema, archetypeIndex, 'standard', 'Uncommon'); + const data = this.createDataData(dnaSchema, archetypeIndex, 'standard', 'Uncommon'); const stats = Array(N_STATS_SOT).fill(30); - dnaData.dataAdv = this.createDataAdv(stats); + const dataAdv = this.createDataAdv(stats); + const dnaData = Object.assign({}, { version, data, dataAdv }); return this.getDna(dnaSchema.version, dnaData); } @@ -301,12 +292,13 @@ export class DNAFactoryV2 { ) { const dataV1 = dnaFactoryV1.parse(v1Dna); const dnaSchema = this.getDNASchema(newVersion ?? LATEST_SCHEMA_VERSION); + const version = dnaSchema.version; - const dnaData = this.initializeDnaData(dnaSchema.version); - dnaData.data = this.createDataDataFromV1(dataV1); + const data = this.createDnaDataFromV1(dataV1); const stats = newSotStats ?? dataV1.dataAdv; - dnaData.dataAdv = this.createDataAdvFromV1(stats); + const dataAdv = this.createDataAdvFromV1(stats); + const dnaData = Object.assign({}, { version, data, dataAdv }); return this.getDna(dnaSchema.version, dnaData); } diff --git a/ts/src/interfaces/types.ts b/ts/src/interfaces/types.ts index a783e08..c474f88 100644 --- a/ts/src/interfaces/types.ts +++ b/ts/src/interfaces/types.ts @@ -202,7 +202,7 @@ export interface NeftiesInfo { } export interface EggInfo { - name: string; + name: NeftyCodeName; description: string; archetypes: NeftyCodeName[]; } @@ -228,25 +228,25 @@ export type GeneWithValues = Gene & { skill_info?: AbilityInfo; }; -export interface DnaDataData { +export interface DnaData { grade: Grade; rarity: Rarity; neftyCodeName: NeftyCodeName; } -export interface DnaDataDataParsed extends DnaDataData { +export interface DnaDataParsed extends DnaData { displayName: string; } export interface DnaDataV2 { version: string; - data: DnaDataData; + data: DnaData; dataAdv: ParseDataPerc; } export interface ParseV2 { version: string; dataRaw: DnaDataV2; - data: DnaDataDataParsed; + data: DnaDataParsed; dataAdv: ParseDataAdv; } From 63e921e7c3b53d244b872d686465b5c4ba8a6486 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:43:22 +0400 Subject: [PATCH 20/22] Update nefties indexing + small syntax changes --- ts/src/deps/schemas/aurory_dna_v4.0.0.json | 50 +++++++++++----------- ts/src/dna_factory_v2.ts | 37 +++++++++------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/ts/src/deps/schemas/aurory_dna_v4.0.0.json b/ts/src/deps/schemas/aurory_dna_v4.0.0.json index 822e2ba..871d651 100644 --- a/ts/src/deps/schemas/aurory_dna_v4.0.0.json +++ b/ts/src/deps/schemas/aurory_dna_v4.0.0.json @@ -8,31 +8,31 @@ } ], "archetypes": { - "0": "Nefty_Bitebit", - "1": "Nefty_Dipking", - "2": "Nefty_Dinobit", - "3": "Nefty_ShibaIgnite", - "4": "Nefty_Zzoo", - "5": "Nefty_Blockchoy", - "6": "Nefty_Number9", - "7": "Nefty_Axobubble", - "8": "Nefty_Unika", - "9": "Nefty_Chocomint", - "10": "Nefty_Cybertooth", - "11": "Nefty_Wassie", - "12": "Nefty_Dracurve", - "13": "Nefty_Raccoin", - "14": "Nefty_Shibark", - "15": "Nefty_Unikirin", - "16": "Nefty_Beeblock", - "17": "Nefty_Chocorex", - "18": "Nefty_Keybab", - "19": "Nefty_Bloomtail", - "20": "Nefty_Tokoma", - "21": "Nefty_Ghouliath", - "22": "Nefty_Whiskube", - "23": "Nefty_Walpuff", - "24": "Nefty_Dinotusk" + "1": "Nefty_Bitebit", + "2": "Nefty_Dipking", + "3": "Nefty_Dinobit", + "4": "Nefty_ShibaIgnite", + "5": "Nefty_Zzoo", + "6": "Nefty_Blockchoy", + "7": "Nefty_Number9", + "8": "Nefty_Axobubble", + "9": "Nefty_Unika", + "10": "Nefty_Chocomint", + "11": "Nefty_Cybertooth", + "12": "Nefty_Wassie", + "13": "Nefty_Dracurve", + "14": "Nefty_Raccoin", + "15": "Nefty_Shibark", + "16": "Nefty_Unikirin", + "17": "Nefty_Beeblock", + "18": "Nefty_Chocorex", + "19": "Nefty_Keybab", + "20": "Nefty_Bloomtail", + "21": "Nefty_Tokoma", + "22": "Nefty_Ghouliath", + "23": "Nefty_Whiskube", + "24": "Nefty_Walpuff", + "25": "Nefty_Dinotusk" }, "rarities": { "0": "Common", diff --git a/ts/src/dna_factory_v2.ts b/ts/src/dna_factory_v2.ts index 4130cb0..67a96ca 100644 --- a/ts/src/dna_factory_v2.ts +++ b/ts/src/dna_factory_v2.ts @@ -183,15 +183,16 @@ export class DNAFactoryV2 { } } - private createDataData(dnaSchema: DNASchemaV4, archetypeIndex: string, grade: Grade, rarityPreset?: Rarity): DnaData { - const dataData = {} as DnaData; - dataData.grade = grade; - dataData.rarity = rarityPreset ?? this._getRandomRarity(grade); - dataData.neftyCodeName = dnaSchema.archetypes[archetypeIndex] as NeftyCodeName; - if (!dataData.neftyCodeName) { + private createDnaData(dnaSchema: DNASchemaV4, archetypeIndex: string, grade: Grade, rarityPreset?: Rarity): DnaData { + const dnaData = { + grade, + rarity: rarityPreset ?? this._getRandomRarity(grade), + neftyCodeName: dnaSchema.archetypes[archetypeIndex] as NeftyCodeName, + }; + if (!dnaData.neftyCodeName) { throw new Error(`No archetype found for index ${archetypeIndex}`); } - return dataData; + return dnaData; } private createDnaDataFromV1(dataV1: Parse): DnaData { @@ -204,16 +205,22 @@ export class DNAFactoryV2 { private createDataAdv(stats: number[]): ParseDataPerc { const [hp, atk, def, speed] = stats; - const dataAdv = {} as ParseDataPerc; - Object.assign(dataAdv, { hp, atk, def, speed }); - return dataAdv; + return { + hp, + atk, + def, + speed, + }; } private createDataAdvFromV1(stats: ParseDataPerc): ParseDataPerc { const { hp, atk, def, speed } = stats; - const dataAdv = {} as ParseDataPerc; - Object.assign(dataAdv, { hp, atk, def, speed }); - return dataAdv; + return { + hp, + atk, + def, + speed, + }; } getArchetypeKeyByNeftyCodeName(neftyCodeName: NeftyCodeName): string { @@ -261,7 +268,7 @@ export class DNAFactoryV2 { const dnaSchema = this.getDNASchema(forcedVersion ?? LATEST_SCHEMA_VERSION); const version = dnaSchema.version; - const data = Object.assign({}, this.createDataData(dnaSchema, archetypeIndex, grade, rarityPreset)); + const data = Object.assign({}, this.createDnaData(dnaSchema, archetypeIndex, grade, rarityPreset)); const stats = this._generateStatsForRarity(N_STATS_SOT, grade, data.rarity); const dataAdv = this.createDataAdv(stats); @@ -275,7 +282,7 @@ export class DNAFactoryV2 { const dnaSchema = this.getDNASchema(forcedVersion ?? LATEST_SCHEMA_VERSION); const version = dnaSchema.version; - const data = this.createDataData(dnaSchema, archetypeIndex, 'standard', 'Uncommon'); + const data = this.createDnaData(dnaSchema, archetypeIndex, 'standard', 'Uncommon'); const stats = Array(N_STATS_SOT).fill(30); const dataAdv = this.createDataAdv(stats); From 97b53288120c85691014d8835cd999a04620e7c2 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:14:34 +0400 Subject: [PATCH 21/22] Prepare for release --- CHANGELOG.md | 12 ++++++++++++ ts/package.json | 2 +- ts/src/deps/schemas/aurory_dna_v4.0.0.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3438da..a662751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,25 +4,37 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v1.0.1] - 18/07/2024 + +- Upgrade to schema version v4.0.0 +- DNA is now a serialized object +- DNA is not unique anymore + ## [v0.7.9] - 24/04/2024 + - Add new eggs and nefties for the Citrine version of Seekers of Tokane ## [v0.7.3] - 16/01/2024 + - Wassie added to Quantum eggs ## [v0.7.0] - 06/12/2023 + - Introduce Cybertooth and Wassie - Add Fen and Moss eggs - Update configuration format for adventure stats ## [v0.6.0] - 18/09/2023 + - Introduce grade for DNA generation - Add standard egg support in EggFactory ## [v0.5.4] - 19/09/2023 + - Sync dictionary with ability names and descriptions from unity team ## [v0.5.1] - 10/08/2023 + - Adjust Chocomint stats ## [v0.5.0] - 03/08/2023 diff --git a/ts/package.json b/ts/package.json index b342665..4935f02 100644 --- a/ts/package.json +++ b/ts/package.json @@ -1,6 +1,6 @@ { "name": "@aurory/dnajs", - "version": "1.0.0", + "version": "1.0.1", "repository": { "type": "git", "url": "git+https://github.com/Aurory-Game/dna.git" diff --git a/ts/src/deps/schemas/aurory_dna_v4.0.0.json b/ts/src/deps/schemas/aurory_dna_v4.0.0.json index 871d651..4d624ce 100644 --- a/ts/src/deps/schemas/aurory_dna_v4.0.0.json +++ b/ts/src/deps/schemas/aurory_dna_v4.0.0.json @@ -1,6 +1,6 @@ { "version": "4.0.0", - "version_date": "05/07/2024", + "version_date": "2024-07-18", "global_genes_header": [ { "name": "version", From 812c57f027a84ef2654fcd4d353c7e2915d9f192 Mon Sep 17 00:00:00 2001 From: Levani Tevdoradze <12980455+tevdoradze@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:33:44 +0400 Subject: [PATCH 22/22] Update schema date --- ts/src/deps/schemas/aurory_dna_v4.0.0.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/src/deps/schemas/aurory_dna_v4.0.0.json b/ts/src/deps/schemas/aurory_dna_v4.0.0.json index 4d624ce..f28825d 100644 --- a/ts/src/deps/schemas/aurory_dna_v4.0.0.json +++ b/ts/src/deps/schemas/aurory_dna_v4.0.0.json @@ -1,6 +1,6 @@ { "version": "4.0.0", - "version_date": "2024-07-18", + "version_date": "18/07/2024", "global_genes_header": [ { "name": "version",