以太坊Solidity開發(fā)入門(大師篇)

上一篇:以太坊Solidity開發(fā)入門(高級篇)

第1章: 可支付

截至目前,我們只接觸到很少的 函數(shù)修飾符。 要記住所有的東西很難,所以我們來個概覽:

  1. 我們有決定函數(shù)何時和被誰調用的可見性修飾符: private 意味著它只能被合約內(nèi)部調用; internal 就像 private 但是也能被繼承的合約調用; external 只能從合約外部調用;最后 public 可以在任何地方調用,不管是內(nèi)部還是外部。

  2. 我們也有狀態(tài)修飾符, 告訴我們函數(shù)如何和區(qū)塊鏈交互: view 告訴我們運行這個函數(shù)不會更改和保存任何數(shù)據(jù); pure 告訴我們這個函數(shù)不但不會往區(qū)塊鏈寫數(shù)據(jù),它甚至不從區(qū)塊鏈讀取數(shù)據(jù)。這兩種在被從合約外部調用的時候都不花費任何gas(但是它們在被內(nèi)部其他函數(shù)調用的時候將會耗費gas)。

  3. 然后我們有了自定義的 modifiers,例如在第三課學習的: onlyOwneraboveLevel。 對于這些修飾符我們可以自定義其對函數(shù)的約束邏輯。

這些修飾符可以同時作用于一個函數(shù)定義上:

function test() external view onlyOwner anotherModifier { /* ... */ }

在這一章,我們來學習一個新的修飾符 payable.

payable 修飾符

payable 方法是讓 Solidity 和以太坊變得如此酷的一部分 —— 它們是一種可以接收以太的特殊函數(shù)。

先放一下。當你在調用一個普通網(wǎng)站服務器上的API函數(shù)的時候,你無法用你的函數(shù)傳送美元——你也不能傳送比特幣。

但是在以太坊中, 因為錢 (以太), 數(shù)據(jù) (事務負載), 以及合約代碼本身都存在于以太坊。你可以在同時調用函數(shù) 并付錢給另外一個合約。

這就允許出現(xiàn)很多有趣的邏輯, 比如向一個合約要求支付一定的錢來運行一個函數(shù)。

來看個例子

contract OnlineStore {
  function buySomething() external payable {
    // 檢查以確定0.001以太發(fā)送出去來運行函數(shù):
    require(msg.value == 0.001 ether);
    // 如果為真,一些用來向函數(shù)調用者發(fā)送數(shù)字內(nèi)容的邏輯
    transferThing(msg.sender);
  }
}

在這里,msg.value 是一種可以查看向合約發(fā)送了多少以太的方法,另外 ether 是一個內(nèi)建單元。

這里發(fā)生的事是,一些人會從 web3.js 調用這個函數(shù) (從DApp的前端), 像這樣 :

// 假設 OnlineStore 在以太坊上指向你的合約:

OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))

注意這個 value 字段, JavaScript 調用來指定發(fā)送多少(0.001)以太。如果把事務想象成一個信封,你發(fā)送到函數(shù)的參數(shù)就是信的內(nèi)容。 添加一個 value 很像在信封里面放錢 —— 信件內(nèi)容和錢同時發(fā)送給了接收者。

注意: 如果一個函數(shù)沒標記為payable, 而你嘗試利用上面的方法發(fā)送以太,函數(shù)將拒絕你的事務。

實戰(zhàn)演習

我們來在僵尸游戲里面創(chuàng)建一個payable 函數(shù)。

假定在我們的游戲中,玩家可以通過支付ETH來升級他們的僵尸。ETH將存儲在你擁有的合約中 —— 一個簡單明了的例子,向你展示你可以通過自己的游戲賺錢。

  1. 定義一個 uint ,命名為 levelUpFee, 將值設定為 0.001 ether。

  2. 定義一個名為 levelUp 的函數(shù)。 它將接收一個 uint 參數(shù) _zombieId。 函數(shù)應該修飾為 external 以及 payable。

  3. 這個函數(shù)首先應該 require 確保 msg.value 等于 levelUpFee。

  4. 然后它應該增加僵尸的 level: zombies[_zombieId].level++。

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  // 1. 在這里定義 levelUpFee
  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 2. 在這里插入 levelUp 函數(shù) 
  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
    require(msg.sender == zombieToOwner[_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;
  }

}

