gotchi-battler-game-logic 2.0.7 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.vscode/settings.json +4 -4
  2. package/Dockerfile +9 -9
  3. package/README.md +49 -49
  4. package/cloudbuild.yaml +27 -27
  5. package/constants/tournamentManagerAbi.json +208 -208
  6. package/game-logic/index.js +7 -6
  7. package/game-logic/v1.4/constants.js +114 -114
  8. package/game-logic/v1.4/index.js +1366 -1366
  9. package/game-logic/v1.6/constants.js +123 -123
  10. package/game-logic/v1.6/index.js +1406 -1406
  11. package/game-logic/v1.7/constants.js +142 -140
  12. package/game-logic/v1.7/helpers.js +610 -593
  13. package/game-logic/v1.7/index.js +802 -795
  14. package/game-logic/v1.8/constants.js +135 -0
  15. package/game-logic/v1.8/helpers.js +628 -0
  16. package/game-logic/v1.8/index.js +826 -0
  17. package/index.js +12 -12
  18. package/package.json +26 -26
  19. package/schemas/team.json +349 -343
  20. package/scripts/balancing/createCSV.js +126 -126
  21. package/scripts/balancing/createTrainingGotchis.js +267 -0
  22. package/scripts/balancing/extractOnchainTraits.js +61 -0
  23. package/scripts/balancing/fixTrainingGotchis.js +155 -259
  24. package/scripts/balancing/processSims.js +229 -229
  25. package/scripts/balancing/sims.js +278 -278
  26. package/scripts/balancing/v1.7/class_combos.js +43 -43
  27. package/scripts/balancing/v1.7/mapGotchi.js +119 -0
  28. package/scripts/balancing/v1.7/setTeamPositions.js +105 -105
  29. package/scripts/balancing/v1.7/training_gotchis.json +20161 -20161
  30. package/scripts/balancing/v1.7/training_gotchis_traits.json +520 -0
  31. package/scripts/balancing/v1.7/trait_combos.json +9 -9
  32. package/scripts/balancing/v1.7.1/class_combos.js +43 -43
  33. package/scripts/balancing/v1.7.1/mapGotchi.js +119 -0
  34. package/scripts/balancing/v1.7.1/setTeamPositions.js +122 -122
  35. package/scripts/balancing/v1.7.1/training_gotchis.json +22401 -22401
  36. package/scripts/balancing/v1.7.1/training_gotchis_traits.json +520 -0
  37. package/scripts/balancing/v1.7.1/trait_combos.json +9 -9
  38. package/scripts/balancing/v1.7.2/class_combos.js +44 -0
  39. package/scripts/balancing/v1.7.2/mapGotchi.js +157 -0
  40. package/scripts/balancing/v1.7.2/setTeamPositions.js +122 -0
  41. package/scripts/balancing/v1.7.2/training_gotchis.json +22402 -0
  42. package/scripts/balancing/v1.7.2/training_gotchis_traits.json +520 -0
  43. package/scripts/balancing/v1.7.2/trait_combos.json +10 -0
  44. package/scripts/balancing/v1.7.3/class_combos.js +44 -0
  45. package/scripts/balancing/v1.7.3/mapGotchi.js +164 -0
  46. package/scripts/balancing/v1.7.3/setTeamPositions.js +122 -0
  47. package/scripts/balancing/v1.7.3/training_gotchis.json +22402 -0
  48. package/scripts/balancing/v1.7.3/training_gotchis_traits.json +37 -0
  49. package/scripts/balancing/v1.7.3/trait_combos.json +10 -0
  50. package/scripts/data/team1.json +213 -213
  51. package/scripts/data/team2.json +200 -200
  52. package/scripts/data/tournaments.json +71 -66
  53. package/scripts/{runBattle.js → runLocalBattle.js} +18 -18
  54. package/scripts/runRealBattle.js +52 -0
  55. package/scripts/simRealBattle.js +121 -0
  56. package/scripts/validateBattle.js +83 -70
  57. package/scripts/validateTournament.js +101 -101
  58. package/utils/contracts.js +12 -12
  59. package/utils/errors.js +29 -29
  60. package/utils/mapGotchi.js +119 -0
  61. package/utils/transforms.js +89 -88
  62. package/utils/validations.js +39 -39
  63. package/debug.log +0 -2
  64. package/game-logic/v1.6/debug.log +0 -1
  65. package/game-logic/v1.7/debug.log +0 -3
  66. package/scripts/data/debug.log +0 -2
  67. package/scripts/debug.log +0 -1
