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

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

第1章: 可支付

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

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

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

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

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

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

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

payable 修飾符

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

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

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

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

來看個例子

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

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

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

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

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

注意這個 value 字段悔常, JavaScript 調(diào)用來指定發(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ā)送以太之后,它將被存儲進以合約的以太坊賬戶中泽篮, 并凍結(jié)在哪里 —— 除非你添加一個函數(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ù)鸦致,這樣我們就不會對相同的輸入值調(diào)用一次以上哈希函數(shù)了)。

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

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

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

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

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

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

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

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

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

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

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

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

實戰(zhàn)演習

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

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

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

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

  4. 最后逆趣,它應該 (在一行代碼中) 計算 now, msg.sender, 以及 randNoncekeccak256 哈希值并轉(zhuǎn)換為 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)斗中去計算結(jié)果痕囱。

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

  • 你選擇一個自己的僵尸田轧,然后選擇一個對手的僵尸去攻擊。
  • 如果你是攻擊方鞍恢,你將有70%的幾率獲勝傻粘,防守方將有30%的幾率獲勝。
  • 所有的僵尸(攻守雙方)都將有一個 winCount 和一個 lossCount有序,這兩個值都將根據(jù)戰(zhàn)斗結(jié)果增長抹腿。
  • 若攻擊方獲勝,這個僵尸將升級并產(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章: 重構(gòu)通用邏輯

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

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

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

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

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

答案

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

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

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

實戰(zhàn)演習

我們回到了 zombiefeeding.sol, 因為這是我們第一次調(diào)用檢查邏輯的地方潘懊。讓我們把它重構(gòu)進它自己的 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章: 更多重構(gòu)

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章: 回到攻擊!

重構(gòu)完成了,回到 zombieattack.sol捞蛋。

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

實戰(zhàn)演習

  1. ownerOf 修飾符添加到 attack 來確保調(diào)用者擁有_zombieId.

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

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

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

  3. 我們將用一個0到100的隨機數(shù)來確定我們的戰(zhàn)斗結(jié)果。 定義一個 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ù)值 — 作為一個單獨的映射,作為一個“排行榜”結(jié)構(gòu)體蚤霞,或者保存在 Zombie 結(jié)構(gòu)體內(nèi)酗失。

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

我們跳回 zombiefactory.sol, 將這些屬性添加進 Zombie 結(jié)構(gòu)體.

實戰(zhàn)演習

  1. 修改 Zombie 結(jié)構(gòu)體拖刃,添加兩個屬性:

    a. winCount, 一個 uint16

    b. lossCount, 也是一個 uint16

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

  1. 現(xiàn)在我們的 Zombie 結(jié)構(gòu)體有了新的屬性, 我們需要修改 _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 里查看調(diào)用它的語句淳附。 對于第三個參數(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ā)入門(宗師篇)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柄冲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子忠蝗,更是在濱河造成了極大的恐慌现横,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異戒祠,居然都是意外死亡骇两,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門姜盈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來低千,“玉大人,你說我怎么就攤上這事馏颂∈狙” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵救拉,是天一觀的道長难审。 經(jīng)常有香客問我,道長亿絮,這世上最難降的妖魔是什么告喊? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮壹无,結(jié)果婚禮上葱绒,老公的妹妹穿的比我還像新娘。我一直安慰自己斗锭,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布失球。 她就那樣靜靜地躺著岖是,像睡著了一般。 火紅的嫁衣襯著肌膚如雪实苞。 梳的紋絲不亂的頭發(fā)上豺撑,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音黔牵,去河邊找鬼聪轿。 笑死,一個胖子當著我的面吹牛猾浦,可吹牛的內(nèi)容都是我干的陆错。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼金赦,長吁一口氣:“原來是場噩夢啊……” “哼音瓷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起夹抗,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤绳慎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杏愤,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡靡砌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了珊楼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乏奥。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖亥曹,靈堂內(nèi)的尸體忽然破棺而出邓了,到底是詐尸還是另有隱情,我是刑警寧澤媳瞪,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布骗炉,位于F島的核電站,受9級特大地震影響蛇受,放射性物質(zhì)發(fā)生泄漏句葵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一兢仰、第九天 我趴在偏房一處隱蔽的房頂上張望乍丈。 院中可真熱鬧,春花似錦把将、人聲如沸轻专。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽请垛。三九已至,卻和暖如春洽议,著一層夾襖步出監(jiān)牢的瞬間宗收,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工亚兄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留混稽,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓审胚,卻偏偏與公主長得像匈勋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子菲盾,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

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