@@ -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);
}
}