contract Character is ERC721Enumerable, ReentrancyGuard, Ownable {

        string[] private char = [
         "Cleric",
         "Fighter",
         "Sorcerer",
         "Magic-User",
         "Dwarf",
         "Elf",
         "Halfling"
    ];
    
    string[] private sex = [
        "Male",
        "Female"
    ];
    
    string[] private age = [
        "Child",
        "Youth",
        "Adult",
        "Mature",
        "Old",
        "Decrepit"
    ];
    
    string[] private cloth = [
        "Messy Clothing",
        "Scant Clothing",
        "Immaculate Clothing",
        "Formal Attire",
        "Threadbare Clothing",
        "Elaborate Attire",
        "Drab Clothing",
        "in Uniform"
    ];
    
    string[] private looks = [
        "Missing Limb",
        "Obese",
        "Scrawny",
        "Muscular",
        "Bald",
        "Hairy",
        "Tall",
        "Short",
        "Ugly"
    ];
    
    function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }
    
    function getChar(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "CHAR", char);
    }
    
    function getSex(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "SEX", sex);
    }
    
    function getAge(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "AGE", age);
    }
    
    function getCloth(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "CLOTH", cloth);
    }

    function getLooks(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "LOOKS", looks);
    }
    
    
    function pluck(uint256 tokenId, string memory keyPrefix, string[] memory sourceArray) internal pure returns (string memory) {
        uint256 rand = random(string(abi.encodePacked(keyPrefix, toString(tokenId))));
        string memory output = sourceArray[rand % sourceArray.length];
        return output;
    }
    
    // dice roll
    function dice(string memory input) internal pure returns (uint256) {
        return (uint256(keccak256(abi.encodePacked(input))) % 6) + 1;
    }
    
    function getStr(uint256 tokenId) public pure returns (string memory) {
        return pluckAttr(tokenId, "STR");
    }
    
    function getDex(uint256 tokenId) public pure returns (string memory) {
        return pluckAttr(tokenId, "DEX");
    }
    
    function getCon(uint256 tokenId) public pure returns (string memory) {
        return pluckAttr(tokenId, "CON");
    }
    
    function getInt(uint256 tokenId) public pure returns (string memory) {
        return pluckAttr(tokenId, "INT");
    }

    function getWis(uint256 tokenId) public pure returns (string memory) {
        return pluckAttr(tokenId, "WIS");
    }
    
    function getCha(uint256 tokenId) public pure returns (string memory) {
        return pluckAttr(tokenId, "CHA");
    }
    
    function pluckAttr(uint256 tokenId, string memory keyPrefix) internal pure returns (string memory) {
        uint256 roll1 = dice(string(abi.encodePacked(keyPrefix, toString(tokenId), "1")));
        uint256 roll2 = dice(string(abi.encodePacked(keyPrefix, toString(tokenId), "2")));
        uint256 roll3 = dice(string(abi.encodePacked(keyPrefix, toString(tokenId), "3")));
        
        // get 3 dice rolls
        uint256 stat = roll1 + roll2 + roll3;
        
        bool flag;
        string memory bonus;
        if (stat <= 3) {
            bonus = " (-3)";
            flag = true;
        } else if (stat >= 4 && stat <= 5) {
            bonus = " (-2)";
            flag = true;
        } else if (stat >= 6 && stat <= 8) {
            bonus = " (-1)";
            flag = true;
        } else if (stat >= 9 && stat <= 12) {
            bonus = "";
        } else if (stat >= 13 && stat <= 15) {
            bonus = " (1)";
        } else if (stat >= 16 && stat <= 17) {
            bonus = " (2)";
        } else {
            bonus = " (3)";
        }

        string memory output = string(abi.encodePacked(keyPrefix, ": ", toString(stat), bonus));
        return output;
    }

    function tokenURI(uint256 tokenId) override public view returns (string memory) {
        string[23] memory parts;
        parts[0] = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: Josefin Sans; font-size: 14px; }</style><rect width="100%" height="100%" fill="#090d20" /><text x="20" y="20" class="base">';

        parts[1] = getChar(tokenId);

        parts[2] = '</text><text x="20" y="60" class="base">';

        parts[3] = getSex(tokenId);

        parts[4] = '</text><text x="20" y="80" class="base">';

        parts[5] = getAge(tokenId);

        parts[6] = '</text><text x="20" y="100" class="base">';

        parts[7] = getCloth(tokenId);

        parts[8] = '</text><text x="20" y="120" class="base">';

        parts[9] = getLooks(tokenId);

        parts[10] = '</text><text x="20" y="160" class="base">';

        parts[11] = getStr(tokenId);

        parts[12] = '</text><text x="20" y="180" class="base">';

        parts[13] = getInt(tokenId);

        parts[14] = '</text><text x="20" y="200" class="base">';

        parts[15] = getWis(tokenId);
        
        parts[16] = '</text><text x="20" y="220" class="base">';

        parts[17] = getDex(tokenId);

        parts[18] = '</text><text x="20" y="240" class="base">';

        parts[19] = getCon(tokenId);

        parts[20] = '</text><text x="20" y="260" class="base">';

        parts[21] = getCha(tokenId);

        parts[22] = '</text></svg>';

        string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8]));
        output = string(abi.encodePacked(output, parts[9], parts[10], parts[11], parts[12], parts[13], parts[14], parts[15], parts[16]));
        output = string(abi.encodePacked(output, parts[17], parts[18], parts[19], parts[20], parts[21], parts[22]));
        
        string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Character #', toString(tokenId), '", "description": "Character for D&D is randomized generated and stored on chain. Skills, equipment, and other functionality are intentionally omitted for others to interpret. Feel free to use Character in any way you want.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
        output = string(abi.encodePacked('data:application/json;base64,', json));

        return output;
    }

    function claim(uint256 tokenId) public nonReentrant {
        require(tokenId > 0 && tokenId < 7778, "Token ID invalid");
        _safeMint(_msgSender(), tokenId);
    }
    
    function ownerClaim(uint256 tokenId) public nonReentrant onlyOwner {
        require(tokenId > 7777 && tokenId < 8001, "Token ID invalid");
        _safeMint(owner(), tokenId);
    }
    
    function toString(uint256 value) internal pure returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT license
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
    
    constructor() ERC721("Character", "DNDCHAR") Ownable() {}
}
function dice(string memory input) internal pure returns (uint256) {
    return (uint256(keccak256(abi.encodePacked(input))) % 6) + 1;
}

// dice roll