Skip to content

Commit

Permalink
Merge pull request #9 from ClickPop/feat/refactor-stats
Browse files Browse the repository at this point in the history
Adds "stat agnostic" functionality
  • Loading branch information
seanmetzgar authored Sep 13, 2021
2 parents 84fa682 + 800a748 commit 051efe9
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 75 deletions.
89 changes: 78 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,97 @@ The configuration schema is as follow (WIP)

```json
{
"piece-order": ["background", "foreground"] /** Array of strings representing the piece names for layering order. */,
"filename": "%s-%s.png" /** Format string to use for loading the individual assets. You will need a %s for each sub-category of assets */,
"pathname": "pieces" /** The pathname where your input files can be found */,
"output-directory": "rats" /** Output directory where generated assets will be stored */,
"output-image-count": 10 /** How many images to generate */,
"max-workers": 3 /** The number of workers to run. Generally you want to keep this below like 6 or 7 unless you have a really beefy machine, as generating can use a lot of memory depending on the number of layers */,
"attributes": /** Object of key value pairs that signify your different pieces and their attributes */ {
"input": { /* Object: key-value pairs that represent the input settings */
"local": {
"filename": "%s-%s.png", /* string: Format string to use for loading the individual assets. You will need a %s for each sub-category of assets */
"pathname": "pieces" /* string: Pathname where input files can be found */
}
},
"output": { /* Object: key-value pairs that represent the input settings */
"local": { "directory": "output" }, /* WIP: Object: Local Settings */
"internal": true, /* WIP: bool: Use internal output (for sending to NFT Minting tool) */
"image-count": 1 /* int: Number of images to generate */
},
"settings": {
"max-workers": 3, /* int: Number of workers to run. Generally you want to keep this below like 6 or 7 unless you have a really beefy machine, as generating can use a lot of memory depending on the number of layers */
"piece-order": ["background", "foreground"], /* []string: Represents the piece names for layering order (bottom to top) */
"stats": { /* Object: key-value pairs that represent the stat settings */
"cunning": { /* string: Object key is the stat "id" */
"minimum": 0, /* int: Prevents the stat from falling below a given value */
"maximum": 7, /* int: Prevents the stat from exceeding a given value */
"name": "Cunning" /* string: Friendly name for stat */
},
"cuteness": {
"minimum": 0,
"maximum": 7,
"name": "Cuteness"
},
"rattitude": {
"minimum": 0,
"maximum": 7,
"name": "Rattitude"
}
}
},
"attributes": { /** WIP: Object: key-value pairs that signify your different pieces and their attributes */
"background": {
"dark": {
"rarity": 6
"rarity": 6,
"rattitude": 2,
"cunning": 1,
"cuteness": -1
},
"light": {
"rarity": 4
"rarity": 4,
"cunning": 2,
"rattitude": -1
}
},
"foreground": {
"pink": {
"rarity": 3,
"cuteness": 2
},
"green": {
"rarity": 4,
"cuteness": 1
"cunning": 1
}
}
},
"descriptions": { /** WIP: Object: key-value pairs to be used by the description generator */
"template": "This little rat is a %s, that means %s. Their favorite hobbies include %s.", /* string: Format string to use as template for description generator */
"hobbies-count": 3, /* int: Number of random hobbies to select */
"fallback-primary-stat": "fallback", /* string: refrences the fallback type in the below "types" object when there is no primary stat */
"types": { /* Object: key-value pairs establishing "types" based on the dominant stat. Use the stat "id" as the key */
"cunning": { /* string: id of primary (dominant) stat to utilize for this type */
"id": "lab-rat", /* string: id for this type */
"name": "Lab Rat", /* string: Friendly name for this type */
"descriptors": [ /* []string: Array of descriptors to be randomly selected by the description generator */
"they’re constantly tinkering with things they probably shouldn’t",
"they're smarter than the average rat",
"they're as interested in where cheese is as why cheese is"
],
"hobbies": [ /* []string: Array of hobbies to be randomly selected by the description generator, utilizes */
"reading scraps of thrown out books",
"naming new constellations",
"pondering"
]
},
"fallback": { /** See fallback-primary-stat above **/
"id": "pack-rat",
"name": "Pack Rat",
"descriptors": [
"they have a trash stash that would make you green with envy",
"their motto is be prepared",
"they always have a ketchup packet on hand"
],
"hobbies": [
"hoarding",
"tossing back cookies",
"digging through old computer towers for spare parts"
]
}
}
}
}
}
```
124 changes: 73 additions & 51 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,31 @@ import (
var wg sync.WaitGroup

type Metadata struct {
Piece string `json:"piece"`
Type string `json:"type"`
Piece string `json:"piece"`
Type string `json:"type"`
Attributes map[string]interface{} `json:"attributes"`
}

type OpenSeaMeta struct {
Image string `json:"image,omitempty"`
ImageData string `json:"image_data,omitempty"`
ExternalURL string `json:"external_url,omitempty"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
Attributes []OpenSeaAttribute `json:"attributes,omitempty"`
BackgroundColor string `json:"background_color,omitempty"`
AnimationURL string `json:"animation_url,omitempty"`
YouTubeURL string `json:"youtube_url,omitempty"`
Image string `json:"image,omitempty"`
ImageData string `json:"image_data,omitempty"`
ExternalURL string `json:"external_url,omitempty"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
Attributes []OpenSeaAttribute `json:"attributes,omitempty"`
BackgroundColor string `json:"background_color,omitempty"`
AnimationURL string `json:"animation_url,omitempty"`
YouTubeURL string `json:"youtube_url,omitempty"`
}

type OpenSeaAttribute struct {
TraitType string `json:"trait_type,omitempty"`
DisplayType string `json:"display_type,omitempty"`
Value interface{} `json:"value,omitempty"`
TraitType string `json:"trait_type,omitempty"`
DisplayType string `json:"display_type,omitempty"`
Value interface{} `json:"value,omitempty"`
}
type GeneratedRat struct {
Image *bytes.Buffer
Meta *bytes.Buffer
Meta *bytes.Buffer
}

func Generate(config *conf.Config, hashCheckCb func(config *conf.Config)) ([]GeneratedRat, error) {
Expand All @@ -60,7 +60,7 @@ func Generate(config *conf.Config, hashCheckCb func(config *conf.Config)) ([]Gen
if (config.Output == conf.OutputObject{}) {
config.Output.Internal = true
}

_, err := os.Stat(outputDir)
if os.IsNotExist(err) {
os.Mkdir(outputDir, 0777)
Expand All @@ -70,7 +70,7 @@ func Generate(config *conf.Config, hashCheckCb func(config *conf.Config)) ([]Gen

image_count := int(config.Output.ImageCount)

if (image_count == 0) {
if image_count == 0 {
image_count = 10
config.Output.ImageCount = float64(image_count)
}
Expand Down Expand Up @@ -113,8 +113,12 @@ func Generate(config *conf.Config, hashCheckCb func(config *conf.Config)) ([]Gen
}

func makeFile(config *conf.Config, jobs <-chan int, results chan<- GeneratedRat, errChan chan<- error) {
stats := make(map[string]int)
for k := range config.Settings.Stats {
stats[k] = 0
}

for job := range jobs {
log.Println(len(jobs))
i := job
log.Printf("Loading files for image #%d\n", i)
files, metadata, err := loadFiles(config)
Expand Down Expand Up @@ -142,16 +146,21 @@ func makeFile(config *conf.Config, jobs <-chan int, results chan<- GeneratedRat,
imageOut := new(bytes.Buffer)
metaOut := new(bytes.Buffer)
var finalMeta OpenSeaMeta
finalMeta.Attributes = make([]OpenSeaAttribute, 0);
attributes := make(map[string]int, 0)
finalMeta.Attributes = make([]OpenSeaAttribute, 0)
attributes := make(map[string]int)
for j := 0; j < len(metadata); j++ {
currMeta := metadata[j]
finalMeta.Attributes = append(finalMeta.Attributes, OpenSeaAttribute{TraitType: currMeta.Type, Value: currMeta.Piece})
for k, v := range currMeta.Attributes {
if k != "rarity" {
switch t := v.(type) {
case float64:
attributes[k] += int(t)
case float64:
attributes[k] += int(t)
if attributes[k] >= config.Settings.Stats[k].Maximum {
attributes[k] = config.Settings.Stats[k].Maximum
} else if attributes[k] <= config.Settings.Stats[k].Minimum {
attributes[k] = config.Settings.Stats[k].Minimum
}
}
}
}
Expand Down Expand Up @@ -192,7 +201,7 @@ func makeFile(config *conf.Config, jobs <-chan int, results chan<- GeneratedRat,
wg.Done()
config.Output.ImageCount -= 1
}
if (config.Output.ImageCount == 0) {
if config.Output.ImageCount == 0 {
close(results)
close(errChan)
}
Expand Down Expand Up @@ -288,35 +297,23 @@ func checkHashes(outputDir string) error {
}

func buildDescription(c *conf.Config, meta OpenSeaMeta) string {
primaryStat := "default"

cunning := 0
cuteness := 0
rattitude := 0
stats := make(map[string]int)
for k := range c.Settings.Stats {
stats[k] = 0
}

for _, v := range meta.Attributes {
switch v.TraitType {
case "cuteness":
cuteness += v.Value.(int)
case "cunning":
cunning += v.Value.(int)
case "rattitude":
rattitude += v.Value.(int)
}
if _, isStat := stats[v.TraitType]; isStat {
stats[v.TraitType] += v.Value.(int)
}
}

if cunning > cuteness && cunning > rattitude {
primaryStat = "cunning"
} else if cuteness > cunning && cuteness > rattitude {
primaryStat = "cuteness"
} else if rattitude > cunning && rattitude > cuteness {
primaryStat = "rattitude"
}
randomDescriptor := getRandomDescriptor(c.DescriptionData[primaryStat].Descriptors)
randomHobbies := getRandomHobbies(c.DescriptionData[primaryStat].Hobbies, 3)
ratType := c.DescriptionData[primaryStat].Name
primaryStat := getPrimaryStat(stats, c.Descriptions.FallbackPrimaryStat)
randomDescriptor := getRandomDescriptor(c.Descriptions.Types[primaryStat].Descriptors)
randomHobbies := getRandomHobbies(c.Descriptions.Types[primaryStat].Hobbies, c.Descriptions.HobbiesCount)
currentType := c.Descriptions.Types[primaryStat].Name

return fmt.Sprintf("This little rat is a %s, that means %s. Their favorite hobbies include %s.", ratType, randomDescriptor, randomHobbies)
return fmt.Sprintf(c.Descriptions.Template, currentType, randomDescriptor, randomHobbies)
}

func getRandomDescriptor(descriptors []string) string {
Expand Down Expand Up @@ -344,14 +341,39 @@ func getRandomHobbies(hobbies []string, n int) string {

func oxfordJoin(slice []string) string {
outStr := ""
complex := false
punct := ","

for _, v := range slice {
if strings.Contains(v, ",") {
complex = true
punct = ";"
}
}

if len(slice) == 1 {
outStr = slice[0]
} else if len(slice) == 2 {
} else if len(slice) == 2 && !complex {
outStr = strings.Join(slice, " and ")
} else if len(slice) > 2 {
outStr = strings.Join(slice[0:(len(slice)-1)], ", ") + ", and " + slice[len(slice)-1]
} else if len(slice) > 2 || (len(slice) == 2 && complex) {
outStr = strings.Join(slice[0:(len(slice)-1)], fmt.Sprintf("%s ", punct)) + fmt.Sprintf("%s and ", punct) + slice[len(slice)-1]
}

return outStr
}
}

func getPrimaryStat(stats map[string]int, fallbackPrimaryStat string) string {
max := -(int(^uint(0) >> 1)) - 1
primaryStat := fallbackPrimaryStat

for stat, v := range stats {
if v > max {
primaryStat = stat
max = v
} else if v == max {
primaryStat = fallbackPrimaryStat
}
}

return primaryStat
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/clickpop/looks

go 1.16
go 1.16
37 changes: 25 additions & 12 deletions utils/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
)

type Config struct {
Input InputObject `json:"input,omitempty"`
Output OutputObject `json:"output,omitempty"`
Settings ConfigSettings `json:"setting,omitempty"`
Attributes map[string]map[string]map[string]interface{} `json:"attributes,omitempty"`
DescriptionData map[string]DescriptionData `json:"description-data,omitempty"`
Input InputObject `json:"input,omitempty"`
Output OutputObject `json:"output,omitempty"`
Settings ConfigSettings `json:"settings,omitempty"`
Attributes map[string]map[string]map[string]interface{} `json:"attributes,omitempty"`
Descriptions ConfigDescriptions `json:"descriptions,omitempty"`
}
type InputObject struct {
Local InputLocalObject `json:"local,omitempty"`
Expand All @@ -22,27 +22,40 @@ type InputLocalObject struct {
}

type OutputObject struct {
Local OutputLocalObject `json:"local,omitempty"`
Internal bool `json:"internal,omitempty"`
ImageCount float64 `json:"image-count,omitempty"`
Local OutputLocalObject `json:"local,omitempty"`
Internal bool `json:"internal,omitempty"`
ImageCount float64 `json:"image-count,omitempty"`
}

type OutputLocalObject struct {
Directory string `json:"directory,omitempty"`
}

type ConfigSettings struct {
PieceOrder []string `json:"piece-order,omitempty"`
MaxWorkers float64 `json:"max-workers,omitempty"`
PieceOrder []string `json:"piece-order,omitempty"`
Stats map[string]ConfigStats `json:"stats,omitempty"`
MaxWorkers float64 `json:"max-workers,omitempty"`
}

type DescriptionData struct {
type ConfigDescriptions struct {
Template string `json:"template,omitempty"`
FallbackPrimaryStat string `json:"fallback-primary-stat,omitempty"`
HobbiesCount int `json:"hobbies-count,omitempty"`
Types map[string]ConfigDescriptionTypes `json:"types,omitempty"`
}
type ConfigDescriptionTypes struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Descriptors []string `json:"descriptors,omitempty"`
Hobbies []string `json:"hobbies,omitempty"`
}

type ConfigStats struct {
Name string `json:"name,omitempty"`
Minimum int `json:"minimum,omitempty"`
Maximum int `json:"maximum,omitempty"`
}

func LoadConfig(path string) (Config, error) {
var config Config
filePath := path
Expand All @@ -58,4 +71,4 @@ func LoadConfig(path string) (Config, error) {
return config, err
}
return config, nil
}
}

0 comments on commit 051efe9

Please sign in to comment.