gotchi-battler-game-logic 2.0.6 → 2.0.8
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.
- package/.vscode/settings.json +4 -4
- package/Dockerfile +9 -9
- package/README.md +49 -49
- package/cloudbuild.yaml +27 -27
- package/constants/tournamentManagerAbi.json +208 -208
- package/game-logic/index.js +6 -6
- package/game-logic/v1.4/constants.js +114 -114
- package/game-logic/v1.4/index.js +1366 -1366
- package/game-logic/v1.6/constants.js +123 -123
- package/game-logic/v1.6/index.js +1406 -1406
- package/game-logic/v1.7/constants.js +142 -140
- package/game-logic/v1.7/helpers.js +595 -593
- package/game-logic/v1.7/index.js +802 -795
- package/index.js +12 -12
- package/package.json +26 -26
- package/schemas/team.json +349 -343
- package/scripts/balancing/createCSV.js +126 -126
- package/scripts/balancing/fixTrainingGotchis.js +155 -259
- package/scripts/balancing/processSims.js +229 -229
- package/scripts/balancing/sims.js +278 -278
- package/scripts/balancing/v1.7/class_combos.js +43 -43
- package/scripts/balancing/v1.7/setTeamPositions.js +105 -105
- package/scripts/balancing/v1.7/training_gotchis.json +20161 -20161
- package/scripts/balancing/v1.7/trait_combos.json +9 -9
- package/scripts/balancing/v1.7.1/class_combos.js +43 -43
- package/scripts/balancing/v1.7.1/setTeamPositions.js +122 -122
- package/scripts/balancing/v1.7.1/training_gotchis.json +22401 -22401
- package/scripts/balancing/v1.7.1/trait_combos.json +9 -9
- package/scripts/balancing/v1.7.2/class_combos.js +44 -0
- package/scripts/balancing/v1.7.2/setTeamPositions.js +122 -0
- package/scripts/balancing/v1.7.2/training_gotchis.json +22402 -0
- package/scripts/balancing/v1.7.2/trait_combos.json +10 -0
- package/scripts/data/team1.json +213 -213
- package/scripts/data/team2.json +200 -200
- package/scripts/data/tournaments.json +66 -66
- package/scripts/{runBattle.js → runLocalBattle.js} +18 -18
- package/scripts/runRealBattle.js +52 -0
- package/scripts/simRealBattle.js +121 -0
- package/scripts/validateBattle.js +74 -70
- package/scripts/validateTournament.js +101 -101
- package/utils/contracts.js +12 -12
- package/utils/errors.js +29 -29
- package/utils/mapGotchi.js +119 -0
- package/utils/transforms.js +89 -88
- package/utils/validations.js +39 -39
|
@@ -1,278 +1,278 @@
|
|
|
1
|
-
require('dotenv').config()
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const crypto = require('crypto')
|
|
4
|
-
const { Storage } = require('@google-cloud/storage')
|
|
5
|
-
const storage = new Storage()
|
|
6
|
-
const classes = ['Ninja','Enlightened','Cleaver','Tank','Cursed','Healer', 'Mage', 'Troll']
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Create teams from all the possible trait combinations from the class combination
|
|
10
|
-
* @param {Array} classCombo The class combinations
|
|
11
|
-
* @param {Array} classTraitCombos The trait combinations for each class
|
|
12
|
-
* @param {Array} powerLevels The power levels
|
|
13
|
-
* @param {Array} trainingGotchis The training gotchis
|
|
14
|
-
* @param {Boolean} useTraitSets If false then exhaustive search is done, if true then only the trait sets are used
|
|
15
|
-
* @returns {Object} A team object
|
|
16
|
-
*/
|
|
17
|
-
const createTeamIndexes = (classCombos, classTraitCombos, powerLevels, trainingGotchis, useTraitSets) => {
|
|
18
|
-
|
|
19
|
-
const teams = []
|
|
20
|
-
|
|
21
|
-
if (!useTraitSets) {
|
|
22
|
-
classCombos.forEach(classCombo => {
|
|
23
|
-
powerLevels.forEach(powerLevel => {
|
|
24
|
-
// Loop over each class in the classCombo
|
|
25
|
-
for(let i = 0; i < classCombo.length; i++) {
|
|
26
|
-
classTraitCombos[classCombo[i] - 1].forEach(traitSet1 => {
|
|
27
|
-
for (let j = i + 1; j < classCombo.length; j++) {
|
|
28
|
-
classTraitCombos[classCombo[j] - 1].forEach(traitSet2 => {
|
|
29
|
-
for (let k = j + 1; k < classCombo.length; k++) {
|
|
30
|
-
classTraitCombos[classCombo[k] - 1].forEach(traitSet3 => {
|
|
31
|
-
for (let l = k + 1; l < classCombo.length; l++) {
|
|
32
|
-
classTraitCombos[classCombo[l] - 1].forEach(traitSet4 => {
|
|
33
|
-
for (let m = l + 1; m < classCombo.length; m++) {
|
|
34
|
-
classTraitCombos[classCombo[m] - 1].forEach(traitSet5 => {
|
|
35
|
-
const team = [];
|
|
36
|
-
|
|
37
|
-
[traitSet1, traitSet2, traitSet3, traitSet4, traitSet5].forEach((traitSet, index) => {
|
|
38
|
-
const gotchiName = `${powerLevel} ${traitSet} ${classes[classCombo[index] - 1]}`
|
|
39
|
-
const gotchi = trainingGotchis.find(gotchi => {
|
|
40
|
-
return gotchi.name === gotchiName
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
if (!gotchi) throw new Error(`Gotchi not found: "${gotchiName}"`)
|
|
44
|
-
|
|
45
|
-
team.push(gotchi.id)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
teams.push(team)
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
} else {
|
|
62
|
-
classCombos.forEach(classCombo => {
|
|
63
|
-
powerLevels.forEach(powerLevel => {
|
|
64
|
-
// Loop over how many trait sets there are in the classTraitCombos array
|
|
65
|
-
classTraitCombos[0].forEach((x, i) => {
|
|
66
|
-
const team = []
|
|
67
|
-
classCombo.forEach(classIndex => {
|
|
68
|
-
const gotchiName = `${powerLevel} ${classTraitCombos[classIndex - 1][i]} ${classes[classIndex - 1]}`
|
|
69
|
-
const gotchi = trainingGotchis.find(gotchi => {
|
|
70
|
-
return gotchi.name === gotchiName
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
if (!gotchi) throw new Error(`Gotchi not found: "${gotchiName}"`)
|
|
74
|
-
|
|
75
|
-
team.push(gotchi.id)
|
|
76
|
-
})
|
|
77
|
-
teams.push(team)
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return teams
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Creates an in game team object from a team index
|
|
90
|
-
* @param {Array} teamIndex An array of gotchi ids
|
|
91
|
-
* @returns {Object} An in game team object
|
|
92
|
-
*/
|
|
93
|
-
const createTeamFromTeamIndex = (teamIndex, trainingGotchis, setTeamPositions) => {
|
|
94
|
-
const team = {
|
|
95
|
-
formation: {
|
|
96
|
-
front: [null, null, null, null, null],
|
|
97
|
-
back: [null, null, null, null, null],
|
|
98
|
-
},
|
|
99
|
-
leader: null,
|
|
100
|
-
name: null,
|
|
101
|
-
owner: null
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Put all in the back row for now
|
|
105
|
-
team.formation.back = teamIndex.map(gotchiId => {
|
|
106
|
-
const gotchi = trainingGotchis.find(gotchi => gotchi.id === gotchiId)
|
|
107
|
-
|
|
108
|
-
if (!gotchi) throw new Error(`Gotchi not found with id: "${gotchiId}"`)
|
|
109
|
-
|
|
110
|
-
return gotchi
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
team.leader = team.formation.back[0].id
|
|
114
|
-
team.name = `${team.formation.back[0].name[0]} ${teamIndex}` // e.g. "M 1,2,3,4,5"
|
|
115
|
-
team.owner = '0x0000000000000000000000000000000000000000'
|
|
116
|
-
|
|
117
|
-
// Set the team positions for each gotchi being in the front or back row
|
|
118
|
-
setTeamPositions(team)
|
|
119
|
-
|
|
120
|
-
return team
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const getGotchisSimNameFromTeam = (team) => {
|
|
124
|
-
const names = [];
|
|
125
|
-
|
|
126
|
-
[0,1,2,3,4].forEach(i => {
|
|
127
|
-
const gotchi = team.formation.back[i] || team.formation.front[i]
|
|
128
|
-
const position = !!team.formation.back[i] ? 'B' : 'F'
|
|
129
|
-
|
|
130
|
-
const nameParts = gotchi.name.split(' ')
|
|
131
|
-
// Return e.g. "R|++++|1_B"
|
|
132
|
-
names.push(`${nameParts[0][0]}|${nameParts[1]}|${classes.indexOf(nameParts[2]) + 1}_${position}`)
|
|
133
|
-
})
|
|
134
|
-
return names
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const runSims = async (simsVersion, gameLogicVersion, simsPerMatchup) => {
|
|
138
|
-
const trainingGotchis = require(`./${simsVersion}/training_gotchis.json`)
|
|
139
|
-
const classCombos = require(`./${simsVersion}/class_combos.js`)
|
|
140
|
-
const classTraitCombos = require(`./${simsVersion}/trait_combos.json`)
|
|
141
|
-
const setTeamPositions = require(`./${simsVersion}/setTeamPositions`)
|
|
142
|
-
const gameLogic = require("../../game-logic")[gameLogicVersion].gameLoop
|
|
143
|
-
|
|
144
|
-
const attackingPowerLevels = ['Godlike']
|
|
145
|
-
const defendingPowerLevels = ['Godlike', 'Mythical', 'Legendary']
|
|
146
|
-
|
|
147
|
-
const attackingTeamIndexes = createTeamIndexes(classCombos, classTraitCombos, attackingPowerLevels, trainingGotchis, true)
|
|
148
|
-
|
|
149
|
-
// console.log(`Running sims for ${attackingTeamIndexes.length} attacking teams`)
|
|
150
|
-
|
|
151
|
-
const defendingTeamIndexes = createTeamIndexes(classCombos, classTraitCombos, defendingPowerLevels, trainingGotchis, true)
|
|
152
|
-
|
|
153
|
-
// console.log(`Against ${defendingTeamIndexes.length} defending teams`)
|
|
154
|
-
|
|
155
|
-
// Which attacking team are we running the sims on?
|
|
156
|
-
// If running on Cloud Run, use the task index
|
|
157
|
-
// If running locally, use the command line argument or default to 0
|
|
158
|
-
const attackingTeamIndex = process.env.CLOUD_RUN_TASK_INDEX ? parseInt(process.env.CLOUD_RUN_TASK_INDEX) : parseInt(process.argv[2]) || 0
|
|
159
|
-
|
|
160
|
-
// Get the attacking team
|
|
161
|
-
const attackingTeam = createTeamFromTeamIndex(attackingTeamIndexes[attackingTeamIndex], trainingGotchis, setTeamPositions)
|
|
162
|
-
const gotchiSimNames = getGotchisSimNameFromTeam(attackingTeam)
|
|
163
|
-
// Run the sims for each defending team
|
|
164
|
-
const results = {
|
|
165
|
-
id: attackingTeamIndex,
|
|
166
|
-
slot1: gotchiSimNames[0],
|
|
167
|
-
slot2: gotchiSimNames[1],
|
|
168
|
-
slot3: gotchiSimNames[2],
|
|
169
|
-
slot4: gotchiSimNames[3],
|
|
170
|
-
slot5: gotchiSimNames[4],
|
|
171
|
-
wins: 0,
|
|
172
|
-
draws: 0,
|
|
173
|
-
losses: 0
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
defendingPowerLevels.forEach(powerLevel => {
|
|
177
|
-
results[`wins${powerLevel}`] = 0
|
|
178
|
-
results[`draws${powerLevel}`] = 0
|
|
179
|
-
results[`losses${powerLevel}`] = 0
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
console.time(`Sims for ${attackingTeam.name}`)
|
|
183
|
-
|
|
184
|
-
defendingTeamIndexes.forEach((defendingTeamIndex, i) => {
|
|
185
|
-
const defendingTeam = createTeamFromTeamIndex(defendingTeamIndex, trainingGotchis, setTeamPositions)
|
|
186
|
-
|
|
187
|
-
let matchupWins = 0
|
|
188
|
-
let matchupDraws = 0
|
|
189
|
-
let matchupLosses = 0
|
|
190
|
-
|
|
191
|
-
// Run the sims
|
|
192
|
-
Array(simsPerMatchup).fill(null).forEach(() => {
|
|
193
|
-
// Quit early if result is already determined
|
|
194
|
-
if (matchupWins >= simsPerMatchup / 2 ||
|
|
195
|
-
matchupLosses >= simsPerMatchup / 2 ||
|
|
196
|
-
matchupDraws >= simsPerMatchup / 2) return
|
|
197
|
-
|
|
198
|
-
const logs = gameLogic(attackingTeam, defendingTeam, crypto.randomBytes(32).toString('hex'))
|
|
199
|
-
|
|
200
|
-
if (logs.result.winner === 1) matchupWins++
|
|
201
|
-
if (logs.result.winner === 0) matchupDraws++
|
|
202
|
-
if (logs.result.winner === 2) matchupLosses++
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
// Get first letter of oppeonent team name to determine power level
|
|
206
|
-
const powerLevel = defendingPowerLevels.find(name => name[0] === defendingTeam.name[0])
|
|
207
|
-
|
|
208
|
-
if (matchupWins >= simsPerMatchup / 2) {
|
|
209
|
-
results.wins++
|
|
210
|
-
results[`wins${powerLevel}`]++
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (matchupDraws >= simsPerMatchup / 2) {
|
|
214
|
-
results.draws++
|
|
215
|
-
results[`draws${powerLevel}`]++
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (matchupLosses >= simsPerMatchup / 2) {
|
|
219
|
-
results.losses++
|
|
220
|
-
results[`losses${powerLevel}`]++
|
|
221
|
-
}
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
console.timeEnd(`Sims for ${attackingTeam.name}`)
|
|
225
|
-
|
|
226
|
-
// Check total wins, draws, losses to make sure they add up to number of defending teams
|
|
227
|
-
const totalMatchups = results.wins + results.draws + results.losses
|
|
228
|
-
if (totalMatchups !== defendingTeamIndexes.length) {
|
|
229
|
-
throw new Error(`Total matchups (${totalMatchups}) does not match number of defending teams (${defendingTeamIndexes.length})`)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (process.env.CLOUD_RUN_JOB && process.env.SIMS_BUCKET) {
|
|
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
|
-
}
|
|
239
|
-
|
|
240
|
-
} else {
|
|
241
|
-
console.log('Results', results)
|
|
242
|
-
|
|
243
|
-
// Test saving to GCS
|
|
244
|
-
if (false) {
|
|
245
|
-
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
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
module.exports = runSims
|
|
256
|
-
|
|
257
|
-
// If running from the command line, run the sims
|
|
258
|
-
// 1st argument is the attacking team index
|
|
259
|
-
// 2nd argument is the sims version
|
|
260
|
-
// 3rd argument is the game logic version
|
|
261
|
-
// 4th argument is the number of sims per matchup
|
|
262
|
-
// node scripts/balancing/sims.js 0 v1.7.
|
|
263
|
-
if (require.main === module) {
|
|
264
|
-
const simsVersion = process.env.SIMS_VERSION || process.argv[3] || 'v1.7'
|
|
265
|
-
const gameLogicVersion = process.env.GAME_LOGIC_VERSION || process.argv[4] || 'v1.7'
|
|
266
|
-
const simsPerMatchup = parseInt(process.env.SIMS_PER_MATCHUP) || parseInt(process.argv[5]) || 3
|
|
267
|
-
const useAvg = process.env.USE_AVG || process.argv[6] === 'true' || false
|
|
268
|
-
|
|
269
|
-
runSims(simsVersion, gameLogicVersion, simsPerMatchup, useAvg)
|
|
270
|
-
.then(() => {
|
|
271
|
-
console.log('Done')
|
|
272
|
-
process.exit(0)
|
|
273
|
-
})
|
|
274
|
-
.catch((err) => {
|
|
275
|
-
console.error(err)
|
|
276
|
-
process.exit(1)
|
|
277
|
-
})
|
|
278
|
-
}
|
|
1
|
+
require('dotenv').config()
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const crypto = require('crypto')
|
|
4
|
+
const { Storage } = require('@google-cloud/storage')
|
|
5
|
+
const storage = new Storage()
|
|
6
|
+
const classes = ['Ninja','Enlightened','Cleaver','Tank','Cursed','Healer', 'Mage', 'Troll']
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create teams from all the possible trait combinations from the class combination
|
|
10
|
+
* @param {Array} classCombo The class combinations
|
|
11
|
+
* @param {Array} classTraitCombos The trait combinations for each class
|
|
12
|
+
* @param {Array} powerLevels The power levels
|
|
13
|
+
* @param {Array} trainingGotchis The training gotchis
|
|
14
|
+
* @param {Boolean} useTraitSets If false then exhaustive search is done, if true then only the trait sets are used
|
|
15
|
+
* @returns {Object} A team object
|
|
16
|
+
*/
|
|
17
|
+
const createTeamIndexes = (classCombos, classTraitCombos, powerLevels, trainingGotchis, useTraitSets) => {
|
|
18
|
+
|
|
19
|
+
const teams = []
|
|
20
|
+
|
|
21
|
+
if (!useTraitSets) {
|
|
22
|
+
classCombos.forEach(classCombo => {
|
|
23
|
+
powerLevels.forEach(powerLevel => {
|
|
24
|
+
// Loop over each class in the classCombo
|
|
25
|
+
for(let i = 0; i < classCombo.length; i++) {
|
|
26
|
+
classTraitCombos[classCombo[i] - 1].forEach(traitSet1 => {
|
|
27
|
+
for (let j = i + 1; j < classCombo.length; j++) {
|
|
28
|
+
classTraitCombos[classCombo[j] - 1].forEach(traitSet2 => {
|
|
29
|
+
for (let k = j + 1; k < classCombo.length; k++) {
|
|
30
|
+
classTraitCombos[classCombo[k] - 1].forEach(traitSet3 => {
|
|
31
|
+
for (let l = k + 1; l < classCombo.length; l++) {
|
|
32
|
+
classTraitCombos[classCombo[l] - 1].forEach(traitSet4 => {
|
|
33
|
+
for (let m = l + 1; m < classCombo.length; m++) {
|
|
34
|
+
classTraitCombos[classCombo[m] - 1].forEach(traitSet5 => {
|
|
35
|
+
const team = [];
|
|
36
|
+
|
|
37
|
+
[traitSet1, traitSet2, traitSet3, traitSet4, traitSet5].forEach((traitSet, index) => {
|
|
38
|
+
const gotchiName = `${powerLevel} ${traitSet} ${classes[classCombo[index] - 1]}`
|
|
39
|
+
const gotchi = trainingGotchis.find(gotchi => {
|
|
40
|
+
return gotchi.name === gotchiName
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if (!gotchi) throw new Error(`Gotchi not found: "${gotchiName}"`)
|
|
44
|
+
|
|
45
|
+
team.push(gotchi.id)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
teams.push(team)
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
} else {
|
|
62
|
+
classCombos.forEach(classCombo => {
|
|
63
|
+
powerLevels.forEach(powerLevel => {
|
|
64
|
+
// Loop over how many trait sets there are in the classTraitCombos array
|
|
65
|
+
classTraitCombos[0].forEach((x, i) => {
|
|
66
|
+
const team = []
|
|
67
|
+
classCombo.forEach(classIndex => {
|
|
68
|
+
const gotchiName = `${powerLevel} ${classTraitCombos[classIndex - 1][i]} ${classes[classIndex - 1]}`
|
|
69
|
+
const gotchi = trainingGotchis.find(gotchi => {
|
|
70
|
+
return gotchi.name === gotchiName
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
if (!gotchi) throw new Error(`Gotchi not found: "${gotchiName}"`)
|
|
74
|
+
|
|
75
|
+
team.push(gotchi.id)
|
|
76
|
+
})
|
|
77
|
+
teams.push(team)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
return teams
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Creates an in game team object from a team index
|
|
90
|
+
* @param {Array} teamIndex An array of gotchi ids
|
|
91
|
+
* @returns {Object} An in game team object
|
|
92
|
+
*/
|
|
93
|
+
const createTeamFromTeamIndex = (teamIndex, trainingGotchis, setTeamPositions) => {
|
|
94
|
+
const team = {
|
|
95
|
+
formation: {
|
|
96
|
+
front: [null, null, null, null, null],
|
|
97
|
+
back: [null, null, null, null, null],
|
|
98
|
+
},
|
|
99
|
+
leader: null,
|
|
100
|
+
name: null,
|
|
101
|
+
owner: null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Put all in the back row for now
|
|
105
|
+
team.formation.back = teamIndex.map(gotchiId => {
|
|
106
|
+
const gotchi = trainingGotchis.find(gotchi => gotchi.id === gotchiId)
|
|
107
|
+
|
|
108
|
+
if (!gotchi) throw new Error(`Gotchi not found with id: "${gotchiId}"`)
|
|
109
|
+
|
|
110
|
+
return gotchi
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
team.leader = team.formation.back[0].id
|
|
114
|
+
team.name = `${team.formation.back[0].name[0]} ${teamIndex}` // e.g. "M 1,2,3,4,5"
|
|
115
|
+
team.owner = '0x0000000000000000000000000000000000000000'
|
|
116
|
+
|
|
117
|
+
// Set the team positions for each gotchi being in the front or back row
|
|
118
|
+
setTeamPositions(team)
|
|
119
|
+
|
|
120
|
+
return team
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const getGotchisSimNameFromTeam = (team) => {
|
|
124
|
+
const names = [];
|
|
125
|
+
|
|
126
|
+
[0,1,2,3,4].forEach(i => {
|
|
127
|
+
const gotchi = team.formation.back[i] || team.formation.front[i]
|
|
128
|
+
const position = !!team.formation.back[i] ? 'B' : 'F'
|
|
129
|
+
|
|
130
|
+
const nameParts = gotchi.name.split(' ')
|
|
131
|
+
// Return e.g. "R|++++|1_B"
|
|
132
|
+
names.push(`${nameParts[0][0]}|${nameParts[1]}|${classes.indexOf(nameParts[2]) + 1}_${position}`)
|
|
133
|
+
})
|
|
134
|
+
return names
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const runSims = async (simsVersion, gameLogicVersion, simsPerMatchup) => {
|
|
138
|
+
const trainingGotchis = require(`./${simsVersion}/training_gotchis.json`)
|
|
139
|
+
const classCombos = require(`./${simsVersion}/class_combos.js`)
|
|
140
|
+
const classTraitCombos = require(`./${simsVersion}/trait_combos.json`)
|
|
141
|
+
const setTeamPositions = require(`./${simsVersion}/setTeamPositions`)
|
|
142
|
+
const gameLogic = require("../../game-logic")[gameLogicVersion].gameLoop
|
|
143
|
+
|
|
144
|
+
const attackingPowerLevels = ['Godlike']
|
|
145
|
+
const defendingPowerLevels = ['Godlike', 'Mythical', 'Legendary']
|
|
146
|
+
|
|
147
|
+
const attackingTeamIndexes = createTeamIndexes(classCombos, classTraitCombos, attackingPowerLevels, trainingGotchis, true)
|
|
148
|
+
|
|
149
|
+
// console.log(`Running sims for ${attackingTeamIndexes.length} attacking teams`)
|
|
150
|
+
|
|
151
|
+
const defendingTeamIndexes = createTeamIndexes(classCombos, classTraitCombos, defendingPowerLevels, trainingGotchis, true)
|
|
152
|
+
|
|
153
|
+
// console.log(`Against ${defendingTeamIndexes.length} defending teams`)
|
|
154
|
+
|
|
155
|
+
// Which attacking team are we running the sims on?
|
|
156
|
+
// If running on Cloud Run, use the task index
|
|
157
|
+
// If running locally, use the command line argument or default to 0
|
|
158
|
+
const attackingTeamIndex = process.env.CLOUD_RUN_TASK_INDEX ? parseInt(process.env.CLOUD_RUN_TASK_INDEX) : parseInt(process.argv[2]) || 0
|
|
159
|
+
|
|
160
|
+
// Get the attacking team
|
|
161
|
+
const attackingTeam = createTeamFromTeamIndex(attackingTeamIndexes[attackingTeamIndex], trainingGotchis, setTeamPositions)
|
|
162
|
+
const gotchiSimNames = getGotchisSimNameFromTeam(attackingTeam)
|
|
163
|
+
// Run the sims for each defending team
|
|
164
|
+
const results = {
|
|
165
|
+
id: attackingTeamIndex,
|
|
166
|
+
slot1: gotchiSimNames[0],
|
|
167
|
+
slot2: gotchiSimNames[1],
|
|
168
|
+
slot3: gotchiSimNames[2],
|
|
169
|
+
slot4: gotchiSimNames[3],
|
|
170
|
+
slot5: gotchiSimNames[4],
|
|
171
|
+
wins: 0,
|
|
172
|
+
draws: 0,
|
|
173
|
+
losses: 0
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
defendingPowerLevels.forEach(powerLevel => {
|
|
177
|
+
results[`wins${powerLevel}`] = 0
|
|
178
|
+
results[`draws${powerLevel}`] = 0
|
|
179
|
+
results[`losses${powerLevel}`] = 0
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
console.time(`Sims for ${attackingTeam.name}`)
|
|
183
|
+
|
|
184
|
+
defendingTeamIndexes.forEach((defendingTeamIndex, i) => {
|
|
185
|
+
const defendingTeam = createTeamFromTeamIndex(defendingTeamIndex, trainingGotchis, setTeamPositions)
|
|
186
|
+
|
|
187
|
+
let matchupWins = 0
|
|
188
|
+
let matchupDraws = 0
|
|
189
|
+
let matchupLosses = 0
|
|
190
|
+
|
|
191
|
+
// Run the sims
|
|
192
|
+
Array(simsPerMatchup).fill(null).forEach(() => {
|
|
193
|
+
// Quit early if result is already determined
|
|
194
|
+
if (matchupWins >= simsPerMatchup / 2 ||
|
|
195
|
+
matchupLosses >= simsPerMatchup / 2 ||
|
|
196
|
+
matchupDraws >= simsPerMatchup / 2) return
|
|
197
|
+
|
|
198
|
+
const logs = gameLogic(attackingTeam, defendingTeam, crypto.randomBytes(32).toString('hex'))
|
|
199
|
+
|
|
200
|
+
if (logs.result.winner === 1) matchupWins++
|
|
201
|
+
if (logs.result.winner === 0) matchupDraws++
|
|
202
|
+
if (logs.result.winner === 2) matchupLosses++
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// Get first letter of oppeonent team name to determine power level
|
|
206
|
+
const powerLevel = defendingPowerLevels.find(name => name[0] === defendingTeam.name[0])
|
|
207
|
+
|
|
208
|
+
if (matchupWins >= simsPerMatchup / 2) {
|
|
209
|
+
results.wins++
|
|
210
|
+
results[`wins${powerLevel}`]++
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (matchupDraws >= simsPerMatchup / 2) {
|
|
214
|
+
results.draws++
|
|
215
|
+
results[`draws${powerLevel}`]++
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (matchupLosses >= simsPerMatchup / 2) {
|
|
219
|
+
results.losses++
|
|
220
|
+
results[`losses${powerLevel}`]++
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
console.timeEnd(`Sims for ${attackingTeam.name}`)
|
|
225
|
+
|
|
226
|
+
// Check total wins, draws, losses to make sure they add up to number of defending teams
|
|
227
|
+
const totalMatchups = results.wins + results.draws + results.losses
|
|
228
|
+
if (totalMatchups !== defendingTeamIndexes.length) {
|
|
229
|
+
throw new Error(`Total matchups (${totalMatchups}) does not match number of defending teams (${defendingTeamIndexes.length})`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (process.env.CLOUD_RUN_JOB && process.env.SIMS_BUCKET) {
|
|
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
|
+
}
|
|
239
|
+
|
|
240
|
+
} else {
|
|
241
|
+
console.log('Results', results)
|
|
242
|
+
|
|
243
|
+
// Test saving to GCS
|
|
244
|
+
if (false) {
|
|
245
|
+
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
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = runSims
|
|
256
|
+
|
|
257
|
+
// If running from the command line, run the sims
|
|
258
|
+
// 1st argument is the attacking team index
|
|
259
|
+
// 2nd argument is the sims version
|
|
260
|
+
// 3rd argument is the game logic version
|
|
261
|
+
// 4th argument is the number of sims per matchup
|
|
262
|
+
// node scripts/balancing/sims.js 0 v1.7.2 v1.7 3 true
|
|
263
|
+
if (require.main === module) {
|
|
264
|
+
const simsVersion = process.env.SIMS_VERSION || process.argv[3] || 'v1.7.2'
|
|
265
|
+
const gameLogicVersion = process.env.GAME_LOGIC_VERSION || process.argv[4] || 'v1.7'
|
|
266
|
+
const simsPerMatchup = parseInt(process.env.SIMS_PER_MATCHUP) || parseInt(process.argv[5]) || 3
|
|
267
|
+
const useAvg = process.env.USE_AVG || process.argv[6] === 'true' || false
|
|
268
|
+
|
|
269
|
+
runSims(simsVersion, gameLogicVersion, simsPerMatchup, useAvg)
|
|
270
|
+
.then(() => {
|
|
271
|
+
console.log('Done')
|
|
272
|
+
process.exit(0)
|
|
273
|
+
})
|
|
274
|
+
.catch((err) => {
|
|
275
|
+
console.error(err)
|
|
276
|
+
process.exit(1)
|
|
277
|
+
})
|
|
278
|
+
}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
const generateClassCombinations = () => {
|
|
2
|
-
// Generate combinations of the 8 classes for the 4 non-leader spots
|
|
3
|
-
const combinations = []
|
|
4
|
-
|
|
5
|
-
for (let i = 1; i <= 8; i++) {
|
|
6
|
-
for (let j = 1; j <= 8; j++) {
|
|
7
|
-
for (let k = 1; k <= 8; k++) {
|
|
8
|
-
for (let l = 1; l <= 8; l++) {
|
|
9
|
-
combinations.push([i, j, k, l].sort())
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Remove duplicate combinations, so [1,1,1,1] is allowed but [1,1,1,2] and [1,1,2,1] are duplicates
|
|
16
|
-
// Keep as numbers for now, convert to strings to remove duplicates, then convert back to numbers
|
|
17
|
-
const uniqueCombinations = [...new Set(combinations.map((combination) => combination.join('')))].map((combination) => combination.split('').map((number) => parseInt(number)))
|
|
18
|
-
|
|
19
|
-
return uniqueCombinations
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const getCombinationsForALeader = (leaderClass) => {
|
|
23
|
-
const combinations = generateClassCombinations()
|
|
24
|
-
|
|
25
|
-
const combinationsForALeader = combinations.map((combination) => {
|
|
26
|
-
return [leaderClass, ...combination]
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
return combinationsForALeader
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const getAllClassCombos = () => {
|
|
33
|
-
const allClassCombos = []
|
|
34
|
-
|
|
35
|
-
for (let i = 1; i <= 8; i++) {
|
|
36
|
-
allClassCombos.push(...getCombinationsForALeader(i))
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return allClassCombos
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const allClassCombos = getAllClassCombos()
|
|
43
|
-
|
|
1
|
+
const generateClassCombinations = () => {
|
|
2
|
+
// Generate combinations of the 8 classes for the 4 non-leader spots
|
|
3
|
+
const combinations = []
|
|
4
|
+
|
|
5
|
+
for (let i = 1; i <= 8; i++) {
|
|
6
|
+
for (let j = 1; j <= 8; j++) {
|
|
7
|
+
for (let k = 1; k <= 8; k++) {
|
|
8
|
+
for (let l = 1; l <= 8; l++) {
|
|
9
|
+
combinations.push([i, j, k, l].sort())
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Remove duplicate combinations, so [1,1,1,1] is allowed but [1,1,1,2] and [1,1,2,1] are duplicates
|
|
16
|
+
// Keep as numbers for now, convert to strings to remove duplicates, then convert back to numbers
|
|
17
|
+
const uniqueCombinations = [...new Set(combinations.map((combination) => combination.join('')))].map((combination) => combination.split('').map((number) => parseInt(number)))
|
|
18
|
+
|
|
19
|
+
return uniqueCombinations
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getCombinationsForALeader = (leaderClass) => {
|
|
23
|
+
const combinations = generateClassCombinations()
|
|
24
|
+
|
|
25
|
+
const combinationsForALeader = combinations.map((combination) => {
|
|
26
|
+
return [leaderClass, ...combination]
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return combinationsForALeader
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const getAllClassCombos = () => {
|
|
33
|
+
const allClassCombos = []
|
|
34
|
+
|
|
35
|
+
for (let i = 1; i <= 8; i++) {
|
|
36
|
+
allClassCombos.push(...getCombinationsForALeader(i))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return allClassCombos
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const allClassCombos = getAllClassCombos()
|
|
43
|
+
|
|
44
44
|
module.exports = allClassCombos
|