第2章: 提現(xiàn)

在上一章,我們學習了如何向合約發(fā)送以太,那么在發(fā)送之后會發(fā)生什么呢?

在你發(fā)送以太之后,它將被存儲進以合約的以太坊賬戶中, 并凍結在哪里 —— 除非你添加一個函數(shù)來從合約中把以太提現(xiàn)。

你可以寫一個函數(shù)來從合約中提現(xiàn)以太,類似這樣:

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}

注意我們使用 Ownable 合約中的 owneronlyOwner,假定它已經(jīng)被引入了。

你可以通過 transfer 函數(shù)向一個地址發(fā)送以太, 然后 this.balance 將返回當前合約存儲了多少以太。 所以如果100個用戶每人向我們支付1以太, this.balance 將是100以太。

你可以通過 transfer 向任何以太坊地址付錢。 比如,你可以有一個函數(shù)在 msg.sender 超額付款的時候給他們退錢:

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);

或者在一個有賣家和賣家的合約中, 你可以把賣家的地址存儲起來, 當有人買了它的東西的時候,把買家支付的錢發(fā)送給它 seller.transfer(msg.value)。

有很多例子來展示什么讓以太坊編程如此之酷 —— 你可以擁有一個不被任何人控制的去中心化市場。

實戰(zhàn)演習

  1. 在我們的合約里創(chuàng)建一個 withdraw 函數(shù),它應該幾乎和上面的GetPaid一樣。

  2. 以太的價格在過去幾年內(nèi)翻了十幾倍,在我們寫這個教程的時候 0.01 以太相當于1美元,如果它再翻十倍 0.001 以太將是10美元,那我們的游戲就太貴了。

    所以我們應該再創(chuàng)建一個函數(shù),允許我們以合約擁有者的身份來設置 levelUpFee。

    a. 創(chuàng)建一個函數(shù),名為 setLevelUpFee, 其接收一個參數(shù) uint _fee,是 external 并使用修飾符 onlyOwner。

    b. 這個函數(shù)應該設置 levelUpFee 等于 _fee

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 1. 在這里創(chuàng)建 withdraw 函數(shù)
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }

  // 2. 在這里創(chuàng)建 setLevelUpFee 函數(shù) 
  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
    require(msg.sender == zombieToOwner[_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;
  }

}

第3章: 僵尸戰(zhàn)斗

在我們學習了可支付函數(shù)和合約余額之后,是時候為僵尸戰(zhàn)斗添加功能了。

遵循上一章的格式,我們新建一個攻擊功能合約,并將代碼放進新的文件中,引入上一個合約。

實戰(zhàn)演習

再來新建一個合約吧。熟能生巧。

如果你不記得怎么做了, 查看一下 zombiehelper.sol — 不過最好先試著做一下,檢查一下你掌握的情況。

  1. 在文件開頭定義 Solidity 的版本 ^0.4.19.

  2. importzombiehelper.sol .

  3. 聲明一個新的 contract,命名為 ZombieBattle, 繼承自ZombieHelper。函數(shù)體就先空著吧。

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
 
}

第4章: 隨機數(shù)

你太棒了!接下來我們梳理一下戰(zhàn)斗邏輯。

優(yōu)秀的游戲都需要一些隨機元素,那么我們在 Solidity 里如何生成隨機數(shù)呢?

真正的答案是你不能,或者最起碼,你無法安全地做到這一點。

我們來看看為什么

keccak256 來制造隨機數(shù)。

Solidity 中最好的隨機數(shù)生成器是 keccak256 哈希函數(shù).

我們可以這樣來生成一些隨機數(shù)

// 生成一個0到100的隨機數(shù):
uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;

