gotchi-battler-game-logic 2.0.8 → 4.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 (74) hide show
  1. package/.cursor/rules/cursor-rules.mdc +67 -0
  2. package/.cursor/rules/directory-structure.mdc +63 -0
  3. package/.cursor/rules/self-improvement.mdc +64 -0
  4. package/.cursor/rules/tech-stack.mdc +99 -0
  5. package/README.md +7 -3
  6. package/eslint.config.js +31 -0
  7. package/game-logic/index.js +2 -5
  8. package/game-logic/v1.4/constants.js +0 -23
  9. package/game-logic/v1.4/index.js +64 -56
  10. package/game-logic/v1.5/constants.js +0 -23
  11. package/game-logic/v1.5/index.js +27 -21
  12. package/game-logic/v1.6/constants.js +0 -23
  13. package/game-logic/v1.6/index.js +27 -21
  14. package/game-logic/v1.7/constants.js +0 -23
  15. package/game-logic/v1.7/helpers.js +18 -3
  16. package/game-logic/v1.7/index.js +24 -18
  17. package/game-logic/v1.8/constants.js +112 -0
  18. package/game-logic/v1.8/helpers.js +628 -0
  19. package/game-logic/v1.8/index.js +832 -0
  20. package/game-logic/v2.0/constants.js +112 -0
  21. package/game-logic/v2.0/helpers.js +713 -0
  22. package/game-logic/v2.0/index.js +782 -0
  23. package/game-logic/v2.0/statuses.json +439 -0
  24. package/package.json +11 -4
  25. package/schemas/crystal.js +14 -0
  26. package/schemas/effect.js +25 -0
  27. package/schemas/gotchi.js +53 -0
  28. package/schemas/ingameteam.js +14 -0
  29. package/schemas/item.js +13 -0
  30. package/schemas/leaderskill.js +15 -0
  31. package/schemas/leaderskillstatus.js +12 -0
  32. package/schemas/special.js +22 -0
  33. package/schemas/team.js +24 -0
  34. package/schemas/team.json +254 -116
  35. package/scripts/balancing/createCSV.js +1 -1
  36. package/scripts/balancing/createTrainingGotchis.js +267 -0
  37. package/scripts/balancing/extractOnchainTraits.js +61 -0
  38. package/scripts/balancing/fixTrainingGotchis.js +41 -41
  39. package/scripts/balancing/processSims.js +6 -6
  40. package/scripts/balancing/sims.js +10 -17
  41. package/scripts/balancing/v1.7/mapGotchi.js +119 -0
  42. package/scripts/balancing/v1.7/setTeamPositions.js +2 -2
  43. package/scripts/balancing/v1.7/training_gotchis_traits.json +520 -0
  44. package/scripts/balancing/v1.7.1/mapGotchi.js +119 -0
  45. package/scripts/balancing/v1.7.1/setTeamPositions.js +2 -2
  46. package/scripts/balancing/v1.7.1/training_gotchis_traits.json +520 -0
  47. package/scripts/balancing/v1.7.2/mapGotchi.js +157 -0
  48. package/scripts/balancing/v1.7.2/setTeamPositions.js +2 -2
  49. package/scripts/balancing/v1.7.2/training_gotchis_traits.json +520 -0
  50. package/scripts/balancing/v1.7.3/class_combos.js +44 -0
  51. package/scripts/balancing/v1.7.3/mapGotchi.js +164 -0
  52. package/scripts/balancing/v1.7.3/setTeamPositions.js +122 -0
  53. package/scripts/balancing/v1.7.3/training_gotchis.json +22402 -0
  54. package/scripts/balancing/v1.7.3/training_gotchis_traits.json +37 -0
  55. package/scripts/balancing/v1.7.3/trait_combos.json +10 -0
  56. package/scripts/data/dungeon_mob_1.json +87 -0
  57. package/scripts/data/dungeon_mob_2.json +87 -0
  58. package/scripts/data/immaterialTeam1.json +374 -0
  59. package/scripts/data/immaterialTeam2.json +365 -0
  60. package/scripts/data/tournaments.json +5 -0
  61. package/scripts/generateAllSpecialsLogs.js +93 -0
  62. package/scripts/generateSpecialLogs.js +94 -0
  63. package/scripts/runCampaignBattles.js +41 -0
  64. package/scripts/runLocalBattle.js +6 -3
  65. package/scripts/runLocalDungeon.js +52 -0
  66. package/scripts/runPvPBattle.js +16 -0
  67. package/scripts/runRealBattle.js +8 -8
  68. package/scripts/simRealBattle.js +8 -8
  69. package/scripts/validateBattle.js +23 -16
  70. package/scripts/validateTournament.js +9 -9
  71. package/tests/getModifiedStats.test.js +78 -0
  72. package/utils/errors.js +13 -13
  73. package/utils/transforms.js +2 -8
  74. package/scripts/output/.gitkeep +0 -0
