7
\$\begingroup\$

For a little toy project of mine I need to have data available in a certain format in order to be able to feed into a Recurrent Neural Network (Long short term memory), it uses HearthStone card data that is available in JSON format.

Example input:

{
    "id":"EX1_306",
    "name":"Succubus",
    "type":"Minion",
    "faction":"Neutral",
    "rarity":"Free",
    "cost":2,
    "attack":4,
    "health":3,
    "text":"<b>Battlecry:</b> Discard a random card.",
    "flavor":"Warlocks have it pretty good.",
    "artist":"Matt Dixon",
    "collectible":true,
    "race":"Demon",
    "playerClass":"Warlock",
    "howToGet":"Unlocked at Level 1.",
    "howToGetGold":"Unlocked at Level 40.",
    "mechanics":[
        "Battlecry"
    ]
}

Example output:

Basic | Spell | Common | Paladin | Blessing of Kings | 4 | Give a minion +4/+4. <i>(+4 Attack/+4 Health)</i>
Classic | Minion | Legendary | Neutral | Dragon | Onyxia | 9 | 8 | 8 | <b>Battlecry:</b> Summon 1/1 Whelps until your side of the battlefield is full.
Basic | Minion | Free | Neutral | Beast | River Crocolisk | 2 | 2 | 3 | 
Basic | Weapon | Free | Warrior | Fiery War Axe | 2 | 3 | 2 | 
Classic | Minion | Legendary | Neutral | Pirate | Captain Greenskin | 5 | 5 | 4 | <b>Battlecry:</b> Give your weapon +1/+1.
Classic | Minion | Legendary | Neutral | None | Tinkmaster Overspark | 3 | 3 | 3 | <b>Battlecry:</b> Transform another random minion into a 5/5 Devilsaur or a 1/1 Squirrel.
Goblins vs Gnomes | Minion | Common | Neutral | Mech | Flying Machine | 3 | 1 | 4 | <b>Windfury</b>

Please note that the example input describes only a single card, while the example output describes an unrelated set of cards.

I decided to implement this in Groovy, and am looking forward to receiving lots of feedback as this is the first standalone program I've written in Groovy.

import groovy.json.JsonSlurper

import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardOpenOption

/**
 * @author Frank van Heeswijk
 */
class Main {
    static String cardDataUrl = "http://hearthstonejson.com/json/AllSets.json"
    static Path targetPath = Paths.get(System.getProperty("user.home"), "HearthStoneTrainData", "data.txt")
    static long targetFileSize = 1024L * 1024L

    static void main(String[] args) {
        saveToTargetFile(convertToOneLiners(splitIntoSets(retrieveCardData())))
    }

    static Object retrieveCardData() {
        JsonSlurper jsonSlurper = new JsonSlurper()
        jsonSlurper.parse(new URL(cardDataUrl), StandardCharsets.UTF_8.name())
    }

    static Map<String, List> splitIntoSets(Object cardsJson) {
        def map = new HashMap()
        cardsJson.each { set, cards ->
            map[set] = cards
        }
        map
    }

    static List<String> convertToOneLiners(Map<String, List> cardsBySet) {
        def list = new ArrayList()
        cardsBySet.each { set, cards ->
            cards.each { card ->
                if (card.collectible) {
                    list.add(convertToOneLiner(set, card))
                }
            }
        }
        list
    }

    static String convertToOneLiner(String set, Object cardJson) {
        def type = cardJson.type
        def rarity = cardJson.rarity ?: "Token"
        def race = cardJson.race ?: "None"
        def playerClass = cardJson.playerClass ?: "Neutral"
        def name = cardJson.name
        def cost = cardJson.cost
        def attack = cardJson.attack
        def health = cardJson.health
        def durability = cardJson.durability
        def text = cardJson.text ?: ""
        switch (type) {
            case "Minion":
                return "${set} | ${type} | ${rarity} | ${playerClass} | ${race} | ${name} | ${cost} | ${attack} | ${health} | ${text}"
            case "Spell":
                return "${set} | ${type} | ${rarity} | ${playerClass} | ${name} | ${cost} | ${text}"
            case "Enchantment":
                return "${set} | ${type} | ${playerClass} | ${text}"
            case "Weapon":
                return "${set} | ${type} | ${rarity} | ${playerClass} | ${name} | ${cost} | ${attack} | ${durability} | ${text}"
            case "Hero":
                return "${set} | ${type} | ${rarity} | ${playerClass} | ${name} | ${health}"
            case "Hero Power":
                return "${set} | ${type} | ${rarity} | ${playerClass} | ${name} | ${cost} | ${text}"
            default:
                throw new IllegalArgumentException("Unknown type: ${type} in ${cardJson}")
        }
    }

    static void saveToTargetFile(List<String> cardOneLiners) {
        def copyCardOneLiners = new ArrayList(cardOneLiners)
        Files.deleteIfExists(targetPath)
        Files.createFile(targetPath)
        def writer = Files.newBufferedWriter(targetPath, StandardCharsets.UTF_8, StandardOpenOption.APPEND)
        writer.withCloseable {
            while (targetPath.size() < targetFileSize) {
                println("Progress: ${targetPath.size()}/${targetFileSize}")
                Collections.shuffle(copyCardOneLiners)
                copyCardOneLiners.each { line ->
                    writer.append(line)
                    writer.newLine()
                }
            }
        }
    }
}
\$\endgroup\$
6
\$\begingroup\$

Looks pretty groovy to me. I only have some minor nitpicks.

Instead of def map = new HashMap(), you can write simpler as def map = [:]

Likewise, instead of def list = new ArrayList(), you can write def list = []

As of Groovy 1.8, the splitIntoSets method can be simplified with collectEntries:

static Map<String, List> splitIntoSets(Object cardsJson) {
    cardsJson.collectEntries { it }
}
|improve this answer|||||
\$\endgroup\$
4
\$\begingroup\$

Main is not a very good name for a class. Not even if it is a main class. Summarize what your code does.

Additionally, you could write this as a pure Groovy script without wrapping everything explicitly in a class (as others have done). Judging by what your main method does, it seems to be a pretty straight-forward, do A, do B, do C, do D.

saveToTargetFile(convertToOneLiners(splitIntoSets(retrieveCardData())))

When using variable interpolation you don't need to write ${variable}, simply using $variable is enough.

return "${set} | ${type} | ${rarity} | ${playerClass} | ${race} | ${name} | ${cost} | ${attack} | ${health} | ${text}"

can become:

return "$set | $type | $rarity | $playerClass | $race | $name | $cost | $attack | $health | $text"
|improve this answer|||||
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.