這個方法首先拿到 now 的時間戳、 msg.sender、 以及一個自增數(shù) nonce (一個僅會被使用一次的數(shù),這樣我們就不會對相同的輸入值調用一次以上哈希函數(shù)了)。

然后利用 keccak 把輸入的值轉變?yōu)橐粋€哈希值, 再將哈希值轉換為 uint, 然后利用 % 100 來取最后兩位, 就生成了一個0到100之間隨機數(shù)了。

這個方法很容易被不誠實的節(jié)點攻擊

在以太坊上, 當你在和一個合約上調用函數(shù)的時候, 你會把它廣播給一個節(jié)點或者在網(wǎng)絡上的 transaction 節(jié)點們。 網(wǎng)絡上的節(jié)點將收集很多事務, 試著成為第一個解決計算密集型數(shù)學問題的人,作為“工作證明”,然后將“工作證明”(Proof of Work, PoW)和事務一起作為一個 block 發(fā)布在網(wǎng)絡上。

一旦一個節(jié)點解決了一個PoW, 其他節(jié)點就會停止嘗試解決這個 PoW, 并驗證其他節(jié)點的事務列表是有效的,然后接受這個節(jié)點轉而嘗試解決下一個節(jié)點。

這就讓我們的隨機數(shù)函數(shù)變得可利用了

我們假設我們有一個硬幣翻轉合約——正面你贏雙倍錢,反面你輸?shù)羲械腻X。假如它使用上面的方法來決定是正面還是反面 (random >= 50 算正面, random < 50 算反面)。

如果我正運行一個節(jié)點,我可以 只對我自己的節(jié)點 發(fā)布一個事務,且不分享它。 我可以運行硬幣翻轉方法來偷窺我的輸贏 — 如果我輸了,我就不把這個事務包含進我要解決的下一個區(qū)塊中去。我可以一直運行這個方法,直到我贏得了硬幣翻轉并解決了下一個區(qū)塊,然后獲利。

所以我們該如何在以太坊上安全地生成隨機數(shù)呢

因為區(qū)塊鏈的全部內(nèi)容對所有參與者來說是透明的, 這就讓這個問題變得很難,它的解決方法不在本課程討論范圍,你可以閱讀 這個 StackOverflow 上的討論 來獲得一些主意。 一個方法是利用 oracle 來訪問以太坊區(qū)塊鏈之外的隨機數(shù)函數(shù)。

當然, 因為網(wǎng)絡上成千上萬的以太坊節(jié)點都在競爭解決下一個區(qū)塊,我能成功解決下一個區(qū)塊的幾率非常之低。 這將花費我們巨大的計算資源來開發(fā)這個獲利方法 — 但是如果獎勵異常地高(比如我可以在硬幣翻轉函數(shù)中贏得 1個億), 那就很值得去攻擊了。

所以盡管這個方法在以太坊上不安全,在實際中,除非我們的隨機函數(shù)有一大筆錢在上面,你游戲的用戶一般是沒有足夠的資源去攻擊的。

因為在這個教程中,我們只是在編寫一個簡單的游戲來做演示,也沒有真正的錢在里面,所以我們決定接受這個不足之處,使用這個簡單的隨機數(shù)生成函數(shù)。但是要謹記它是不安全的。

實戰(zhàn)演習

我們來實現(xiàn)一個隨機數(shù)生成函數(shù),好來計算戰(zhàn)斗的結果。雖然這個函數(shù)一點兒也不安全。

  1. 給我們合約一個名為 randNonceuint,將其值設置為 0。

  2. 建立一個函數(shù),命名為 randMod (random-modulus)。它將作為internal 函數(shù),傳入一個名為 _modulusuint,并 returns 一個 uint。

  3. 這個函數(shù)首先將為 randNonce加一, (使用 randNonce++ 語句)。

  4. 最后,它應該 (在一行代碼中) 計算 now, msg.sender, 以及 randNoncekeccak256 哈希值并轉換為 uint—— 最后 return % _modulus 的值。 (天! 聽起來太拗口了。如果你有點理解不過來,看一下我們上面計算隨機數(shù)的例子,它們的邏輯非常相似)

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  // 在這里開始
  uint randNonce = 0;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randnonce)) % _modulus;
    
  }

}