@@ -1,126 +1,126 @@
1
- require('dotenv').config()
2
- const { writeFile } = require('node:fs/promises')
3
- const path = require('node:path')
4
-
5
- const { Storage } = require('@google-cloud/storage')
6
- const storage = new Storage()
7
- // Use key file locally
8
- process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(__dirname, '../../keyfile.json')
9
-
10
- const jsoncsv = require('json-csv')
11
-
12
- // Generate csv files for a Google Cloud run job
13
- const main = async () => {
14
- if (!process.argv[2]) {
15
- throw new Error('Execution ID required')
16
- }
17
- const executionId = process.argv[2]
18
- // const numOfTasks = 2640
19
- const numOfTasks = 5280
20
-
21
- const results = []
22
-
23
- // Download all the files in batches of 100
24
- for (let i = 0; i < numOfTasks; i += 100) {
25
- const batch = []
26
-
27
- for (let j = 0; j < 100; j++) {
28
- if (i + j < numOfTasks) {
29
- batch.push(storage.bucket(process.env.SIMS_BUCKET).file(`${executionId}_${i + j}.json`).download())
30
- }
31
- }
32
-
33
- const batchResults = await Promise.all(batch)
34
- results.push(...batchResults)
35
-
36
- console.log(`Downloaded ${i + batchResults.length} files`)
37
- }
38
-
39
- const combinedResults = []
40
-
41
- for (const result of results) {
42
- combinedResults.push(JSON.parse(result.toString()))
43
- }
44
-
45
- const fields = [
46
- {
47
- name: 'id',
48
- label: 'id'
49
- },
50
- {
51
- name: 'slot1',
52
- label: 'slot1'
53
- },
54
- {
55
- name: 'slot2',
56
- label: 'slot2'
57
- },
58
- {
59
- name: 'slot3',
60
- label: 'slot3'
61
- },
62
- {
63
- name: 'slot4',
64
- label: 'slot4'
65
- },
66
- {
67
- name: 'slot5',
68
- label: 'slot5'
69
- },
70
- {
71
- name: 'wins',
72
- label: 'wins'
73
- },
74
- {
75
- name: 'draws',
76
- label: 'draws'
77
- },
78
- {
79
- name: 'losses',
80
- label: 'losses'
81
- }
82
- ]
83
-
84
- // Check which powerLevels are present
85
- const powerLevels = ['Godlike', 'Mythical', 'Legendary', 'Rare', 'Uncommon', 'Common', 'Garbage']
86
- for (const powerLevel of powerLevels) {
87
- if (combinedResults[0][`wins${powerLevel}`]) {
88
- fields.push({
89
- name: `wins${powerLevel}`,
90
- label: `wins${powerLevel}`
91
- })
92
- fields.push({
93
- name: `draws${powerLevel}`,
94
- label: `draws${powerLevel}`
95
- })
96
- fields.push({
97
- name: `losses${powerLevel}`,
98
- label: `losses${powerLevel}`
99
- })
100
- }
101
- }
102
-
103
- const csv = await jsoncsv.buffered(combinedResults, {
104
- fields
105
- })
106
-
107
- await writeFile(path.join(__dirname, `/output/${executionId}.csv`), csv)
108
- console.log(`Wrote ${executionId}.csv`)
109
- }
110
-
111
- module.exports = main
112
-
113
- if (require.main === module) {
114
- // node scripts/balancing/createCSV.js balancing-sims-fgpsl
115
-
116
- main()
117
- .then(() => {
118
- console.log('Done')
119
- process.exit(0)
120
- })
121
- .catch((err) => {
122
- console.error(err)
123
- process.exit(1)
124
- })
125
- }
126
-
1
+ require('dotenv').config()
2
+ const { writeFile } = require('node:fs/promises')
3
+ const path = require('node:path')
4
+
5
+ const { Storage } = require('@google-cloud/storage')
6
+ const storage = new Storage()
7
+ // Use key file locally
8
+ process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(__dirname, '../../keyfile.json')
9
+
10
+ const jsoncsv = require('json-csv')
11
+
12
+ // Generate csv files for a Google Cloud run job
13
+ const main = async () => {
14
+ if (!process.argv[2]) {
15
+ throw new Error('Execution ID required')
16
+ }
17
+ const executionId = process.argv[2]
18
+ // const numOfTasks = 2640
19
+ const numOfTasks = 5280
20
+
21
+ const results = []
22
+
23
+ // Download all the files in batches of 100
24
+ for (let i = 0; i < numOfTasks; i += 100) {
25
+ const batch = []
26
+
27
+ for (let j = 0; j < 100; j++) {
28
+ if (i + j < numOfTasks) {
29
+ batch.push(storage.bucket(process.env.SIMS_BUCKET).file(`${executionId}_${i + j}.json`).download())
30
+ }
31
+ }
32
+
33
+ const batchResults = await Promise.all(batch)
34
+ results.push(...batchResults)
35
+
36
+ console.log(`Downloaded ${i + batchResults.length} files`)
37
+ }
38
+
39
+ const combinedResults = []
40
+
41
+ for (const result of results) {
42
+ combinedResults.push(JSON.parse(result.toString()))
43
+ }
44
+
45
+ const fields = [
46
+ {
47
+ name: 'id',
48
+ label: 'id'
49
+ },
50
+ {
51
+ name: 'slot1',
52
+ label: 'slot1'
53
+ },
54
+ {
55
+ name: 'slot2',
56
+ label: 'slot2'
57
+ },
58
+ {
59
+ name: 'slot3',
60
+ label: 'slot3'
61
+ },
62
+ {
63
+ name: 'slot4',
64
+ label: 'slot4'
65
+ },
66
+ {
67
+ name: 'slot5',
68
+ label: 'slot5'
69
+ },
70
+ {
71
+ name: 'wins',
72
+ label: 'wins'
73
+ },
74
+ {
75
+ name: 'draws',
76
+ label: 'draws'
77
+ },
78
+ {
79
+ name: 'losses',
80
+ label: 'losses'
81
+ }
82
+ ]
83
+
84
+ // Check which powerLevels are present
85
+ const powerLevels = ['Godlike', 'Mythical', 'Legendary', 'Rare', 'Uncommon', 'Common', 'Garbage']
86
+ for (const powerLevel of powerLevels) {
87
+ if (combinedResults[0][`wins${powerLevel}`]) {
88
+ fields.push({
89
+ name: `wins${powerLevel}`,
90
+ label: `wins${powerLevel}`
91
+ })
92
+ fields.push({
93
+ name: `draws${powerLevel}`,
94
+ label: `draws${powerLevel}`
95
+ })
96
+ fields.push({
97
+ name: `losses${powerLevel}`,
98
+ label: `losses${powerLevel}`
99
+ })
100
+ }
101
+ }
102
+
103
+ const csv = await jsoncsv.buffered(combinedResults, {
104
+ fields
105
+ })
106
+
107
+ await writeFile(path.join(__dirname, `/output/${executionId}.csv`), csv)
108
+ console.log(`Wrote ${executionId}.csv`)
109
+ }
110
+
111
+ module.exports = main
112
+
113
+ if (require.main === module) {
114
+ // node scripts/balancing/createCSV.js avg-sims-8jzd7
115
+
116
+ main()
117
+ .then(() => {
118
+ console.log('Done')
119
+ process.exit(0)
120
+ })
121
+ .catch((err) => {
122
+ console.error(err)
123
+ process.exit(1)
124
+ })
125
+ }
126
+
@@ -0,0 +1,267 @@
1
+ const fs = require('fs')
2
+
3
+ const specials = [
4
+ {
5
+ "id": 1,
6
+ "class": "Ninja",
7
+ "name": "Spectral strike",
8
+ "cooldown": 0,
9
+ "leaderPassive": "Sharpen blades"
10
+ },
11
+ {
12
+ "id": 2,
13
+ "class": "Enlightened",
14
+ "name": "Meditate",
15
+ "cooldown": 0,
16
+ "leaderPassive": "Cloud of Zen"
17
+ },
18
+ {
19
+ "id": 3,
20
+ "class": "Cleaver",
21
+ "name": "Cleave",
22
+ "cooldown": 2,
23
+ "leaderPassive": "Frenzy"
24
+ },
25
+ {
26
+ "id": 4,
27
+ "class": "Tank",
28
+ "name": "Taunt",
29
+ "cooldown": 0,
30
+ "leaderPassive": "Fortify"
31
+ },
32
+ {
33
+ "id": 5,
34
+ "class": "Cursed",
35
+ "name": "Curse",
36
+ "cooldown": 0,
37
+ "leaderPassive": "Spread the fear"
38
+ },
39
+ {
40
+ "id": 6,
41
+ "class": "Healer",
42
+ "name": "Blessing",
43
+ "cooldown": 0,
44
+ "leaderPassive": "Cleansing Aura"
45
+ },
46
+ {
47
+ "id": 7,
48
+ "class": "Mage",
49
+ "name": "Thunder",
50
+ "cooldown": 2,
51
+ "leaderPassive": "Channel the coven"
52
+ },
53
+ {
54
+ "id": 8,
55
+ "class": "Troll",
56
+ "name": "Devestating Smash",
57
+ "cooldown": 2,
58
+ "leaderPassive": "Clan momentum"
59
+ }
60
+ ]
61
+
62
+
63
+ const powerLevels = ['Godlike', 'Mythical', 'Legendary', 'Rare', 'Uncommon', 'Common', 'Garbage']
64
+ const traitCombos = [
65
+ '++++', '+++-', '++-+', '++--', '+-++', '+-+-', '+--+', '+---',
66
+ '-+++', '-++-', '-+-+', '-+--', '--++', '--+-', '---+', '----'
67
+ ]
68
+ const classes = ['Ninja','Enlightened','Cleaver','Tank','Cursed','Healer', 'Mage', 'Troll']
69
+
70
+
71
+ // Create training gotchi objects like below for all combinations of powerLevels, traitCombos and classes
72
+ // {
73
+ // "id": 1,
74
+ // "snapshotBlock": 0,
75
+ // "onchainId": 1000001,
76
+ // "name": "Godlike ++++ Ninja",
77
+ // "brs": 783,
78
+ // "nrg": 110,
79
+ // "agg": 98,
80
+ // "spk": 101,
81
+ // "brn": 107,
82
+ // "eyc": 94,
83
+ // "eys": 94,
84
+ // "kinship": 0,
85
+ // "xp": 0,
86
+ // "speed": 161,
87
+ // "health": 587,
88
+ // "crit": 25,
89
+ // "armor": 0,
90
+ // "evade": 16,
91
+ // "resist": 0,
92
+ // "magic": 486,
93
+ // "physical": 196,
94
+ // "accuracy": 95,
95
+ // "attack": "magic",
96
+ // "actionDelay": 0.617,
97
+ // "specialId": 1,
98
+ // "svgFront": "https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/ninja_godlike_front.png",
99
+ // "svgBack": "https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/ninja_godlike_back.png",
100
+ // "svgLeft": "https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/ninja_godlike_left.png",
101
+ // "svgRight": "https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/ninja_godlike_right.png",
102
+ // "createdAt": "2023-09-09 12:10:01",
103
+ // "updatedAt": "2024-06-05 16:34:00",
104
+ // "special": {
105
+ // "id": 1,
106
+ // "class": "Ninja",
107
+ // "name": "Spectral strike",
108
+ // "cooldown": 0,
109
+ // "leaderPassive": "Sharpen blades"
110
+ // }
111
+ // }
112
+
113
+ const main = (version) => {
114
+ const trainingGotchisTraits = require(`./v${version}/training_gotchis_traits.json`)
115
+ const { mapGotchi } = require(`./v${version}/mapGotchi.js`)
116
+
117
+ const trainingGotchis = []
118
+
119
+ let id = 1
120
+ traitCombos.forEach((t) => {
121
+ powerLevels.forEach((p) => {
122
+ for (let index = 0; index < t.length; index ++) {
123
+ const c = t[index]
124
+
125
+ const gotchi = {
126
+ id,
127
+ snapshotBlock: 0,
128
+ onchainId: 1000000 + id,
129
+ }
130
+
131
+ // Based on the position of the trait in the traitCombos array, we can determine the class of the gotchi
132
+ // e.g. if the first trait is positive then add ninja, if it's negative then add enlightened
133
+ const className = classes[index * 2 + (c === '+' ? 0 : 1)]
134
+
135
+ // e.g Godlike ++++ Mage
136
+ gotchi.name = `${p} ${t} ${className}`
137
+
138
+ /**
139
+ * This is for versions <= 1.7.2
140
+ */
141
+ // const trainingGotchiTraits = trainingGotchisTraits[p.toLowerCase()][className.toLowerCase()]
142
+
143
+ // if (!trainingGotchiTraits) {
144
+ // throw new Error(`No traits found for ${p} ${className}`)
145
+ // }
146
+
147
+ // gotchi.brs = trainingGotchiTraits.brs
148
+ // gotchi.nrg = trainingGotchiTraits.nrg[t[0] === '-' ? 0 : 1]
149
+ // gotchi.agg = trainingGotchiTraits.agg[t[1] === '-' ? 0 : 1]
150
+ // gotchi.spk = trainingGotchiTraits.spk[t[2] === '-' ? 0 : 1]
151
+ // gotchi.brn = trainingGotchiTraits.brn[t[3] === '-' ? 0 : 1]
152
+ // gotchi.eys = trainingGotchiTraits.eys[1] // These are always in index 1
153
+ // gotchi.eyc = trainingGotchiTraits.eyc[1] // These are always in index 1
154
+
155
+ /**
156
+ * This is for versions >= 1.7.3
157
+ */
158
+ const trainingGotchiTraits = trainingGotchisTraits[p.toLowerCase()]
159
+
160
+ if (!trainingGotchiTraits) {
161
+ throw new Error(`No traits found for ${p}`)
162
+ }
163
+
164
+ gotchi.brs = trainingGotchiTraits.brs
165
+ gotchi.nrg = trainingGotchiTraits.traits[t[0] === '-' ? 0 : 1]
166
+ gotchi.agg = trainingGotchiTraits.traits[t[1] === '-' ? 0 : 1]
167
+ gotchi.spk = trainingGotchiTraits.traits[t[2] === '-' ? 0 : 1]
168
+ gotchi.brn = trainingGotchiTraits.traits[t[3] === '-' ? 0 : 1]
169
+ gotchi.eys = trainingGotchiTraits.eyes
170
+ gotchi.eyc = trainingGotchiTraits.eyes
171
+
172
+ gotchi.kinship = 0
173
+ gotchi.xp = 0
174
+
175
+ // Map the in game stats to the gotchi
176
+ mapGotchi(gotchi)
177
+ gotchi.attack = t[3] === '-' ? 'physical' : 'magic'
178
+ gotchi.actionDelay = Math.round(((100 / gotchi.speed) + Number.EPSILON) * 1000) / 1000
179
+
180
+ gotchi.specialId = classes.indexOf(className) + 1
181
+ gotchi.svgFront = `https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/${className.toLowerCase()}_${p.toLowerCase()}_front.png`
182
+ gotchi.svgBack = `https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/${className.toLowerCase()}_${p.toLowerCase()}_back.png`
183
+ gotchi.svgLeft = `https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/${className.toLowerCase()}_${p.toLowerCase()}_left.png`
184
+ gotchi.svgRight = `https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/${className.toLowerCase()}_${p.toLowerCase()}_right.png`
185
+ gotchi.createdAt = "2023-09-09 12:10:01"
186
+ gotchi.updatedAt = "2024-06-05 16:34:00"
187
+ gotchi.special = specials.find(s => s.id === gotchi.specialId)
188
+
189
+ trainingGotchis.push(gotchi)
190
+
191
+ id++
192
+ }
193
+ })
194
+ })
195
+
196
+ const avgGotchis = createAvgGotchs(trainingGotchis)
197
+
198
+ trainingGotchis.push(...avgGotchis)
199
+
200
+ fs.writeFileSync(`./training_gotchis.json`, JSON.stringify(trainingGotchis, null, 2))
201
+ }
202
+
203
+ const createAvgGotchs = (trainingGotchis) => {
204
+ const statsToOverwrite = ["speed", "health", "crit", "armor", "evade", "resist", "accuracy"]
205
+
206
+ const avgGotchis = [];
207
+
208
+ // Add magic gotchis
209
+ ['magic', 'physical'].forEach((brainValue) => {
210
+ powerLevels.forEach((powerLevel) => {
211
+ classes.forEach((className) => {
212
+ // Find all gotchis with the same class and power level
213
+ // Names are in the format "Godlike ++++ Ninja"
214
+ const gotchis = trainingGotchis.filter(gotchi => gotchi.name.includes(powerLevel) && gotchi.name.includes(className))
215
+
216
+ // Copy over one of the gotchis
217
+ const avgGotchi = JSON.parse(JSON.stringify(gotchis[0]))
218
+
219
+ // Add an id
220
+ avgGotchi.id = trainingGotchis.length + avgGotchis.length + 1
221
+
222
+ // Overwrite the name
223
+ avgGotchi.name = `${powerLevel} avg-${brainValue} ${className}`
224
+
225
+ // Overwrite the stats
226
+ statsToOverwrite.forEach((stat) => {
227
+ const total = gotchis.reduce((acc, gotchi) => acc + gotchi[stat], 0)
228
+
229
+ if (['speed', 'health', 'armor', 'resist', 'magic', 'physical', 'accuracy'].includes(stat)) {
230
+ avgGotchi[stat] = Math.round(total / gotchis.length)
231
+ } else {
232
+ // Round to 2 decimal places
233
+ avgGotchi[stat] = Math.round((total / gotchis.length) * 100) / 100
234
+ }
235
+ })
236
+
237
+ // Get the highest/lowest magic value
238
+ const magicValues = gotchis.map(gotchi => gotchi.magic)
239
+ const highestMagic = Math.max(...magicValues)
240
+ const lowestMagic = Math.min(...magicValues)
241
+
242
+ // Get the highest/lowest physical value
243
+ const physicalValues = gotchis.map(gotchi => gotchi.physical)
244
+ const highestPhysical = Math.max(...physicalValues)
245
+ const lowestPhysical = Math.min(...physicalValues)
246
+
247
+ // If this is a magic gotchi then set the magic value to the highest
248
+ if (brainValue === 'magic') {
249
+ avgGotchi.magic = highestMagic
250
+ avgGotchi.physical = lowestPhysical
251
+ avgGotchi.attack = 'magic'
252
+ } else {
253
+ avgGotchi.magic = lowestMagic
254
+ avgGotchi.physical = highestPhysical
255
+ avgGotchi.attack = 'physical'
256
+ }
257
+
258
+ avgGotchis.push(avgGotchi)
259
+ })
260
+ })
261
+ })
262
+
263
+ return avgGotchis
264
+ }
265
+
266
+ // node scripts/balancing/createTrainingGotchis.js
267
+ main('1.7.3')
@@ -0,0 +1,61 @@
1
+ const fs = require('fs')
2
+ const trainingGotchis = require('./v1.7/training_gotchis.json')
3
+
4
+ const onchainTraitNames = [
5
+ 'nrg',
6
+ 'agg',
7
+ 'spk',
8
+ 'brn',
9
+ 'eys',
10
+ 'eyc'
11
+ ]
12
+
13
+ const main = () => {
14
+ const onchainTraits = {}
15
+
16
+ // Loop through all training gotchis ids 1-448 and extract the onchain traits
17
+ for (let i = 1; i < 449; i++) {
18
+ const gotchi = trainingGotchis[i]
19
+
20
+ const powerLevel = gotchi.name.split(' ')[0].toLowerCase()
21
+ const gotchiClass = gotchi.name.split(' ')[2].toLowerCase()
22
+
23
+ if (!onchainTraits[powerLevel]) {
24
+ onchainTraits[powerLevel] = {}
25
+ }
26
+
27
+ if (!onchainTraits[powerLevel][gotchiClass]) {
28
+ onchainTraits[powerLevel][gotchiClass] = {}
29
+ }
30
+
31
+ if (!onchainTraits[powerLevel][gotchiClass].brs) {
32
+ onchainTraits[powerLevel][gotchiClass].brs = gotchi.brs
33
+ }
34
+
35
+ onchainTraitNames.forEach((trait) => {
36
+ if (!onchainTraits[powerLevel][gotchiClass][trait]) {
37
+ onchainTraits[powerLevel][gotchiClass][trait] = []
38
+ }
39
+
40
+ if (gotchi[trait] < 50) {
41
+ if (!onchainTraits[powerLevel][gotchiClass][trait][0]) {
42
+ onchainTraits[powerLevel][gotchiClass][trait][0] = gotchi[trait]
43
+ }
44
+ } else {
45
+ if (!onchainTraits[powerLevel][gotchiClass][trait][1]) {
46
+ onchainTraits[powerLevel][gotchiClass][trait][1] = gotchi[trait]
47
+ }
48
+ }
49
+ })
50
+ }
51
+
52
+ fs.writeFileSync('./training_gotchis_traits.json', JSON.stringify(onchainTraits, (key, value) => {
53
+ if (Array.isArray(value)) {
54
+ return JSON.stringify(value); // Convert array to a string
55
+ }
56
+ return value;
57
+ }, 2).replace(/"\[(.*?)\]"/g, "[$1]"))
58
+ }
59
+
60
+ // node scripts/balancing/extractOnchainTraits.js
61
+ main()