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.
- 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 +7 -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 +610 -593
- package/game-logic/v1.7/index.js +802 -795
- package/game-logic/v1.8/constants.js +135 -0
- package/game-logic/v1.8/helpers.js +628 -0
- package/game-logic/v1.8/index.js +826 -0
- 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/createTrainingGotchis.js +267 -0
- package/scripts/balancing/extractOnchainTraits.js +61 -0
- 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/mapGotchi.js +119 -0
- package/scripts/balancing/v1.7/setTeamPositions.js +105 -105
- package/scripts/balancing/v1.7/training_gotchis.json +20161 -20161
- package/scripts/balancing/v1.7/training_gotchis_traits.json +520 -0
- 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/mapGotchi.js +119 -0
- 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/training_gotchis_traits.json +520 -0
- 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/mapGotchi.js +157 -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/training_gotchis_traits.json +520 -0
- package/scripts/balancing/v1.7.2/trait_combos.json +10 -0
- package/scripts/balancing/v1.7.3/class_combos.js +44 -0
- package/scripts/balancing/v1.7.3/mapGotchi.js +164 -0
- package/scripts/balancing/v1.7.3/setTeamPositions.js +122 -0
- package/scripts/balancing/v1.7.3/training_gotchis.json +22402 -0
- package/scripts/balancing/v1.7.3/training_gotchis_traits.json +37 -0
- package/scripts/balancing/v1.7.3/trait_combos.json +10 -0
- package/scripts/data/team1.json +213 -213
- package/scripts/data/team2.json +200 -200
- package/scripts/data/tournaments.json +71 -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 +83 -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
- package/debug.log +0 -2
- package/game-logic/v1.6/debug.log +0 -1
- package/game-logic/v1.7/debug.log +0 -3
- package/scripts/data/debug.log +0 -2
- package/scripts/debug.log +0 -1
|
@@ -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.85',
|
|
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.35',
|
|
52
|
+
multiplier: 5,
|
|
53
|
+
traitKey: 3,
|
|
54
|
+
isNegative: false
|
|
55
|
+
},
|
|
56
|
+
physical: {
|
|
57
|
+
baseFormula: 'brs*0.35',
|
|
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
|
+
}
|
package/utils/transforms.js
CHANGED
|
@@ -1,89 +1,90 @@
|
|
|
1
|
-
const logToInGameTeams = (originalLog) => {
|
|
2
|
-
// Deep copy the log to avoid modifying the original log
|
|
3
|
-
const log = JSON.parse(JSON.stringify(originalLog))
|
|
4
|
-
|
|
5
|
-
const teams = [];
|
|
6
|
-
|
|
7
|
-
[0, 1].forEach((teamIndex) => {
|
|
8
|
-
teams.push({
|
|
9
|
-
formation: {
|
|
10
|
-
front: log.layout.teams[teamIndex].rows[0].slots.map((slot) => {
|
|
11
|
-
if (slot.isActive) {
|
|
12
|
-
const gotchi = log.gotchis.find((gotchi) => gotchi.id === slot.id)
|
|
13
|
-
|
|
14
|
-
if (!gotchi) {
|
|
15
|
-
throw new Error(`Gotchi not found: ${slot.id}`)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return gotchi
|
|
19
|
-
} else {
|
|
20
|
-
return null
|
|
21
|
-
}
|
|
22
|
-
}),
|
|
23
|
-
back: log.layout.teams[teamIndex].rows[1].slots.map((slot) => {
|
|
24
|
-
if (slot.isActive) {
|
|
25
|
-
const gotchi = log.gotchis.find((gotchi) => gotchi.id === slot.id)
|
|
26
|
-
|
|
27
|
-
if (!gotchi) {
|
|
28
|
-
throw new Error(`Gotchi not found: ${slot.id}`)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return gotchi
|
|
32
|
-
} else {
|
|
33
|
-
return null
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
},
|
|
37
|
-
leader: log.layout.teams[teamIndex].leaderId,
|
|
38
|
-
name: log.layout.teams[teamIndex].name,
|
|
39
|
-
owner: log.layout.teams[teamIndex].owner
|
|
40
|
-
})
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return teams
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const webappTeamToInGameTeam = (webappTeam) => {
|
|
47
|
-
const inGameTeam = {
|
|
48
|
-
formation: {
|
|
49
|
-
front: [front1Gotchi, front2Gotchi, front3Gotchi, front4Gotchi, front5Gotchi],
|
|
50
|
-
back: [back1Gotchi, back2Gotchi, back3Gotchi, back4Gotchi, back5Gotchi],
|
|
51
|
-
},
|
|
52
|
-
leader: webappTeam.leader,
|
|
53
|
-
name: webappTeam.name,
|
|
54
|
-
owner: webappTeam.owner
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
inGameTeam.formation.front.forEach(gotchi => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
1
|
+
const logToInGameTeams = (originalLog) => {
|
|
2
|
+
// Deep copy the log to avoid modifying the original log
|
|
3
|
+
const log = JSON.parse(JSON.stringify(originalLog))
|
|
4
|
+
|
|
5
|
+
const teams = [];
|
|
6
|
+
|
|
7
|
+
[0, 1].forEach((teamIndex) => {
|
|
8
|
+
teams.push({
|
|
9
|
+
formation: {
|
|
10
|
+
front: log.layout.teams[teamIndex].rows[0].slots.map((slot) => {
|
|
11
|
+
if (slot.isActive) {
|
|
12
|
+
const gotchi = log.gotchis.find((gotchi) => gotchi.id === slot.id)
|
|
13
|
+
|
|
14
|
+
if (!gotchi) {
|
|
15
|
+
throw new Error(`Gotchi not found: ${slot.id}`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return gotchi
|
|
19
|
+
} else {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
}),
|
|
23
|
+
back: log.layout.teams[teamIndex].rows[1].slots.map((slot) => {
|
|
24
|
+
if (slot.isActive) {
|
|
25
|
+
const gotchi = log.gotchis.find((gotchi) => gotchi.id === slot.id)
|
|
26
|
+
|
|
27
|
+
if (!gotchi) {
|
|
28
|
+
throw new Error(`Gotchi not found: ${slot.id}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return gotchi
|
|
32
|
+
} else {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
},
|
|
37
|
+
leader: log.layout.teams[teamIndex].leaderId,
|
|
38
|
+
name: log.layout.teams[teamIndex].name,
|
|
39
|
+
owner: log.layout.teams[teamIndex].owner
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return teams
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const webappTeamToInGameTeam = (webappTeam) => {
|
|
47
|
+
const inGameTeam = {
|
|
48
|
+
formation: {
|
|
49
|
+
front: [webappTeam.front1Gotchi, webappTeam.front2Gotchi, webappTeam.front3Gotchi, webappTeam.front4Gotchi, webappTeam.front5Gotchi],
|
|
50
|
+
back: [webappTeam.back1Gotchi, webappTeam.back2Gotchi, webappTeam.back3Gotchi, webappTeam.back4Gotchi, webappTeam.back5Gotchi],
|
|
51
|
+
},
|
|
52
|
+
leader: webappTeam.leader,
|
|
53
|
+
name: webappTeam.name,
|
|
54
|
+
owner: webappTeam.owner
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
inGameTeam.formation.front.forEach(gotchi => {
|
|
58
|
+
if (!gotchi) return
|
|
59
|
+
// remove availableSpecials
|
|
60
|
+
delete gotchi.availableSpecials
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return inGameTeam
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const inGameTeamToWebappTeam = (inGameTeam) => {
|
|
67
|
+
const webappTeam = {
|
|
68
|
+
front1Gotchi: inGameTeam.formation.front[0],
|
|
69
|
+
front2Gotchi: inGameTeam.formation.front[1],
|
|
70
|
+
front3Gotchi: inGameTeam.formation.front[2],
|
|
71
|
+
front4Gotchi: inGameTeam.formation.front[3],
|
|
72
|
+
front5Gotchi: inGameTeam.formation.front[4],
|
|
73
|
+
back1Gotchi: inGameTeam.formation.back[0],
|
|
74
|
+
back2Gotchi: inGameTeam.formation.back[1],
|
|
75
|
+
back3Gotchi: inGameTeam.formation.back[2],
|
|
76
|
+
back4Gotchi: inGameTeam.formation.back[3],
|
|
77
|
+
back5Gotchi: inGameTeam.formation.back[4],
|
|
78
|
+
leader: inGameTeam.leader,
|
|
79
|
+
name: inGameTeam.name,
|
|
80
|
+
owner: inGameTeam.owner
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return webappTeam
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
logToInGameTeams,
|
|
88
|
+
webappTeamToInGameTeam,
|
|
89
|
+
inGameTeamToWebappTeam
|
|
89
90
|
}
|
package/utils/validations.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
const { ValidationError } = require('./errors')
|
|
2
|
-
|
|
3
|
-
const compareLogs = (originalLogs, newLogs) => {
|
|
4
|
-
// Check winner, loser and numOfTurns properties
|
|
5
|
-
if (originalLogs.result.winner !== newLogs.result.winner) {
|
|
6
|
-
throw new ValidationError(`Winner mismatch: ${originalLogs.result.winner} !== ${newLogs.result.winner}`, originalLogs, newLogs)
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
if (originalLogs.result.loser !== newLogs.result.loser) {
|
|
10
|
-
throw new ValidationError(`Loser mismatch: ${originalLogs.result.loser} !== ${newLogs.result.loser}`, originalLogs, newLogs)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (originalLogs.result.numOfTurns !== newLogs.result.numOfTurns) {
|
|
14
|
-
throw new ValidationError(`numOfTurns mismatch: ${originalLogs.result.numOfTurns} !== ${newLogs.result.numOfTurns}`, originalLogs, newLogs)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Validate winningTeam array
|
|
18
|
-
originalLogs.result.winningTeam.forEach((gotchi) => {
|
|
19
|
-
// Check id, name and health properties
|
|
20
|
-
const gotchi2 = newLogs.result.winningTeam.find((gotchi2) => gotchi2.id === gotchi.id)
|
|
21
|
-
|
|
22
|
-
if (!gotchi2) {
|
|
23
|
-
throw new ValidationError(`Gotchi not found in winningTeam: ${gotchi.id}`, originalLogs, newLogs)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (gotchi.name !== gotchi2.name) {
|
|
27
|
-
throw new ValidationError(`Gotchi name mismatch: ${gotchi.name} !== ${gotchi2.name}`, originalLogs, newLogs)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (gotchi.health !== gotchi2.health) {
|
|
31
|
-
throw new ValidationError(`Gotchi health mismatch: ${gotchi.health} !== ${gotchi2.health}`, originalLogs, newLogs)
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
return true
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
module.exports = {
|
|
39
|
-
compareLogs
|
|
1
|
+
const { ValidationError } = require('./errors')
|
|
2
|
+
|
|
3
|
+
const compareLogs = (originalLogs, newLogs) => {
|
|
4
|
+
// Check winner, loser and numOfTurns properties
|
|
5
|
+
if (originalLogs.result.winner !== newLogs.result.winner) {
|
|
6
|
+
throw new ValidationError(`Winner mismatch: ${originalLogs.result.winner} !== ${newLogs.result.winner}`, originalLogs, newLogs)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (originalLogs.result.loser !== newLogs.result.loser) {
|
|
10
|
+
throw new ValidationError(`Loser mismatch: ${originalLogs.result.loser} !== ${newLogs.result.loser}`, originalLogs, newLogs)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (originalLogs.result.numOfTurns !== newLogs.result.numOfTurns) {
|
|
14
|
+
throw new ValidationError(`numOfTurns mismatch: ${originalLogs.result.numOfTurns} !== ${newLogs.result.numOfTurns}`, originalLogs, newLogs)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Validate winningTeam array
|
|
18
|
+
originalLogs.result.winningTeam.forEach((gotchi) => {
|
|
19
|
+
// Check id, name and health properties
|
|
20
|
+
const gotchi2 = newLogs.result.winningTeam.find((gotchi2) => gotchi2.id === gotchi.id)
|
|
21
|
+
|
|
22
|
+
if (!gotchi2) {
|
|
23
|
+
throw new ValidationError(`Gotchi not found in winningTeam: ${gotchi.id}`, originalLogs, newLogs)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (gotchi.name !== gotchi2.name) {
|
|
27
|
+
throw new ValidationError(`Gotchi name mismatch: ${gotchi.name} !== ${gotchi2.name}`, originalLogs, newLogs)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (gotchi.health !== gotchi2.health) {
|
|
31
|
+
throw new ValidationError(`Gotchi health mismatch: ${gotchi.health} !== ${gotchi2.health}`, originalLogs, newLogs)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
compareLogs
|
|
40
40
|
}
|
package/debug.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[1125/170833.130:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
[1125/170832.999:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
|
|
2
|
-
[1125/170833.267:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
|
|
3
|
-
[1125/170833.445:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
|
package/scripts/data/debug.log
DELETED
package/scripts/debug.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[1125/170833.969:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
|