第5章: 僵尸對戰(zhàn)

我們的合約已經(jīng)有了一些隨機性的來源,可以用進我們的僵尸戰(zhàn)斗中去計算結果。

我們的僵尸戰(zhàn)斗看起來將是這個流程:

  • 你選擇一個自己的僵尸,然后選擇一個對手的僵尸去攻擊。
  • 如果你是攻擊方,你將有70%的幾率獲勝,防守方將有30%的幾率獲勝。
  • 所有的僵尸(攻守雙方)都將有一個 winCount 和一個 lossCount,這兩個值都將根據(jù)戰(zhàn)斗結果增長。
  • 若攻擊方獲勝,這個僵尸將升級并產(chǎn)生一個新僵尸。
  • 如果攻擊方失敗,除了失敗次數(shù)將加一外,什么都不會發(fā)生。
  • 無論輸贏,當前僵尸的冷卻時間都將被激活。

這有一大堆的邏輯需要處理,我們將把這些步驟分解到接下來的課程中去。

實戰(zhàn)演習

  1. 給我們合約一個 uint 類型的變量,命名為 attackVictoryProbability, 將其值設定為 70

  2. 創(chuàng)建一個名為 attack的函數(shù)。它將傳入兩個參數(shù): _zombieId (uint 類型) 以及 _targetId (也是 uint)。它將是一個 external 函數(shù)。

函數(shù)體先留空吧。

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  // 在這里創(chuàng)建 attackVictoryProbability
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  // 在這里創(chuàng)建新函數(shù)
  function attack(uint _zombieId, uint _targetid) external {
    
  }
}

第6章: 重構通用邏輯

不管誰調用我們的 attack 函數(shù) —— 我們想確保用戶的確擁有他們用來攻擊的僵尸。如果你能用其他人的僵尸來攻擊將是一個很大的安全問題。

你能想一下我們?nèi)绾翁砑右粋€檢查步驟來看看調用這個函數(shù)的人就是他們傳入的 _zombieId 的擁有者么?

想一想,看看你能不能自己找到一些答案。

花點時間…… 參考我們前面課程的代碼來獲得靈感。

答案在下面,在你有一些想法之前不要繼續(xù)閱讀。

答案

我們在前面的課程里面已經(jīng)做過很多次這樣的檢查了。 在 changeName(), changeDna(), 和 feedAndMultiply()里,我們做過這樣的檢查:

require(msg.sender == zombieToOwner[_zombieId]);

這和我們 attack 函數(shù)將要用到的檢查邏輯是相同的。 正因我們要多次調用這個檢查邏輯,讓我們把它移到它自己的 modifier 中來清理代碼并避免重復編碼。

實戰(zhàn)演習

我們回到了 zombiefeeding.sol, 因為這是我們第一次調用檢查邏輯的地方。讓我們把它重構進它自己的 modifier。

  1. 創(chuàng)建一個 modifier, 命名為 ownerOf。它將傳入一個參數(shù), _zombieId (一個 uint)。

    它的函數(shù)體應該 require msg.sender 等于 zombieToOwner[_zombieId], 然后繼續(xù)這個函數(shù)剩下的內(nèi)容。 如果你忘記了修飾符的寫法,可以參考 zombiehelper.sol。

  2. 將這個函數(shù)的 feedAndMultiply 定義修改為其使用修飾符 ownerOf

  3. 現(xiàn)在我們使用 modifier了,你可以刪除這行了: require(msg.sender == zombieToOwner[_zombieId]);

pragma solidity ^0.4.19;

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;

  // 1. 在這里創(chuàng)建 modifier
  modifier ownerOf(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);
  }

  // 2. 在函數(shù)定義時增加 modifier :
  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
    // 3. 移除這一行
    //require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    require(_isReady(myZombie));
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    if (keccak256(_species) == keccak256("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");
  }
}