@@ -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()
@@ -4,60 +4,60 @@ const { mapGotchi } = require('../../utils/mapGotchi')
4
4
 
5
5
  const specials = [
6
6
  {
7
- "id": 1,
8
- "class": "Ninja",
9
- "name": "Spectral strike",
10
- "cooldown": 0,
11
- "leaderPassive": "Sharpen blades"
7
+ 'id': 1,
8
+ 'class': 'Ninja',
9
+ 'name': 'Spectral strike',
10
+ 'cooldown': 0,
11
+ 'leaderPassive': 'Sharpen blades'
12
12
  },
13
13
  {
14
- "id": 2,
15
- "class": "Enlightened",
16
- "name": "Meditate",
17
- "cooldown": 0,
18
- "leaderPassive": "Cloud of Zen"
14
+ 'id': 2,
15
+ 'class': 'Enlightened',
16
+ 'name': 'Meditate',
17
+ 'cooldown': 0,
18
+ 'leaderPassive': 'Cloud of Zen'
19
19
  },
20
20
  {
21
- "id": 3,
22
- "class": "Cleaver",
23
- "name": "Cleave",
24
- "cooldown": 2,
25
- "leaderPassive": "Frenzy"
21
+ 'id': 3,
22
+ 'class': 'Cleaver',
23
+ 'name': 'Cleave',
24
+ 'cooldown': 2,
25
+ 'leaderPassive': 'Frenzy'
26
26
  },
27
27
  {
28
- "id": 4,
29
- "class": "Tank",
30
- "name": "Taunt",
31
- "cooldown": 0,
32
- "leaderPassive": "Fortify"
28
+ 'id': 4,
29
+ 'class': 'Tank',
30
+ 'name': 'Taunt',
31
+ 'cooldown': 0,
32
+ 'leaderPassive': 'Fortify'
33
33
  },
34
34
  {
35
- "id": 5,
36
- "class": "Cursed",
37
- "name": "Curse",
38
- "cooldown": 0,
39
- "leaderPassive": "Spread the fear"
35
+ 'id': 5,
36
+ 'class': 'Cursed',
37
+ 'name': 'Curse',
38
+ 'cooldown': 0,
39
+ 'leaderPassive': 'Spread the fear'
40
40
  },
41
41
  {
42
- "id": 6,
43
- "class": "Healer",
44
- "name": "Blessing",
45
- "cooldown": 0,
46
- "leaderPassive": "Cleansing Aura"
42
+ 'id': 6,
43
+ 'class': 'Healer',
44
+ 'name': 'Blessing',
45
+ 'cooldown': 0,
46
+ 'leaderPassive': 'Cleansing Aura'
47
47
  },
48
48
  {
49
- "id": 7,
50
- "class": "Mage",
51
- "name": "Thunder",
52
- "cooldown": 2,
53
- "leaderPassive": "Channel the coven"
49
+ 'id': 7,
50
+ 'class': 'Mage',
51
+ 'name': 'Thunder',
52
+ 'cooldown': 2,
53
+ 'leaderPassive': 'Channel the coven'
54
54
  },
55
55
  {
56
- "id": 8,
57
- "class": "Troll",
58
- "name": "Devestating Smash",
59
- "cooldown": 2,
60
- "leaderPassive": "Clan momentum"
56
+ 'id': 8,
57
+ 'class': 'Troll',
58
+ 'name': 'Devestating Smash',
59
+ 'cooldown': 2,
60
+ 'leaderPassive': 'Clan momentum'
61
61
  }
62
62
  ]
