Skip to content

Commit d1c7c4e

Browse files
ShouqyanShouqyan
Shouqyan
authored and
Shouqyan
committed
#12 allow defining specific percentages for rarity/layer combinations for more expressiveness in NFT combination
1 parent b95526e commit d1c7c4e

File tree

3 files changed

+167
-84
lines changed

3 files changed

+167
-84
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,9 @@ To start, copy the layers/features and their images in a flat hierarchy at a dir
3232

3333
After adding the `layers`, adjust them accordingly in the `config.js` by providing the directory path, positioning and sizes.
3434

35+
### Allowing different rarities for certain rarity/layer combinations
36+
It is possible to provide a percentage at which e.g. a rare item would contain a rare vs. common part in a given layer. This can be done via the `addRarityPercentForLayer` that can be found in the `config.js` as well.
37+
This allows for more fine grained control over how much randomness there should be during the generation process, and allows a combination of common and rare parts.
38+
3539
# Development suggestions
3640
- Preferably use VSCode with the prettifier plugin for a consistent coding style (or equivalent js formatting rules)

index.js

+25-4
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,14 @@ const drawElement = (_element) => {
9292
// drawing on a canvas
9393
const constructLayerToDna = (_dna = [], _layers = [], _rarity) => {
9494
let mappedDnaToLayers = _layers.map((layer, index) => {
95-
let selectedElement = layer.elements[_rarity][_dna[index]];
95+
let selectedElement = layer.elements.find(element => element.id === _dna[index]);
9696
return {
9797
location: layer.location,
9898
position: layer.position,
9999
size: layer.size,
100100
selectedElement: selectedElement,
101101
};
102102
});
103-
104103
return mappedDnaToLayers;
105104
};
106105

@@ -111,13 +110,35 @@ const isDnaUnique = (_DnaList = [], _dna = []) => {
111110
return foundDna == undefined ? true : false;
112111
};
113112

113+
const getRandomRarity = (_rarityOptions) => {
114+
let randomPercent = Math.random() * 100;
115+
let percentCount = 0;
116+
117+
for (let i = 0; i <= _rarityOptions.length; i++) {
118+
percentCount += _rarityOptions[i].percent;
119+
if (percentCount >= randomPercent) {
120+
console.log(`use random rarity ${_rarityOptions[i].id}`)
121+
return _rarityOptions[i].id;
122+
}
123+
}
124+
return _rarityOptions[0].id;
125+
}
126+
114127
// create a dna based on the available layers for the given rarity
115128
// use a random part for each layer
116129
const createDna = (_layers, _rarity) => {
117130
let randNum = [];
131+
let _rarityWeight = rarityWeights.find(rw => rw.value === _rarity);
118132
_layers.forEach((layer) => {
119-
let num = Math.floor(Math.random() * layer.elements[_rarity].length);
120-
randNum.push(num);
133+
let num = Math.floor(Math.random() * layer.elementIdsForRarity[_rarity].length);
134+
if (_rarityWeight && _rarityWeight.layerPercent[layer.id]) {
135+
// if there is a layerPercent defined, we want to identify which dna to actually use here (instead of only picking from the same rarity)
136+
let _rarityForLayer = getRandomRarity(_rarityWeight.layerPercent[layer.id]);
137+
num = Math.floor(Math.random() * layer.elementIdsForRarity[_rarityForLayer].length);
138+
randNum.push(layer.elementIdsForRarity[_rarityForLayer][num]);
139+
} else {
140+
randNum.push(layer.elementIdsForRarity[_rarity][num]);
141+
}
121142
});
122143
return randNum;
123144
};

input/config.js

+138-80
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,161 @@
1+
/**************************************************************
2+
* UTILITY FUNCTIONS
3+
* - scroll to BEGIN CONFIG to provide the config values
4+
*************************************************************/
15
const fs = require("fs");
2-
const width = 1000;
3-
const height = 1000;
46
const dir = __dirname;
5-
const description = "This is an NFT made by the coolest generative code.";
6-
const baseImageUri = "https://hashlips/nft";
7-
const startEditionFrom = 1;
8-
const editionSize = 10;
9-
const rarityWeights = [
10-
{
11-
value: "super_rare",
12-
from: 1,
13-
to: 1,
14-
},
15-
{
16-
value: "rare",
17-
from: 2,
18-
to: 5,
19-
},
20-
{
21-
value: "original",
22-
from: 5,
23-
to: editionSize,
24-
},
25-
];
267

8+
// adds a rarity to the configuration. This is expected to correspond with a directory containing the rarity for each defined layer
9+
// @param _id - id of the rarity
10+
// @param _from - number in the edition to start this rarity from
11+
// @param _to - number in the edition to generate this rarity to
12+
// @return a rarity object used to dynamically generate the NFTs
13+
const addRarity = (_id, _from, _to) => {
14+
const _rarityWeight = {
15+
value: _id,
16+
from: _from,
17+
to: _to,
18+
layerPercent: {}
19+
};
20+
return _rarityWeight;
21+
};
22+
23+
// get the name without last 4 characters -> slice .png from the name
2724
const cleanName = (_str) => {
2825
let name = _str.slice(0, -4);
2926
return name;
3027
};
3128

32-
const getElements = (path) => {
29+
// reads the filenames of a given folder and returns it with its name and path
30+
const getElements = (_path, _elementCount) => {
3331
return fs
34-
.readdirSync(path)
32+
.readdirSync(_path)
3533
.filter((item) => !/(^|\/)\.[^\/\.]/g.test(item))
3634
.map((i) => {
3735
return {
36+
id: _elementCount,
3837
name: cleanName(i),
39-
path: `${path}/${i}`,
38+
path: `${_path}/${i}`
4039
};
4140
});
4241
};
4342

43+
// adds a layer to the configuration. The layer will hold information on all the defined parts and
44+
// where they should be rendered in the image
45+
// @param _id - id of the layer
46+
// @param _position - on which x/y value to render this part
47+
// @param _size - of the image
48+
// @return a layer object used to dynamically generate the NFTs
49+
const addLayer = (_id, _position, _size) => {
50+
if (!_id) {
51+
console.log('error adding layer, parameters id required');
52+
return null;
53+
}
54+
if (!_position) {
55+
_position = { x: 0, y: 0 };
56+
}
57+
if (!_size) {
58+
_size = { width: width, height: height }
59+
}
60+
// add two different dimension for elements:
61+
// - all elements with their path information
62+
// - only the ids mapped to their rarity
63+
let elements = [];
64+
let elementCount = 0;
65+
let elementIdsForRarity = {};
66+
rarityWeights.forEach((rarityWeight) => {
67+
let elementsForRarity = getElements(`${dir}/${_id}/${rarityWeight.value}`);
68+
69+
elementIdsForRarity[rarityWeight.value] = [];
70+
elementsForRarity.forEach((_elementForRarity) => {
71+
_elementForRarity.id = `${editionDnaPrefix}${elementCount}`;
72+
elements.push(_elementForRarity);
73+
elementIdsForRarity[rarityWeight.value].push(_elementForRarity.id);
74+
elementCount++;
75+
})
76+
elements[rarityWeight.value] = elementsForRarity;
77+
});
78+
79+
let elementsForLayer = {
80+
id: _id,
81+
position: _position,
82+
size: _size,
83+
elements,
84+
elementIdsForRarity
85+
};
86+
return elementsForLayer;
87+
};
88+
89+
// adds layer-specific percentages to use one vs another rarity
90+
// @param _rarityId - the id of the rarity to specifiy
91+
// @param _layerId - the id of the layer to specifiy
92+
// @param _percentages - an object defining the rarities and the percentage with which a given rarity for this layer should be used
93+
const addRarityPercentForLayer = (_rarityId, _layerId, _percentages) => {
94+
let _rarityFound = false;
95+
rarityWeights.forEach((_rarityWeight) => {
96+
if (_rarityWeight.value === _rarityId) {
97+
let _percentArray = [];
98+
for (let percentType in _percentages) {
99+
_percentArray.push({
100+
id: percentType,
101+
percent: _percentages[percentType]
102+
})
103+
}
104+
_rarityWeight.layerPercent[_layerId] = _percentArray;
105+
_rarityFound = true;
106+
}
107+
});
108+
if (!_rarityFound) {
109+
console.log(`rarity ${_rarityId} not found, failed to add percentage information`);
110+
}
111+
}
112+
113+
/**************************************************************
114+
* BEGIN CONFIG
115+
*************************************************************/
116+
117+
// image width in pixels
118+
const width = 1000;
119+
// image height in pixels
120+
const height = 1000;
121+
// description for NFT in metadata file
122+
const description = "This is an NFT made by the coolest generative code.";
123+
// base url to use in metadata file
124+
// the id of the nft will be added to this url, in the example e.g. https://hashlips/nft/1 for NFT with id 1
125+
const baseImageUri = "https://hashlips/nft";
126+
// id for edition to start from
127+
const startEditionFrom = 1;
128+
// amount of NFTs to generate in edition
129+
const editionSize = 10;
130+
// prefix to add to edition dna ids (to distinguish dna counts from different generation processes for the same collection)
131+
const editionDnaPrefix = 0
132+
133+
// create required weights
134+
// for each weight, call 'addRarity' with the id and from which to which element this rarity should be applied
135+
let rarityWeights = [
136+
addRarity('super_rare', 1, 1),
137+
addRarity('rare', 2, 5),
138+
addRarity('original', 5, 10)
139+
];
140+
141+
// create required layers
142+
// for each layer, call 'addLayer' with the id and optionally the positioning and size
143+
// the id would be the name of the folder in your input directory, e.g. 'ball' for ./input/ball
44144
const layers = [
45-
{
46-
elements: {
47-
original: getElements(`${dir}/ball/original`),
48-
rare: getElements(`${dir}/ball/rare`),
49-
super_rare: getElements(`${dir}/ball/super_rare`),
50-
},
51-
position: { x: 0, y: 0 },
52-
size: { width: width, height: height },
53-
},
54-
{
55-
elements: {
56-
original: getElements(`${dir}/eye color/original`),
57-
rare: getElements(`${dir}/eye color/rare`),
58-
super_rare: getElements(`${dir}/eye color/super_rare`),
59-
},
60-
position: { x: 0, y: 0 },
61-
size: { width: width, height: height },
62-
},
63-
{
64-
elements: {
65-
original: getElements(`${dir}/iris/original`),
66-
rare: getElements(`${dir}/iris/rare`),
67-
super_rare: getElements(`${dir}/iris/super_rare`),
68-
},
69-
position: { x: 0, y: 0 },
70-
size: { width: width, height: height },
71-
},
72-
{
73-
elements: {
74-
original: getElements(`${dir}/shine/original`),
75-
rare: getElements(`${dir}/shine/rare`),
76-
super_rare: getElements(`${dir}/shine/super_rare`),
77-
},
78-
position: { x: 0, y: 0 },
79-
size: { width: width, height: height },
80-
},
81-
{
82-
elements: {
83-
original: getElements(`${dir}/bottom lid/original`),
84-
rare: getElements(`${dir}/bottom lid/rare`),
85-
super_rare: getElements(`${dir}/bottom lid/super_rare`),
86-
},
87-
position: { x: 0, y: 0 },
88-
size: { width: width, height: height },
89-
},
90-
{
91-
elements: {
92-
original: getElements(`${dir}/top lid/original`),
93-
rare: getElements(`${dir}/top lid/rare`),
94-
super_rare: getElements(`${dir}/top lid/super_rare`),
95-
},
96-
position: { x: 0, y: 0 },
97-
size: { width: width, height: height },
98-
},
145+
addLayer('ball'),
146+
addLayer('eye color'),
147+
addLayer('iris'),
148+
addLayer('shine'),
149+
addLayer('bottom lid'),
150+
addLayer('top lid')
99151
];
100152

153+
// provide any specific percentages that are required for a given layer and rarity level
154+
// all provided options are used based on their percentage values to decide which layer to select from
155+
addRarityPercentForLayer('super_rare', 'ball', { 'super_rare': 33, 'rare': 33, 'original': 33 });
156+
addRarityPercentForLayer('super_rare', 'eye color', { 'super_rare': 50, 'rare': 25, 'original': 25 });
157+
addRarityPercentForLayer('original', 'eye color', { 'super_rare': 50, 'rare': 25, 'original': 25 });
158+
101159
module.exports = {
102160
layers,
103161
width,

0 commit comments

Comments
 (0)