第7章: 更多重構

zombiehelper.sol里有幾處地方,需要我們實現(xiàn)我們新的 modifier—— ownerOf。

實戰(zhàn)演習

  1. 修改 changeName() 使其使用 ownerOf

  2. 修改 changeDna() 使其使用 ownerOf

pragma solidity ^0.4.19;

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 {
    owner.transfer(this.balance);
  }

  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  // 1. 使用 `ownerOf` 修改這個函數(shù):
  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
    //require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

  // 2. 對這個函數(shù)做同樣的事:
  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
    //require(msg.sender == zombieToOwner[_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;
  }

}

第8章: 回到攻擊!

重構完成了,回到 zombieattack.sol。

繼續(xù)來完善我們的 attack 函數(shù), 現(xiàn)在我們有了 ownerOf 修飾符來用了。

實戰(zhàn)演習

  1. ownerOf 修飾符添加到 attack 來確保調用者擁有_zombieId.

  2. 我們的函數(shù)所需要做的第一件事就是獲得一個雙方僵尸的 storage 指針, 這樣我們才能很方便和它們交互:

    a. 定義一個 Zombie storage 命名為 myZombie,使其值等于 zombies[_zombieId]。

    b. 定義一個 Zombie storage 命名為 enemyZombie, 使其值等于 zombies[_targetId]

  3. 我們將用一個0到100的隨機數(shù)來確定我們的戰(zhàn)斗結果。 定義一個 uint,命名為 rand, 設定其值等于 randMod 函數(shù)的返回值,此函數(shù)傳入 100作為參數(shù)。

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  // 1. 在這里增加 modifier
  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId)  {
    // 2. 在這里開始定義函數(shù)
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
  }
}

第9章: 僵尸的輸贏

對我們的僵尸游戲來說,我們將要追蹤我們的僵尸輸贏了多少場。有了這個我們可以在游戲里維護一個 "僵尸排行榜"。

有多種方法在我們的DApp里面保存一個數(shù)值 — 作為一個單獨的映射,作為一個“排行榜”結構體,或者保存在 Zombie 結構體內(nèi)。

每個方法都有其優(yōu)缺點,取決于我們打算如何和這些數(shù)據(jù)打交道。在這個教程中,簡單起見我們將這個狀態(tài)保存在 Zombie 結構體中,將其命名為 winCountlossCount

我們跳回 zombiefactory.sol, 將這些屬性添加進 Zombie 結構體.

實戰(zhàn)演習

  1. 修改 Zombie 結構體,添加兩個屬性:

    a. winCount, 一個 uint16

    b. lossCount, 也是一個 uint16

注意: 記住, 因為我們能在結構體中包裝uint, 我們打算用適合我們的最小的 uint。 一個 uint8 太小了, 因為 2^8 = 256 —— 如果我們的僵尸每天都作戰(zhàn),不到一年就溢出了。但是 2^16 = 65536 (uint16)—— 除非一個僵尸連續(xù)179年每天作戰(zhàn),否則我們就是安全的。

  1. 現(xiàn)在我們的 Zombie 結構體有了新的屬性, 我們需要修改 _createZombie()中的函數(shù)定義。

    修改僵尸生成定義,讓每個新僵尸都有 0 贏和 0 輸。

pragma solidity ^0.4.19;

import "./ownable.sol";

contract ZombieFactory is Ownable {

    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;
      // 1. 在這里添加新的屬性
      uint16 winCount;
      uint16 lossCount;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string _name, uint _dna) internal {
        // 2. 在這里修改修改新僵尸的創(chuàng)建:
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(_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);
    }

}

第10章: 僵尸勝利了 ??

有了 winCountlossCount,我們可以根據(jù)僵尸哪個僵尸贏了戰(zhàn)斗來更新它們了。

在第六章我們計算出來一個0到100的隨機數(shù)。現(xiàn)在讓我們用那個數(shù)來決定那誰贏了戰(zhàn)斗,并以此更新我們的狀態(tài)。