63
63
 
@@ -65,7 +65,7 @@ const classes = ['Ninja','Enlightened','Cleaver','Tank','Cursed','Healer', 'Mage
65
65
  const powerLevels = ['Godlike', 'Mythical', 'Legendary', 'Rare', 'Uncommon', 'Common', 'Garbage']
66
66
 
67
67
  const addAvgGotchs = () => {
68
- const statsToOverwrite = ["speed", "health", "crit", "armor", "evade", "resist", "accuracy"]
68
+ const statsToOverwrite = ['speed', 'health', 'crit', 'armor', 'evade', 'resist', 'accuracy']
69
69
 
70
70
  const avgGotchis = [];
71
71
 
@@ -41,16 +41,16 @@ const classes = ['Ninja','Enlightened','Cleaver','Tank','Cursed','Healer', 'Mage
41
41
 
42
42
  const calculateKpis = (data) => {
43
43
  // Extract wins values
44
- const wins = data.map(item => item.wins);
44
+ const wins = data.map(item => item.wins)
45
45
 
46
46
  // Calculate the mean of wins
47
- const winsMean = wins.reduce((sum, value) => sum + value, 0) / wins.length;
47
+ const winsMean = wins.reduce((sum, value) => sum + value, 0) / wins.length
48
48
 
49
49
  // Calculate the wins per slot
50
50
  const winsPerSlot = winsMean / 5
51
51
 
52
52
  // Calculate the standard deviation of wins
53
- const winsVariance = wins.reduce((sum, value) => sum + Math.pow(value - winsMean, 2), 0) / (wins.length - 1);
53
+ const winsVariance = wins.reduce((sum, value) => sum + Math.pow(value - winsMean, 2), 0) / (wins.length - 1)
54
54
  const winsStdev = Math.sqrt(winsVariance)
55
55
 
56
56
  // Calculate Interquartile Range (IQR)
@@ -91,8 +91,8 @@ const calculateKpis = (data) => {
91
91
  // G is power level, avg is trait combo, 1 is class number, B is formation position
92
92
  const leaderWins = data.filter(item => item.slot1.includes(`${i + 1}`)).map(item => item.wins)
93
93
 
94
- const leaderWinsMean = leaderWins.reduce((sum, value) => sum + value, 0) / leaderWins.length;
95
- const leaderWinsVariance = leaderWins.reduce((sum, value) => sum + Math.pow(value - leaderWinsMean, 2), 0) / (leaderWins.length - 1);
94
+ const leaderWinsMean = leaderWins.reduce((sum, value) => sum + value, 0) / leaderWins.length
95
+ const leaderWinsVariance = leaderWins.reduce((sum, value) => sum + Math.pow(value - leaderWinsMean, 2), 0) / (leaderWins.length - 1)
96
96
  const leaderWinsStdev = Math.sqrt(leaderWinsVariance)
97
97
 
98
98
  // Calculate Interquartile Range (IQR)
@@ -196,7 +196,7 @@ const main = async (executionId, numOfTasks, gameLogicVersion) => {
196
196
  })
197
197
 
198
198
  if(process.env.CLOUD_RUN_JOB) {
199
- const bucket = storage.bucket('gotchi-battler-sims-v1-7-2')
199
+ const bucket = storage.bucket('gotchi-battler-sims-v1-7-3')
200
200
 
201
201
  // Get number of files in the bucket
202
202
  const [files] = await bucket.getFiles()
@@ -125,7 +125,7 @@ const getGotchisSimNameFromTeam = (team) => {
125
125
 
126
126
  [0,1,2,3,4].forEach(i => {
127
127
  const gotchi = team.formation.back[i] || team.formation.front[i]
128
- const position = !!team.formation.back[i] ? 'B' : 'F'
128
+ const position = team.formation.back[i] ? 'B' : 'F'
129
129
 
130
130
  const nameParts = gotchi.name.split(' ')
131
131
  // Return e.g. "R|++++|1_B"
@@ -139,7 +139,7 @@ const runSims = async (simsVersion, gameLogicVersion, simsPerMatchup) => {
139
139
  const classCombos = require(`./${simsVersion}/class_combos.js`)
140
140
  const classTraitCombos = require(`./${simsVersion}/trait_combos.json`)
141
141
  const setTeamPositions = require(`./${simsVersion}/setTeamPositions`)
142
- const gameLogic = require("../../game-logic")[gameLogicVersion].gameLoop
142
+ const gameLogic = require('../../game-logic')[gameLogicVersion].gameLoop
143
143
 
144
144
  const attackingPowerLevels = ['Godlike']
145
145
  const defendingPowerLevels = ['Godlike', 'Mythical', 'Legendary']
@@ -181,7 +181,7 @@ const runSims = async (simsVersion, gameLogicVersion, simsPerMatchup) => {
181
181
 
182
182
  console.time(`Sims for ${attackingTeam.name}`)
183
183
 
184
- defendingTeamIndexes.forEach((defendingTeamIndex, i) => {
184
+ defendingTeamIndexes.forEach((defendingTeamIndex) => {
185
185
  const defendingTeam = createTeamFromTeamIndex(defendingTeamIndex, trainingGotchis, setTeamPositions)
186
186
 
187
187
  let matchupWins = 0
@@ -231,23 +231,16 @@ const runSims = async (simsVersion, gameLogicVersion, simsPerMatchup) => {
231
231
 
232
232
  if (process.env.CLOUD_RUN_JOB && process.env.SIMS_BUCKET) {
233
233
  // Save as JSON to GCS
234
- try {
235
- await storage.bucket(process.env.SIMS_BUCKET).file(`${process.env.CLOUD_RUN_EXECUTION}_${attackingTeamIndex}.json`).save(JSON.stringify(results))
236
- } catch (err) {
237
- throw err
238
- }
234
+ await storage.bucket(process.env.SIMS_BUCKET).file(`${process.env.CLOUD_RUN_EXECUTION}_${attackingTeamIndex}.json`).save(JSON.stringify(results))
239
235
 
240
236
  } else {
241
- console.log('Results', results)
237
+ console.log('Results', results)
242
238
 
243
239
  // Test saving to GCS
244
- if (false) {
240
+ const test = false
241
+ if (test) {
245
242
  process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(__dirname, '../../keyfile.json')
246
- try {
247
- await storage.bucket(process.env.SIMS_BUCKET).file(`avg-sims-znw68_${attackingTeamIndex}.json`).save(JSON.stringify(results))
248
- } catch (err) {
249
- throw err
250
- }
243
+ await storage.bucket(process.env.SIMS_BUCKET).file(`avg-sims-znw68_${attackingTeamIndex}.json`).save(JSON.stringify(results))
251
244
  }
252
245
  }
253
246
  }
@@ -259,9 +252,9 @@ module.exports = runSims
259
252
  // 2nd argument is the sims version
260
253
  // 3rd argument is the game logic version
261
254
  // 4th argument is the number of sims per matchup
262
- // node scripts/balancing/sims.js 0 v1.7.2 v1.7 3 true
255
+ // node scripts/balancing/sims.js 0 v1.7.3 v1.7 3 true
263
256
  if (require.main === module) {
264
- const simsVersion = process.env.SIMS_VERSION || process.argv[3] || 'v1.7.2'
257
+ const simsVersion = process.env.SIMS_VERSION || process.argv[3] || 'v1.7.3'
265
258
  const gameLogicVersion = process.env.GAME_LOGIC_VERSION || process.argv[4] || 'v1.7'
266
259
  const simsPerMatchup = parseInt(process.env.SIMS_PER_MATCHUP) || parseInt(process.argv[5]) || 3
267
260
  const useAvg = process.env.USE_AVG || process.argv[6] === 'true' || false
@@ -0,0 +1,119 @@
1
+ const mapTeam = (team) => {
2
+ for(const gotchi of team.formation.front) {
3
+ if (gotchi) mapGotchi(gotchi)
4
+ }
5
+
6
+ for(const gotchi of team.formation.back) {
7
+ if (gotchi) mapGotchi(gotchi)
8
+ }
9
+ }
10
+
11
+ // This is copied/hacked from the mapGotchi function in the backend
12
+ const mapGotchi = (gotchi) => {
13
+ const traitMaps = {
14
+ speed: {
15
+ baseFormula: 100,
16
+ multiplier: 1,
17
+ traitKey: 0,
18
+ isNegative: false
19
+ },
20
+ health: {
21
+ baseFormula: 'brs*0.75',
22
+ multiplier: 12,
23
+ traitKey: 0,
24
+ isNegative: true
25
+ },
26
+ crit: {
27
+ baseFormula: 0,
28
+ multiplier: 0.5,
29
+ traitKey: 1,
30
+ isNegative: false
31
+ },
32
+ armor: {
33
+ baseFormula: 0,
34
+ multiplier: 2,
35
+ traitKey: 1,
36
+ isNegative: true
37
+ },
38
+ evade: {
39
+ baseFormula: 0,
40
+ multiplier: 0.3,
41
+ traitKey: 2,
42
+ isNegative: false
43
+ },
44
+ resist: {
45
+ baseFormula: 0,
46
+ multiplier: 1,
47
+ traitKey: 2,
48
+ isNegative: true
49
+ },
50
+ magic: {
51
+ baseFormula: 'brs*0.25',
52
+ multiplier: 5,
53
+ traitKey: 3,
54
+ isNegative: false
55
+ },
56
+ physical: {
57
+ baseFormula: 'brs*0.25',
58
+ multiplier: 5,
59
+ traitKey: 3,
60
+ isNegative: true
61
+ },
62
+ accuracy: {
63
+ baseFormula: 50,
64
+ multiplier: 0.5,
65
+ traitKey: 45,
66
+ isNegative: false
67
+ }
68
+ }
69
+
70
+ const traitValue = (trait) => {
71
+ return trait < 50 ? 50 - trait : trait - 50 + 1
72
+ }
73
+
74
+ const onchainVals = [
75
+ gotchi.nrg,
76
+ gotchi.agg,
77
+ gotchi.spk,
78
+ gotchi.brn,
79
+ gotchi.eyc,
80
+ gotchi.eys
81
+ ]
82
+ // Convert trait value to in-game value
83
+ const traitValues = onchainVals.map(x => { return traitValue(x) })
84
+
85
+ // Map traits
86
+ for(const trait in traitMaps) {
87
+ const traitMap = traitMaps[trait]
88
+ const onchainVal = onchainVals[traitMap.traitKey]
89
+
90
+ let base = traitMap.baseFormula
91
+
92
+ // If baseFormula is a string and contains a * then it is a formula
93
+ if (typeof traitMap.baseFormula === 'string' && traitMap.baseFormula.includes('*')) {
94
+ const formula = traitMap.baseFormula.split('*')
95
+
96
+ if (!gotchi[formula[0]]) throw new Error('Trait not found: ', formula[0])
97
+
98
+ base = Math.round(Number(gotchi[formula[0]]) * Number(formula[1]))
99
+ }
100
+
101
+ let newTrait
102
+ if (trait !== 'accuracy') {
103
+ if (traitMap.isNegative) {
104
+ newTrait = onchainVal < 50 ? Math.round(base + (traitValues[traitMap.traitKey] * traitMap.multiplier)) : base
105
+ } else {
106
+ newTrait = onchainVal < 50 ? base : Math.round(base + (traitValues[traitMap.traitKey] * traitMap.multiplier))
107
+ }
108
+ } else {
109
+ newTrait = base + ((traitValues[4] + traitValues[5]) * traitMap.multiplier)
110
+ }
111
+
112
+ if (newTrait !== gotchi[trait]) gotchi[trait] = newTrait
113
+ }
114
+ }
115
+
116
+ module.exports = {
117
+ mapGotchi,
118
+ mapTeam
119
+ }