Skip to content

Instantly share code, notes, and snippets.

@cleancoindev
Forked from adrianhajdin/AvaxGods.sol
Created May 28, 2023 01:29
Show Gist options
  • Save cleancoindev/38eb785e04887c961ac0a32c8344e3b4 to your computer and use it in GitHub Desktop.
Save cleancoindev/38eb785e04887c961ac0a32c8344e3b4 to your computer and use it in GitHub Desktop.

Revisions

  1. @adrianhajdin adrianhajdin revised this gist Oct 30, 2022. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions emptyAccount.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    const emptyAccount = '0x0000000000000000000000000000000000000000';
  2. @adrianhajdin adrianhajdin created this gist Oct 28, 2022.
    507 changes: 507 additions & 0 deletions AvaxGods.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,507 @@
    // SPDX-License-Identifier: MIT

    pragma solidity ^0.8.16;

    import '@openzeppelin/contracts/token/ERC1155/ERC1155.sol';
    import '@openzeppelin/contracts/access/Ownable.sol';
    import '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol';

    /// @title AVAXGods
    /// @notice This contract handles the token management and battle logic for the AVAXGods game
    /// @notice Version 1.0.0
    /// @author Ava-Labs
    /// @author Julian Martinez
    /// @author Gabriel Cardona
    /// @author Raj Ranjan

    contract AVAXGods is ERC1155, Ownable, ERC1155Supply {
    string public baseURI; // baseURI where token metadata is stored
    uint256 public totalSupply; // Total number of tokens minted
    uint256 public constant DEVIL = 0;
    uint256 public constant GRIFFIN = 1;
    uint256 public constant FIREBIRD = 2;
    uint256 public constant KAMO = 3;
    uint256 public constant KUKULKAN = 4;
    uint256 public constant CELESTION = 5;

    uint256 public constant MAX_ATTACK_DEFEND_STRENGTH = 10;

    enum BattleStatus{ PENDING, STARTED, ENDED }

    /// @dev GameToken struct to store player token info
    struct GameToken {
    string name; /// @param name battle card name; set by player
    uint256 id; /// @param id battle card token id; will be randomly generated
    uint256 attackStrength; /// @param attackStrength battle card attack; generated randomly
    uint256 defenseStrength; /// @param defenseStrength battle card defense; generated randomly
    }

    /// @dev Player struct to store player info
    struct Player {
    address playerAddress; /// @param playerAddress player wallet address
    string playerName; /// @param playerName player name; set by player during registration
    uint256 playerMana; /// @param playerMana player mana; affected by battle results
    uint256 playerHealth; /// @param playerHealth player health; affected by battle results
    bool inBattle; /// @param inBattle boolean to indicate if a player is in battle
    }

    /// @dev Battle struct to store battle info
    struct Battle {
    BattleStatus battleStatus; /// @param battleStatus enum to indicate battle status
    bytes32 battleHash; /// @param battleHash a hash of the battle name
    string name; /// @param name battle name; set by player who creates battle
    address[2] players; /// @param players address array representing players in this battle
    uint8[2] moves; /// @param moves uint array representing players' move
    address winner; /// @param winner winner address
    }

    mapping(address => uint256) public playerInfo; // Mapping of player addresses to player index in the players array
    mapping(address => uint256) public playerTokenInfo; // Mapping of player addresses to player token index in the gameTokens array
    mapping(string => uint256) public battleInfo; // Mapping of battle name to battle index in the battles array

    Player[] public players; // Array of players
    GameToken[] public gameTokens; // Array of game tokens
    Battle[] public battles; // Array of battles

    function isPlayer(address addr) public view returns (bool) {
    if(playerInfo[addr] == 0) {
    return false;
    } else {
    return true;
    }
    }

    function getPlayer(address addr) public view returns (Player memory) {
    require(isPlayer(addr), "Player doesn't exist!");
    return players[playerInfo[addr]];
    }

    function getAllPlayers() public view returns (Player[] memory) {
    return players;
    }

    function isPlayerToken(address addr) public view returns (bool) {
    if(playerTokenInfo[addr] == 0) {
    return false;
    } else {
    return true;
    }
    }

    function getPlayerToken(address addr) public view returns (GameToken memory) {
    require(isPlayerToken(addr), "Game token doesn't exist!");
    return gameTokens[playerTokenInfo[addr]];
    }

    function getAllPlayerTokens() public view returns (GameToken[] memory) {
    return gameTokens;
    }

    // Battle getter function
    function isBattle(string memory _name) public view returns (bool) {
    if(battleInfo[_name] == 0) {
    return false;
    } else {
    return true;
    }
    }

    function getBattle(string memory _name) public view returns (Battle memory) {
    require(isBattle(_name), "Battle doesn't exist!");
    return battles[battleInfo[_name]];
    }

    function getAllBattles() public view returns (Battle[] memory) {
    return battles;
    }

    function updateBattle(string memory _name, Battle memory _newBattle) private {
    require(isBattle(_name), "Battle doesn't exist");
    battles[battleInfo[_name]] = _newBattle;
    }

    // Events
    event NewPlayer(address indexed owner, string name);
    event NewBattle(string battleName, address indexed player1, address indexed player2);
    event BattleEnded(string battleName, address indexed winner, address indexed loser);
    event BattleMove(string indexed battleName, bool indexed isFirstMove);
    event NewGameToken(address indexed owner, uint256 id, uint256 attackStrength, uint256 defenseStrength);
    event RoundEnded(address[2] damagedPlayers);

    /// @dev Initializes the contract by setting a `metadataURI` to the token collection
    /// @param _metadataURI baseURI where token metadata is stored
    constructor(string memory _metadataURI) ERC1155(_metadataURI) {
    baseURI = _metadataURI; // Set baseURI
    initialize();
    }

    function setURI(string memory newuri) public onlyOwner {
    _setURI(newuri);
    }

    function initialize() private {
    gameTokens.push(GameToken("", 0, 0, 0));
    players.push(Player(address(0), "", 0, 0, false));
    battles.push(Battle(BattleStatus.PENDING, bytes32(0), "", [address(0), address(0)], [0, 0], address(0)));
    }

    /// @dev Registers a player
    /// @param _name player name; set by player
    function registerPlayer(string memory _name, string memory _gameTokenName) external {
    require(!isPlayer(msg.sender), "Player already registered"); // Require that player is not already registered

    uint256 _id = players.length;
    players.push(Player(msg.sender, _name, 10, 25, false)); // Adds player to players array
    playerInfo[msg.sender] = _id; // Creates player info mapping

    createRandomGameToken(_gameTokenName);

    emit NewPlayer(msg.sender, _name); // Emits NewPlayer event
    }

    /// @dev internal function to generate random number; used for Battle Card Attack and Defense Strength
    function _createRandomNum(uint256 _max, address _sender) internal view returns (uint256 randomValue) {
    uint256 randomNum = uint256(keccak256(abi.encodePacked(block.difficulty, block.timestamp, _sender)));

    randomValue = randomNum % _max;
    if(randomValue == 0) {
    randomValue = _max / 2;
    }

    return randomValue;
    }

    /// @dev internal function to create a new Battle Card
    function _createGameToken(string memory _name) internal returns (GameToken memory) {
    uint256 randAttackStrength = _createRandomNum(MAX_ATTACK_DEFEND_STRENGTH, msg.sender);
    uint256 randDefenseStrength = MAX_ATTACK_DEFEND_STRENGTH - randAttackStrength;

    uint8 randId = uint8(uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender))) % 100);
    randId = randId % 6;
    if (randId == 0) {
    randId++;
    }

    GameToken memory newGameToken = GameToken(
    _name,
    randId,
    randAttackStrength,
    randDefenseStrength
    );

    uint256 _id = gameTokens.length;
    gameTokens.push(newGameToken);
    playerTokenInfo[msg.sender] = _id;

    _mint(msg.sender, randId, 1, '0x0');
    totalSupply++;

    emit NewGameToken(msg.sender, randId, randAttackStrength, randDefenseStrength);
    return newGameToken;
    }

    /// @dev Creates a new game token
    /// @param _name game token name; set by player
    function createRandomGameToken(string memory _name) public {
    require(!getPlayer(msg.sender).inBattle, "Player is in a battle"); // Require that player is not already in a battle
    require(isPlayer(msg.sender), "Please Register Player First"); // Require that the player is registered

    _createGameToken(_name); // Creates game token
    }

    function getTotalSupply() external view returns (uint256) {
    return totalSupply;
    }

    /// @dev Creates a new battle
    /// @param _name battle name; set by player
    function createBattle(string memory _name) external returns (Battle memory) {
    require(isPlayer(msg.sender), "Please Register Player First"); // Require that the player is registered
    require(!isBattle(_name), "Battle already exists!"); // Require battle with same name should not exist

    bytes32 battleHash = keccak256(abi.encode(_name));

    Battle memory _battle = Battle(
    BattleStatus.PENDING, // Battle pending
    battleHash, // Battle hash
    _name, // Battle name
    [msg.sender, address(0)], // player addresses; player 2 empty until they joins battle
    [0, 0], // moves for each player
    address(0) // winner address; empty until battle ends
    );

    uint256 _id = battles.length;
    battleInfo[_name] = _id;
    battles.push(_battle);

    return _battle;
    }

    /// @dev Player joins battle
    /// @param _name battle name; name of battle player wants to join
    function joinBattle(string memory _name) external returns (Battle memory) {
    Battle memory _battle = getBattle(_name);

    require(_battle.battleStatus == BattleStatus.PENDING, "Battle already started!"); // Require that battle has not started
    require(_battle.players[0] != msg.sender, "Only player two can join a battle"); // Require that player 2 is joining the battle
    require(!getPlayer(msg.sender).inBattle, "Already in battle"); // Require that player is not already in a battle

    _battle.battleStatus = BattleStatus.STARTED;
    _battle.players[1] = msg.sender;
    updateBattle(_name, _battle);

    players[playerInfo[_battle.players[0]]].inBattle = true;
    players[playerInfo[_battle.players[1]]].inBattle = true;

    emit NewBattle(_battle.name, _battle.players[0], msg.sender); // Emits NewBattle event
    return _battle;
    }

    // Read battle move info for player 1 and player 2
    function getBattleMoves(string memory _battleName) public view returns (uint256 P1Move, uint256 P2Move) {
    Battle memory _battle = getBattle(_battleName);

    P1Move = _battle.moves[0];
    P2Move = _battle.moves[1];

    return (P1Move, P2Move);
    }

    function _registerPlayerMove(uint256 _player, uint8 _choice, string memory _battleName) internal {
    require(_choice == 1 || _choice == 2, "Choice should be either 1 or 2!");
    require(_choice == 1 ? getPlayer(msg.sender).playerMana >= 3 : true, "Mana not sufficient for attacking!");
    battles[battleInfo[_battleName]].moves[_player] = _choice;
    }

    // User chooses attack or defense move for battle card
    function attackOrDefendChoice(uint8 _choice, string memory _battleName) external {
    Battle memory _battle = getBattle(_battleName);

    require(
    _battle.battleStatus == BattleStatus.STARTED,
    "Battle not started. Please tell another player to join the battle"
    ); // Require that battle has started
    require(
    _battle.battleStatus != BattleStatus.ENDED,
    "Battle has already ended"
    ); // Require that battle has not ended
    require(
    msg.sender == _battle.players[0] || msg.sender == _battle.players[1],
    "You are not in this battle"
    ); // Require that player is in the battle

    require(_battle.moves[_battle.players[0] == msg.sender ? 0 : 1] == 0, "You have already made a move!");

    _registerPlayerMove(_battle.players[0] == msg.sender ? 0 : 1, _choice, _battleName);

    _battle = getBattle(_battleName);
    uint _movesLeft = 2 - (_battle.moves[0] == 0 ? 0 : 1) - (_battle.moves[1] == 0 ? 0 : 1);
    emit BattleMove(_battleName, _movesLeft == 1 ? true : false);

    if(_movesLeft == 0) {
    _awaitBattleResults(_battleName);
    }
    }

    // Awaits battle results
    function _awaitBattleResults(string memory _battleName) internal {
    Battle memory _battle = getBattle(_battleName);

    require(
    msg.sender == _battle.players[0] || msg.sender == _battle.players[1],
    "Only players in this battle can make a move"
    );

    require(
    _battle.moves[0] != 0 && _battle.moves[1] != 0,
    "Players still need to make a move"
    );

    _resolveBattle(_battle);
    }

    struct P {
    uint index;
    uint move;
    uint health;
    uint attack;
    uint defense;
    }

    /// @dev Resolve battle function to determine winner and loser of battle
    /// @param _battle battle; battle to resolve
    function _resolveBattle(Battle memory _battle) internal {
    P memory p1 = P(
    playerInfo[_battle.players[0]],
    _battle.moves[0],
    getPlayer(_battle.players[0]).playerHealth,
    getPlayerToken(_battle.players[0]).attackStrength,
    getPlayerToken(_battle.players[0]).defenseStrength
    );

    P memory p2 = P(
    playerInfo[_battle.players[1]],
    _battle.moves[1],
    getPlayer(_battle.players[1]).playerHealth,
    getPlayerToken(_battle.players[1]).attackStrength,
    getPlayerToken(_battle.players[1]).defenseStrength
    );

    address[2] memory _damagedPlayers = [address(0), address(0)];

    if (p1.move == 1 && p2.move == 1) {
    if (p1.attack >= p2.health) {
    _endBattle(_battle.players[0], _battle);
    } else if (p2.attack >= p1.health) {
    _endBattle(_battle.players[1], _battle);
    } else {
    players[p1.index].playerHealth -= p2.attack;
    players[p2.index].playerHealth -= p1.attack;

    players[p1.index].playerMana -= 3;
    players[p2.index].playerMana -= 3;

    // Both player's health damaged
    _damagedPlayers = _battle.players;
    }
    } else if (p1.move == 1 && p2.move == 2) {
    uint256 PHAD = p2.health + p2.defense;
    if (p1.attack >= PHAD) {
    _endBattle(_battle.players[0], _battle);
    } else {
    uint256 healthAfterAttack;

    if(p2.defense > p1.attack) {
    healthAfterAttack = p2.health;
    } else {
    healthAfterAttack = PHAD - p1.attack;

    // Player 2 health damaged
    _damagedPlayers[0] = _battle.players[1];
    }

    players[p2.index].playerHealth = healthAfterAttack;

    players[p1.index].playerMana -= 3;
    players[p2.index].playerMana += 3;
    }
    } else if (p1.move == 2 && p2.move == 1) {
    uint256 PHAD = p1.health + p1.defense;
    if (p2.attack >= PHAD) {
    _endBattle(_battle.players[1], _battle);
    } else {
    uint256 healthAfterAttack;

    if(p1.defense > p2.attack) {
    healthAfterAttack = p1.health;
    } else {
    healthAfterAttack = PHAD - p2.attack;

    // Player 1 health damaged
    _damagedPlayers[0] = _battle.players[0];
    }

    players[p1.index].playerHealth = healthAfterAttack;

    players[p1.index].playerMana += 3;
    players[p2.index].playerMana -= 3;
    }
    } else if (p1.move == 2 && p2.move == 2) {
    players[p1.index].playerMana += 3;
    players[p2.index].playerMana += 3;
    }

    emit RoundEnded(
    _damagedPlayers
    );

    // Reset moves to 0
    _battle.moves[0] = 0;
    _battle.moves[1] = 0;
    updateBattle(_battle.name, _battle);

    // Reset random attack and defense strength
    uint256 _randomAttackStrengthPlayer1 = _createRandomNum(MAX_ATTACK_DEFEND_STRENGTH, _battle.players[0]);
    gameTokens[playerTokenInfo[_battle.players[0]]].attackStrength = _randomAttackStrengthPlayer1;
    gameTokens[playerTokenInfo[_battle.players[0]]].defenseStrength = MAX_ATTACK_DEFEND_STRENGTH - _randomAttackStrengthPlayer1;

    uint256 _randomAttackStrengthPlayer2 = _createRandomNum(MAX_ATTACK_DEFEND_STRENGTH, _battle.players[1]);
    gameTokens[playerTokenInfo[_battle.players[1]]].attackStrength = _randomAttackStrengthPlayer2;
    gameTokens[playerTokenInfo[_battle.players[1]]].defenseStrength = MAX_ATTACK_DEFEND_STRENGTH - _randomAttackStrengthPlayer2;
    }

    function quitBattle(string memory _battleName) public {
    Battle memory _battle = getBattle(_battleName);
    require(_battle.players[0] == msg.sender || _battle.players[1] == msg.sender, "You are not in this battle!");

    _battle.players[0] == msg.sender ? _endBattle(_battle.players[1], _battle) : _endBattle(_battle.players[0], _battle);
    }

    /// @dev internal function to end the battle
    /// @param battleEnder winner address
    /// @param _battle battle; taken from attackOrDefend function
    function _endBattle(address battleEnder, Battle memory _battle) internal returns (Battle memory) {
    require(_battle.battleStatus != BattleStatus.ENDED, "Battle already ended"); // Require that battle has not ended

    _battle.battleStatus = BattleStatus.ENDED;
    _battle.winner = battleEnder;
    updateBattle(_battle.name, _battle);

    uint p1 = playerInfo[_battle.players[0]];
    uint p2 = playerInfo[_battle.players[1]];

    players[p1].inBattle = false;
    players[p1].playerHealth = 25;
    players[p1].playerMana = 10;

    players[p2].inBattle = false;
    players[p2].playerHealth = 25;
    players[p2].playerMana = 10;

    address _battleLoser = battleEnder == _battle.players[0] ? _battle.players[1] : _battle.players[0];

    emit BattleEnded(_battle.name, battleEnder, _battleLoser); // Emits BattleEnded event

    return _battle;
    }

    // Turns uint256 into string
    function uintToStr(uint256 _i) internal pure returns (string memory _uintAsString) {
    if (_i == 0) {
    return '0';
    }
    uint256 j = _i;
    uint256 len;
    while (j != 0) {
    len++;
    j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint256 k = len;
    while (_i != 0) {
    k = k - 1;
    uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
    bytes1 b1 = bytes1(temp);
    bstr[k] = b1;
    _i /= 10;
    }
    return string(bstr);
    }

    // Token URI getter function
    function tokenURI(uint256 tokenId) public view returns (string memory) {
    return string(abi.encodePacked(baseURI, '/', uintToStr(tokenId), '.json'));
    }

    // The following functions are overrides required by Solidity.
    function _beforeTokenTransfer(
    address operator,
    address from,
    address to,
    uint256[] memory ids,
    uint256[] memory amounts,
    bytes memory data
    ) internal override(ERC1155, ERC1155Supply) {
    super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
    }
    }
    27 changes: 27 additions & 0 deletions deploy.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    import { ethers } from 'hardhat';
    import console from 'console';

    const _metadataUri = 'https://gateway.pinata.cloud/ipfs/https://gateway.pinata.cloud/ipfs/QmX2ubhtBPtYw75Wrpv6HLb1fhbJqxrnbhDo1RViW3oVoi';

    async function deploy(name: string, ...params: [string]) {
    const contractFactory = await ethers.getContractFactory(name);

    return await contractFactory.deploy(...params).then((f) => f.deployed());
    }

    async function main() {
    const [admin] = await ethers.getSigners();

    console.log(`Deploying a smart contract...`);

    const AVAXGods = (await deploy('AVAXGods', _metadataUri)).connect(admin);

    console.log({ AVAXGods: AVAXGods.address });
    }

    main()
    .then(() => process.exit(0))
    .catch((error) => {
    console.error(error)
    process.exit(1)
    });
    39 changes: 39 additions & 0 deletions hardhat.config.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,39 @@
    import dotenv from 'dotenv';
    import '@nomiclabs/hardhat-ethers';

    dotenv.config();

    //* Notes for deploying the smart contract on your own subnet
    //* More info on subnets: https://docs.avax.network/subnets
    //* Why deploy on a subnet: https://docs.avax.network/subnets/when-to-use-subnet-vs-c-chain
    //* How to deploy on a subnet: https://docs.avax.network/subnets/create-a-local-subnet
    //* Transactions on the C-Chain might take 2-10 seconds -> the ones on the subnet will be much faster
    //* On C-Chain we're relaying on the Avax token to confirm transactions -> on the subnet we can create our own token
    //* You are in complete control over the network and it's inner workings

    export default {
    solidity: {
    version: '0.8.16',
    settings: {
    viaIR: true,
    optimizer: {
    enabled: true,
    runs: 100,
    },
    },
    },
    networks: {
    fuji: {
    url: 'https://api.avax-test.network/ext/bc/C/rpc',
    gasPrice: 225000000000,
    chainId: 43113,
    accounts: [process.env.PRIVATE_KEY],
    },
    // subnet: {
    // url: process.env.NODE_URL,
    // chainId: Number(process.env.CHAIN_ID),
    // gasPrice: 'auto',
    // accounts: [process.env.PRIVATE_KEY],
    // },
    },
    }