實戰(zhàn)演習

  1. 創(chuàng)建一個 if 語句來檢查 rand 是不是 小于或者等于 attackVictoryProbability。

  2. 如果以上條件為 true, 我們的僵尸就贏了!所以:

    a. 增加 myZombiewinCount。

    b. 增加 myZombielevel。 (升級了啦!!!!!!!)

    c. 增加 enemyZombielossCount. (輸家!!!!!! ?? ?? ??)

    d. 運行 feedAndMultiply 函數(shù)。 在 zombiefeeding.sol 里查看調用它的語句。 對于第三個參數(shù) (_species),傳入字符串 "zombie". (現(xiàn)在它實際上什么都不做,不過在稍后, 如果我們愿意,可以添加額外的方法,用來制造僵尸變的僵尸)。

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    // 在這里開始
    if (rand <= attackVictoryProbability) {
        myZombie.winCount++;
        myZombie.level++;
        enemyZombie.lossCount++;
        feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    }
  }
}

第11章: 僵尸失敗 ??

我們已經(jīng)編寫了你的僵尸贏了之后會發(fā)生什么, 該看看 輸了 的時候要怎么做了。

在我們的游戲中,僵尸輸了后并不會降級 —— 只是簡單地給 lossCount 加一,并觸發(fā)冷卻,等待一天后才能再次參戰(zhàn)。

要實現(xiàn)這個邏輯,我們需要一個 else 語句。

else 語句和 JavaScript 以及很多其他語言的 else 語句一樣。

if (zombieCoins[msg.sender] > 100000000) {
  // 你好有錢!!!
} else {
  // 我們需要更多的僵尸幣...
}

實戰(zhàn)演習

  1. 添加一個 else 語句。 若我們的僵尸輸了:

    a. 增加 myZombielossCount。

    b. 增加 enemyZombiewinCount。

  2. else 最后, 對 myZombie 運行 _triggerCooldown 方法。這讓每個僵尸每天只能參戰(zhàn)一次。

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    // 在這里開始
    } else {
       myZombie.lossCount++;
       enemyZombie.winCount++; 
      _triggerCooldown(myZombie);
    }
  }
}

第12章: 放在一起

恭喜你啊,又完成了第四課。

在右邊測試你的戰(zhàn)斗函數(shù)把。

認領你的戰(zhàn)利品

在贏了戰(zhàn)斗之后:

  1. 你的僵尸將會升級

  2. 你僵尸的 winCount 將會增加

  3. 你將為你的僵尸大軍獲得一個新的僵尸

繼續(xù)測試戰(zhàn)斗,玩夠了以后點擊下一章來完成本課。
示例

下一篇:以太坊Solidity開發(fā)入門(宗師篇)

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 上一篇:以太坊Solidity開發(fā)入門(基礎篇) 第1章: 第二課概覽 在第一課中,我們創(chuàng)建了一個函數(shù)用來生成僵尸...
    那個大螺絲閱讀 3,813評論 0 8
  • 上一篇:以太坊Solidity開發(fā)入門(進階篇) 第1章: 智能協(xié)議的永固性 到現(xiàn)在為止,我們講的 Solidit...
    那個大螺絲閱讀 4,165評論 0 8
  • 分享一個很有趣的以太坊開發(fā)教程。從零開始手把手教你以太坊開發(fā)。 第1章: 課程概述 第一課你將創(chuàng)造一個"僵尸工廠"...
    那個大螺絲閱讀 2,562評論 1 20
  • 對你而言,社群最大的價值是什么? 1,更深刻的了解自己,明確了前行的方向。 2,比過去的自己改變一些,不再懶,不再...
    豆瓣綠兒閱讀 206評論 0 0
  • 一個農(nóng)民從洪水中救起了他的妻子,他的孩子卻被淹死了。 事后,人們議論紛紛。有的說他做得對,因為孩子可以再生一個,妻...
    songsong_add0閱讀 197評論 0 0

友情鏈接更多精彩內(nèi)容