Skip to content

Latest commit

 

History

History
663 lines (545 loc) · 24.4 KB

06.md

File metadata and controls

663 lines (545 loc) · 24.4 KB
title actions requireLogin material
Visualizzare il nostro esercito di zombi
checkAnswer
hints
true
editor
language startingCode answer
html
index.html zombieownership.sol zombieattack.sol zombiehelper.sol zombiefeeding.sol zombiefactory.sol ownable.sol safemath.sol erc721.sol
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CryptoZombies front-end</title> <script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script language="javascript" type="text/javascript" src="web3.min.js"></script> <script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script> </head> <body> <div id="zombies"></div> <script> var cryptoZombies; var userAccount; function startApp() { var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS"; cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress); var accountInterval = setInterval(function() { // Controlla se l'account è stato modificato if (web3.eth.accounts[0] !== userAccount) { userAccount = web3.eth.accounts[0]; // Chiama alcune funzioni per aggiornare l'interfaccia utente (UI) con il nuovo account getZombiesByOwner(userAccount) .then(displayZombies); } }, 100); } function displayZombies(ids) { // Inizia qui } function getZombieDetails(id) { return cryptoZombies.methods.zombies(id).call() } function zombieToOwner(id) { return cryptoZombies.methods.zombieToOwner(id).call() } function getZombiesByOwner(owner) { return cryptoZombies.methods.getZombiesByOwner(owner).call() } window.addEventListener('load', function() { // Verifica se Web3 è stato iniettato dal browser (Mist/MetaMask) if (typeof web3 !== 'undefined') { // Usa il provider Mist/MetaMask web3js = new Web3(web3.currentProvider); } else { // Gestire il caso in cui l'utente non ha installato Metamask // Probabilmente mostrare loro un messaggio che richiede di installare Metamask } // Ora puoi avviare la tua app e accedere a web3 liberamente: startApp() }) </script> </body> </html>
pragma solidity ^0.4.25; import "./zombieattack.sol"; import "./erc721.sol"; import "./safemath.sol"; contract ZombieOwnership is ZombieAttack, ERC721 { using SafeMath for uint256; mapping (uint => address) zombieApprovals; function balanceOf(address _owner) external view returns (uint256) { return ownerZombieCount[_owner]; } function ownerOf(uint256 _tokenId) external view returns (address) { return zombieToOwner[_tokenId]; } function _transfer(address _from, address _to, uint256 _tokenId) private { ownerZombieCount[_to] = ownerZombieCount[_to].add(1); ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); zombieToOwner[_tokenId] = _to; emit Transfer(_from, _to, _tokenId); } function transferFrom(address _from, address _to, uint256 _tokenId) external payable { require (zombieToOwner[_tokenId] == msg.sender || zombieApprovals[_tokenId] == msg.sender); _transfer(_from, _to, _tokenId); } function approve(address _approved, uint256 _tokenId) external payable onlyOwnerOf(_tokenId) { zombieApprovals[_tokenId] = _approved; emit Approval(msg.sender, _approved, _tokenId); } }
pragma solidity ^0.4.25; import "./zombiehelper.sol"; contract ZombieAttack is ZombieHelper { uint randNonce = 0; uint attackVictoryProbability = 70; function randMod(uint _modulus) internal returns(uint) { randNonce = randNonce.add(1); return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus; } function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { Zombie storage myZombie = zombies[_zombieId]; Zombie storage enemyZombie = zombies[_targetId]; uint rand = randMod(100); if (rand <= attackVictoryProbability) { myZombie.winCount = myZombie.winCount.add(1); myZombie.level = myZombie.level.add(1); enemyZombie.lossCount = enemyZombie.lossCount.add(1); feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); } else { myZombie.lossCount = myZombie.lossCount.add(1); enemyZombie.winCount = enemyZombie.winCount.add(1); _triggerCooldown(myZombie); } } }
pragma solidity ^0.4.25; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { uint levelUpFee = 0.001 ether; modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } function withdraw() external onlyOwner { address _owner = owner(); _owner.transfer(address(this).balance); } function setLevelUpFee(uint _fee) external onlyOwner { levelUpFee = _fee; } function levelUp(uint _zombieId) external payable { require(msg.value == levelUpFee); zombies[_zombieId].level = zombies[_zombieId].level.add(1); } function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) { zombies[_zombieId].name = _newName; } function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_zombieId) { zombies[_zombieId].dna = _newDna; } function getZombiesByOwner(address _owner) external view returns(uint[]) { uint[] memory result = new uint[](ownerZombieCount[_owner]); uint counter = 0; for (uint i = 0; i < zombies.length; i++) { if (zombieToOwner[i] == _owner) { result[counter] = i; counter++; } } return result; } }
pragma solidity ^0.4.25; import "./zombiefactory.sol"; contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { KittyInterface kittyContract; modifier onlyOwnerOf(uint _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); _; } function setKittyContractAddress(address _address) external onlyOwner { kittyContract = KittyInterface(_address); } function _triggerCooldown(Zombie storage _zombie) internal { _zombie.readyTime = uint32(now + cooldownTime); } function _isReady(Zombie storage _zombie) internal view returns (bool) { return (_zombie.readyTime <= now); } function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal onlyOwnerOf(_zombieId) { Zombie storage myZombie = zombies[_zombieId]; require(_isReady(myZombie)); _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("kitty"))) { newDna = newDna - newDna % 100 + 99; } _createZombie("NoName", newDna); _triggerCooldown(myZombie); } function feedOnKitty(uint _zombieId, uint _kittyId) public { uint kittyDna; (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); } }
pragma solidity ^0.4.25; import "./ownable.sol"; import "./safemath.sol"; contract ZombieFactory is Ownable { using SafeMath for uint256; using SafeMath32 for uint32; using SafeMath16 for uint16; event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; uint cooldownTime = 1 days; struct Zombie { string name; uint dna; uint32 level; uint32 readyTime; uint16 winCount; uint16 lossCount; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string _name, uint _dna) internal { uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1); emit NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus; } function createRandomZombie(string _name) public { require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); randDna = randDna - randDna % 100; _createZombie(_name, randDna); } }
pragma solidity ^0.4.25; /** * @title Ownable * @dev Il contratto di proprietà ha un indirizzo del proprietario e fornisce funzioni di controllo * delle autorizzazioni di base, ciò semplifica l'implementazione delle "autorizzazioni dell'utente". */ contract Ownable { address private _owner; event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev Il costruttore di proprietà imposta il `proprietario` originale del contratto sull'account del mittente. */ constructor() internal { _owner = msg.sender; emit OwnershipTransferred(address(0), _owner); } /** * @return the address of the owner. */ function owner() public view returns(address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner()); _; } /** * @return true if `msg.sender` is the owner of the contract. */ function isOwner() public view returns(bool) { return msg.sender == _owner; } /** * @dev Allows the current owner to relinquish control of the contract. * @notice Renouncing to ownership will leave the contract without an owner. * It will not be possible to call the functions with the `onlyOwner` * modifier anymore. */ function renounceOwnership() public onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { _transferOwnership(newOwner); } /** * @dev Transfers control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function _transferOwnership(address newOwner) internal { require(newOwner != address(0)); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } }
pragma solidity ^0.4.25; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Multiplies two numbers, throws on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } /** * @dev Integer division of two numbers, truncating the quotient. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity lo lancia automaticamente quando si divide per 0 uint256 c = a / b; // assert(a == b * c + a % b); // Non esiste un caso in cui ciò non valga return c; } /** * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } /** * @title SafeMath32 * @dev SafeMath library implemented for uint32 */ library SafeMath32 { function mul(uint32 a, uint32 b) internal pure returns (uint32) { if (a == 0) { return 0; } uint32 c = a * b; assert(c / a == b); return c; } function div(uint32 a, uint32 b) internal pure returns (uint32) { // assert(b > 0); // Solidity lo lancia automaticamente quando si divide per 0 uint32 c = a / b; // assert(a == b * c + a % b); // Non esiste un caso in cui ciò non valga return c; } function sub(uint32 a, uint32 b) internal pure returns (uint32) { assert(b <= a); return a - b; } function add(uint32 a, uint32 b) internal pure returns (uint32) { uint32 c = a + b; assert(c >= a); return c; } } /** * @title SafeMath16 * @dev SafeMath library implemented for uint16 */ library SafeMath16 { function mul(uint16 a, uint16 b) internal pure returns (uint16) { if (a == 0) { return 0; } uint16 c = a * b; assert(c / a == b); return c; } function div(uint16 a, uint16 b) internal pure returns (uint16) { // assert(b > 0); // Solidity lo lancia automaticamente quando si divide per 0 uint16 c = a / b; // assert(a == b * c + a % b); // Non esiste un caso in cui ciò non valga return c; } function sub(uint16 a, uint16 b) internal pure returns (uint16) { assert(b <= a); return a - b; } function add(uint16 a, uint16 b) internal pure returns (uint16) { uint16 c = a + b; assert(c >= a); return c; } }
pragma solidity ^0.4.25; contract ERC721 { event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CryptoZombies front-end</title> <script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script language="javascript" type="text/javascript" src="web3.min.js"></script> <script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script> </head> <body> <div id="zombies"></div> <script> var cryptoZombies; var userAccount; function startApp() { var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS"; cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress); var accountInterval = setInterval(function() { // Controlla se l'account è stato modificato if (web3.eth.accounts[0] !== userAccount) { userAccount = web3.eth.accounts[0]; // Chiama alcune funzioni per aggiornare l'interfaccia utente (UI) con il nuovo account getZombiesByOwner(userAccount) .then(displayZombies); } }, 100); } function displayZombies(ids) { $("#zombies").empty(); for (id of ids) { // Cerca i dettagli degli zombi dal nostro contratto. Restituisce un oggetto `zombie` getZombieDetails(id) .then(function(zombie) { // Utilizzo dei "template literals" di ES6 per iniettare variabili nel codice HTML. // Aggiungi ognuno al nostro div #zombies $("#zombies").append(`<div class="zombie"> <ul> <li>Name: ${zombie.name}&lt;/li&gt; &lt;li&gt;DNA: ${zombie.dna}</li> <li>Level: ${zombie.level}&lt;/li&gt; &lt;li&gt;Wins: ${zombie.winCount}</li> <li>Losses: ${zombie.lossCount}&lt;/li&gt; &lt;li&gt;Ready Time: ${zombie.readyTime}</li> </ul> </div>`); }); } } function getZombieDetails(id) { return cryptoZombies.methods.zombies(id).call() } function zombieToOwner(id) { return cryptoZombies.methods.zombieToOwner(id).call() } function getZombiesByOwner(owner) { return cryptoZombies.methods.getZombiesByOwner(owner).call() } window.addEventListener('load', function() { // Verifica se Web3 è stato iniettato dal browser (Mist/MetaMask) if (typeof web3 !== 'undefined') { // Usa il provider Mist/MetaMask web3js = new Web3(web3.currentProvider); } else { // Gestire il caso in cui l'utente non ha installato Metamask // Probabilmente mostrare loro un messaggio che richiede di installare Metamask } // Ora puoi avviare la tua app e accedere a web3 liberamente: startApp() }) </script> </body> </html>

Questo tutorial non sarebbe completo se non ti mostrassimo come visualizzare effettivamente i dati che ricevi dal contratto.

Tuttavia, realisticamente, vorrai utilizzare un framework front-end come React o Vue.js nella tua app, poiché ti semplificano la vita come sviluppatore front-end. Ma spiegare React o Vue.js è ben al di fuori dello scopo di questo tutorial — servirebbe un intero tutorial con molte lezioni.

Quindi, al fine di mantenere CryptoZombies.io concentrato su Ethereum e sui contratti intelligenti, mostreremo solo un rapido esempio in JQuery per dimostrare come è possibile analizzare e visualizzare i dati che si ottengono dal contratto intelligente.

Visualizzare i dati degli zombi - un esempio grezzo

Abbiamo aggiunto un <div id="zombies"></div> vuoto al body del nostro documento, così come una funzione vuota displayZombies.

Ricordiamo che nel capitolo precedente abbiamo chiamato displayZombies dall'interno di startApp() con il risultato di una chiamata a getZombiesByOwner. Verrà passato un array di ID zombi che assomiglia a:

[0, 13, 47]

Quindi vogliamo che la nostra funzione displayZombies:

  1. Per prima cosa cancelli il contenuto del div #zombies se c'è già qualcosa al suo interno. (In questo modo se l'utente cambia il suo account MetaMask attivo, pulirà il suo vecchio esercito di zombi prima di caricare quello nuovo).

  2. Passi attraverso ogni id e per ciascuno chiami getZombieDetails(id) per cercare tutte le informazioni di quello zombi dal nostro contratto intelligente, quindi

  3. Metta le informazioni su quello zombie in un modello HTML per formattarlo per la visualizzazione e aggiunga quel modello al div #zombies.

Ancora una volta, stiamo solo usando JQuery qui, che per impostazione predefinita non ha un motore di template, quindi sarà brutto. Ma ecco un semplice esempio di come potremmo generare questi dati per ogni zombi:

// Cerca i dettagli degli zombi dal nostro contratto. Restituisce un oggetto `zombie`
getZombieDetails(id)
.then(function(zombie) {
  // Utilizzo dei "template literals" di ES6 per iniettare variabili nel codice HTML.
  // Aggiungi ognuno al nostro div #zombies
  $("#zombies").append(`<div class="zombie">
    <ul>
      <li>Name: ${zombie.name}</li>
      <li>DNA: ${zombie.dna}</li>
      <li>Level: ${zombie.level}</li>
      <li>Wins: ${zombie.winCount}</li>
      <li>Losses: ${zombie.lossCount}</li>
      <li>Ready Time: ${zombie.readyTime}</li>
    </ul>
  </div>`);
});

Che ne dici di mostrare le immagini degli zombi?

Nell'esempio sopra, stiamo semplicemente mostrando il DNA come una stringa. Ma nella tua DApp vorresti convertirlo in immagini per visualizzare il tuo zombi.

Lo abbiamo fatto suddividendo la stringa di DNA in sottostringhe e facendo corrispondere ogni 2 cifre ad un'immagine. Qualcosa di simile a:

// Ottieni un numero intero 1-7 che rappresenta la nostra testa di zombi:
var head = parseInt(zombie.dna.substring(0, 2)) % 7 + 1

// Abbiamo 7 immagini di testa con nomi di file sequenziali:
var headSrc = "../assets/zombieparts/head-" + head + ".png"

Ogni componente viene posizionato tramite CSS usando il posizionamento assoluto (absolute) per sovrapporlo sulle altre immagini.

Se vuoi vedere la nostra esatta implementazione, abbiamo aperto il componente Vue.js che usiamo per l'aspetto dello zombi, lo puoi visualizzare qui.

Tuttavia, poiché nel file è presente molto codice, non rientra nell'ambito di questo tutorial. Per questa lezione seguiremo l'implementazione JQuery di sopra estremamente semplice e ti lasceremo immergerti in un'implementazione più bella come compito da fare a casa 😉

Facciamo una prova

Abbiamo creato una funzione vuota displayZombies per te. Compiliamola.

  1. La prima cosa che vorremmo fare è svuotare il div #zombies. In JQuery, puoi farlo con $("#zombies").empty();.

  2. Successivamente vorremmo scorrere tutti gli id usando un ciclo for: for (id of ids) {

  3. All'interno del ciclo for, copia/incolla il blocco del codice di sopra chiamato getZombieDetails(id) per ogni id e quindi usa $("#zombies").append(...) per aggiungerlo al nostro HTML.