接上篇 文章,這裏繼續學習Solidity高級理論。
接下來,咱們將添加一些輔助方法。咱們爲您建立了一個名爲 zombiehelper.sol
的新文件,而且將 zombiefeeding.sol
導入其中,這讓咱們的代碼更整潔。前端
咱們打算讓殭屍在達到必定水平後,得到特殊能力。可是達到這個小目標,咱們還須要學一學什麼是「函數修飾符」。web
以前咱們已經讀過一個簡單的函數修飾符了:onlyOwner
。函數修飾符也能夠帶參數。例如:編程
// 存儲用戶年齡的映射 mapping (uint => uint) public age; // 限定用戶年齡的修飾符 modifier olderThan(uint _age, uint _userId) { require(age[_userId] >= _age); _; } // 必須年滿16週歲才容許開車 (至少在美國是這樣的). // 咱們能夠用以下參數調用`olderThan` 修飾符: function driveCar(uint _userId) public olderThan(16, _userId) { // 其他的程序邏輯 }
看到了吧, olderThan
修飾符能夠像函數同樣接收參數,是「宿主」函數 driveCar
把參數傳遞給它的修飾符的。segmentfault
來,咱們本身生產一個修飾符,經過傳入的level參數來限制殭屍使用某些特殊功能。數組
_;
,表示修飾符調用結束後返回,並執行調用函數餘下的部分。pragma solidity ^0.4.19; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { // 在這裏開始 modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } }
如今讓咱們設計一些使用 aboveLevel
修飾符的函數。安全
做爲遊戲,您得有一些措施激勵玩家們去升級他們的殭屍:服務器
是實現這些功能的時候了。如下是上一課的示例代碼,供參考:網絡
// 存儲用戶年齡的映射 mapping (uint => uint) public age; // 限定用戶年齡的修飾符 modifier olderThan(uint _age, uint _userId) { require (age[_userId] >= _age); _; } // 必須年滿16週歲才容許開車 (至少在美國是這樣的). // 咱們能夠用以下參數調用`olderThan` 修飾符: function driveCar(uint _userId) public olderThan(16, _userId) { // 其他的程序邏輯 }
changeName
的函數。它接收2個參數:_zombieId
(uint類型)以及 _newName
(string類型),可見性爲 external
。它帶有一個 aboveLevel
修飾符,調用的時候經過 _level 參數傳入2, 固然,別忘了同時傳 _zombieId
參數。zombieToOwner [_zombieId]
。zombies[_zombieId] .name
設置爲 _newName
。changeDna
的函數。它的定義和內容幾乎和 changeName 相同,不過它第二個參數是 _newDna(uint類型),在修飾符 aboveLevel 的 _level 參數中傳遞 20 。如今,他能夠把殭屍的 dna 設置爲 _newDna 了。zombiehelper.sol
oracle
pragma solidity ^0.4.19; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _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; } }
如今須要添加的一個功能是:咱們的 DApp 須要一個方法來查看某玩家的整個殭屍軍團 - 咱們稱之爲 getZombiesByOwner
。app
實現這個功能只需從區塊鏈中讀取數據,因此它能夠是一個 view
函數。這讓咱們不得不回顧一下「gas優化」這個重要話題。
當玩家從外部調用一個view
函數,是不須要支付一分 gas
的。
這是由於 view
函數不會真正改變區塊鏈上的任何數據 - 它們只是讀取。所以用 view
標記一個函數,意味着告訴 web3.js
,運行這個函數只須要查詢你的本地以太坊節點,而不須要在區塊鏈上建立一個事務(事務須要運行在每一個節點上,所以花費 gas)。
稍後咱們將介紹如何在本身的節點上設置 web3.js。但如今,你關鍵是要記住,在所能只讀的函數上標記上表示「只讀」的external view
聲明,就能爲你的玩家減小在 DApp 中 gas 用量。
注意:若是一個
view
函數在另外一個函數的內部被調用,而調用函數與 view 函數的不屬於同一個合約,也會產生調用成本。這是由於若是主調函數在以太坊建立了一個事務,它仍然須要逐個節點去驗證。因此標記爲 view 的函數只有在外部調用時纔是免費的。
咱們來寫一個」返回某玩家的整個殭屍軍團「的函數。當咱們從 web3.js
中調用它,便可顯示某一玩家的我的資料頁。
這個函數的邏輯有點複雜,咱們須要好幾個章節來描述它的實現。
getZombiesByOwner
的新函數。它有一個名爲 _owner
的 address
類型的參數。external view
函數,這樣當玩家從 web3.js 中調用它時,不須要花費任何 gas。uint []
(uint數組
)。先這麼聲明着,咱們將在下一章中填充函數體。zombiehelper.sol
pragma solidity ^0.4.19; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _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 []) { } }
Solidity 使用 storage
(存儲)是至關昂貴的,」寫入「操做尤爲貴。
這是由於,不管是寫入仍是更改一段數據, 這都將永久性地寫入區塊鏈。」永久性「啊!須要在全球數千個節點的硬盤上存入這些數據,隨着區塊鏈的增加,拷貝份數更多,存儲量也就越大。這是須要成本的!
爲了下降成本,不到萬不得已,避免將數據寫入存儲。這也會致使效率低下的編程邏輯 - 好比每次調用一個函數,都須要在 memory
(內存) 中重建一個數組,而不是簡單地將上次計算的數組給存儲下來以便快速查找。
在大多數編程語言中,遍歷大數據集合都是昂貴的。可是在 Solidity 中,使用一個標記了external view
的函數,遍歷比 storage
要便宜太多,由於 view
函數不會產生任何花銷。 (gas但是真金白銀啊!)。
咱們將在下一章討論 for
循環,如今咱們來看一下看如何如何在內存中聲明數組。
在數組後面加上 memory
關鍵字, 代表這個數組是僅僅在內存中建立,不須要寫入外部存儲,而且在函數調用結束時它就解散了。與在程序結束時把數據保存進 storage
的作法相比,內存運算能夠大大節省gas開銷 -- 把這數組放在view
裏用,徹底不用花錢。
如下是申明一個內存數組的例子:
function getArray() external pure returns(uint[]) { // 初始化一個長度爲3的內存數組 uint[] memory values = new uint[](3); // 賦值 values.push(1); values.push(2); values.push(3); // 返回數組 return values; }
這個小例子展現了一些語法規則,下一章中,咱們將經過一個實際用例,展現它和 for
循環結合的作法。
注意:內存數組 必須 用長度參數(在本例中爲3)建立。目前不支持
array.push()
之類的方法調整數組大小,在將來的版本可能會支持長度修改。
咱們要要建立一個名爲 getZombiesByOwner
的函數,它以uint []
數組的形式返回某一用戶所擁有的全部殭屍。
result
的uint [] memory
(內存變量數組)uint
類型數組。數組的長度爲該 _owner 所擁有的殭屍數量,這可經過調用 ownerZombieCount [_ owner]
來獲取。result
。目前它只是個空數列,咱們到下一章去實現它。zombiehelper.sol
pragma solidity ^0.4.19; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _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]); return result; } }
在以前的博文中,咱們提到過,函數中使用的數組是運行時在內存中經過 for
循環實時構建,而不是預先創建在存儲中的。
爲何要這樣作呢?
爲了實現 getZombiesByOwner
函數,一種「無腦式」的解決方案是在 ZombieFactory
中存入」主人「和」殭屍軍團「的映射。
mapping (address => uint[]) public ownerToZombies
而後咱們每次建立新殭屍時,執行 ownerToZombies[owner].push(zombieId)
將其添加到主人的殭屍數組中。而 getZombiesByOwner
函數也很是簡單:
function getZombiesByOwner(address _owner) external view returns (uint[]) { return ownerToZombies[_owner]; }
作法卻是簡單。但是若是咱們須要一個函數來把一頭殭屍轉移到另外一個主人名下(咱們必定會在後面的課程中實現的),又會發生什麼?
這個「換主」函數要作到:
可是第三步實在是太貴了!由於每挪動一頭殭屍,咱們都要執行一次寫操做。若是一個主人有20頭殭屍,而第一頭被挪走了,那爲了保持數組的順序,咱們得作19個寫操做。
因爲寫入存儲是 Solidity 中最費 gas 的操做之一,使得換主函數的每次調用都很是昂貴。更糟糕的是,每次調用的時候花費的 gas 都不一樣!具體還取決於用戶在原主軍團中的殭屍頭數,以及移走的殭屍所在的位置。以致於用戶都不知道應該支付多少 gas。
注意:固然,咱們也能夠把數組中最後一個殭屍往前挪來填補空槽,並將數組長度減小一。但這樣每作一筆交易,都會改變殭屍軍團的秩序。
因爲從外部調用一個 view 函數是免費的,咱們也能夠在 getZombiesByOwner 函數中用一個for循環遍歷整個殭屍數組,把屬於某個主人的殭屍挑出來構建出殭屍數組。那麼咱們的 transfer 函數將會便宜得多,由於咱們不須要挪動存儲裏的殭屍數組從新排序,整體上這個方法會更便宜,雖然有點反直覺。
for循環的語法在 Solidity 和 JavaScript 中相似。
來看一個建立偶數數組的例子:
function getEvens() pure external returns(uint[]) { uint[] memory evens = new uint[](5); // 在新數組中記錄序列號 uint counter = 0; // 在循環從1迭代到10: for (uint i = 1; i <= 10; i++) { // 若是 `i` 是偶數... if (i % 2 == 0) { // 把它加入偶數數組 evens[counter] = i; //索引加一, 指向下一個空的‘even’ counter++; } } return evens; }
這個函數將返回一個形爲 [2,4,6,8,10]
的數組。
咱們回到 getZombiesByOwner 函數, 經過一條 for 循環來遍歷 DApp 中全部的殭屍, 將給定的‘用戶id'與每頭殭屍的‘主人’進行比較,並在函數返回以前將它們推送到咱們的result 數組中。
就是這樣 - 這個函數能返回 _owner
所擁有的殭屍數組,不花一分錢 gas。
pragma solidity ^0.4.19; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _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; } }
截至目前,咱們只接觸到不多的 函數修飾符
。 要記住全部的東西很難,因此咱們來個概覽:
private
意味着它只能被合約內部調用; internal
就像 private
可是也能被繼承的合約調用; external
只能從合約外部調用;最後 public
能夠在任何地方調用,無論是內部仍是外部。view
告訴咱們運行這個函數不會更改和保存任何數據; pure
告訴咱們這個函數不但不會往區塊鏈寫數據,它甚至不從區塊鏈讀取數據。這兩種在被從合約外部調用的時候都不花費任何gas(可是它們在被內部其餘函數調用的時候將會耗費gas)。modifiers
,例如在第三課學習的: onlyOwner
和 aboveLevel
。 對於這些修飾符咱們能夠自定義其對函數的約束邏輯。這些修飾符能夠同時做用於一個函數定義上:
function test() external view onlyOwner anotherModifier { /* ... */ }
在這一章,咱們來學習一個新的修飾符 payable
.
payable
方法是讓 Solidity 和以太坊變得如此酷的一部分 —— 它們是一種能夠接收以太的特殊函數。
先放一下。當你在調用一個普通網站服務器上的API函數的時候,你沒法用你的函數傳送美圓——你也不能傳送比特幣。
可是在以太坊中, 由於錢 (以太), 數據 (事務負載), 以及合約代碼自己都存在於以太坊。你能夠在同時調用函數 並付錢給另一個合約。
這就容許出現不少有趣的邏輯, 好比向一個合約要求支付必定的錢來運行一個函數。
contract OnlineStore { function buySomething() external payable { // 檢查以肯定0.001以太發送出去來運行函數: require(msg.value == 0.001 ether); // 若是爲真,一些用來向函數調用者發送數字內容的邏輯 transferThing(msg.sender); } }
在這裏,msg.value
是一種能夠查看向合約發送了多少以太的方法,另外 ether
是一個內建單元。
這裏發生的事是,一些人會從 web3.js
調用這個函數 (從DApp的前端), 像這樣 :
// 假設 `OnlineStore` 在以太坊上指向你的合約: OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))
注意這個 value
字段, JavaScript 調用來指定發送多少(0.001)以太。若是把事務想象成一個信封,你發送到函數的參數就是信的內容。 添加一個 value 很像在信封裏面放錢 —— 信件內容和錢同時發送給了接收者。
注意: 若是一個函數沒標記爲
payable
, 而你嘗試利用上面的方法發送以太,函數將拒絕你的事務。
咱們來在殭屍遊戲裏面建立一個payable
函數。
假定在咱們的遊戲中,玩家能夠經過支付ETH來升級他們的殭屍。ETH將存儲在你擁有的合約中 —— 一個簡單明瞭的例子,向你展現你能夠經過本身的遊戲賺錢。
uint
,命名爲 levelUpFee
, 將值設定爲 0.001 ether
。levelUp
的函數。 它將接收一個 uint
參數 _zombieId
。 函數應該修飾爲 external
以及 payable
。require
確保 msg.value
等於 levelUpFee
。而後它應該增長殭屍的 level
: zombies[_zombieId].level++
。
zombiehelper.sol
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 函數 function levelUp(uint _zombieId) external payable { // 檢查以肯定0.001以太發送出去來運行函數: 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; } }
在上一節,咱們學習瞭如何向合約發送以太,那麼在發送以後會發生什麼呢?
在你發送以太以後,它將被存儲進以合約的以太坊帳戶中, 並凍結在哪裏 —— 除非你添加一個函數來從合約中把以太提現。
你能夠寫一個函數來從合約中提現以太,相似這樣:
contract GetPaid is Ownable { function withdraw() external onlyOwner { owner.transfer(this.balance); } }
注意咱們使用 Ownable
合約中的 owner
和 onlyOwner
,假定它已經被引入了。
你能夠經過 transfer
函數向一個地址發送以太, 而後 this.balance
將返回當前合約存儲了多少以太。 因此若是100個用戶每人向咱們支付1以太, this.balance
將是100以太。
你能夠經過 transfer
向任何以太坊地址付錢。 好比,你能夠有一個函數在 msg.sender
超額付款的時候給他們退錢:
uint itemFee = 0.001 ether; msg.sender.transfer(msg.value - itemFee);
或者在一個有賣家和賣家的合約中, 你能夠把賣家的地址存儲起來, 當有人買了它的東西的時候,把買家支付的錢發送給它 seller.transfer(msg.value)
。
有不少例子來展現什麼讓以太坊編程如此之酷 —— 你能夠擁有一個不被任何人控制的去中心化市場。
withdraw
函數,它應該幾乎和上面的GetPaid
同樣。a. 建立一個函數,名爲 setLevelUpFee
, 其接收一個參數 uint _fee
,是 external
並使用修飾符 onlyOwner
。
b. 這個函數應該設置 levelUpFee
等於 _fee
。
zombiehelper.sol
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. 在這裏建立 withdraw 函數 function withdraw() external onlyOwner { owner.transfer(this.balance); } // 2. 在這裏建立 setLevelUpFee 函數 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; } }
咱們新建一個攻擊功能合約,並將代碼放進新的文件中,引入上一個合約。
再來新建一個合約吧。熟能生巧。
若是你不記得怎麼作了, 查看一下 zombiehelper.sol
— 不過最好先試着作一下,檢查一下你掌握的狀況。
^0.4.19
.import
自 zombiehelper.sol
.contract
,命名爲 ZombieBattle
, 繼承自ZombieHelper
。函數體就先空着吧。zombiebattle.sol
pragma solidity ^0.4.19; import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { }
優秀的遊戲都須要一些隨機元素,那麼咱們在 Solidity 裏如何生成隨機數呢?
真正的答案是你不能,或者最起碼,你沒法安全地作到這一點。
咱們來看看爲何
用 keccak256
來製造隨機數
Solidity 中最好的隨機數生成器是 keccak256
哈希函數.
咱們能夠這樣來生成一些隨機數
// 生成一個0到100的隨機數: 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
、 以及一個自增數 nonce
(一個僅會被使用一次的數,這樣咱們就不會對相同的輸入值調用一次以上哈希函數了)。
而後利用 keccak
把輸入的值轉變爲一個哈希值, 再將哈希值轉換爲 uint
, 而後利用 % 100
來取最後兩位, 就生成了一個0到100之間隨機數了。
這個方法很容易被不誠實的節點攻擊
在以太坊上, 當你在和一個合約上調用函數的時候, 你會把它廣播給一個節點或者在網絡上的 transaction
節點們。 網絡上的節點將收集不少事務, 試着成爲第一個解決計算密集型數學問題的人,做爲「工做證實」,而後將「工做證實」(Proof of Work, PoW)和事務一塊兒做爲一個 block
發佈在網絡上。
一旦一個節點解決了一個PoW, 其餘節點就會中止嘗試解決這個 PoW, 並驗證其餘節點的事務列表是有效的,而後接受這個節點轉而嘗試解決下一個節點。
這就讓咱們的隨機數函數變得可利用了
咱們假設咱們有一個硬幣翻轉合約——正面你贏雙倍錢,反面你輸掉全部的錢。假如它使用上面的方法來決定是正面仍是反面 (random >= 50
算正面, random < 50
算反面)。
若是我正運行一個節點,我能夠 只對我本身的節點 發佈一個事務,且不分享它。 我能夠運行硬幣翻轉方法來偷窺個人輸贏 — 若是我輸了,我就不把這個事務包含進我要解決的下一個區塊中去。我能夠一直運行這個方法,直到我贏得了硬幣翻轉並解決了下一個區塊,而後獲利。
因此咱們該如何在以太坊上安全地生成隨機數呢 ?
由於區塊鏈的所有內容對全部參與者來講是透明的, 這就讓這個問題變得很難,它的解決方法不在本課程討論範圍,你能夠閱讀 這個 StackOverflow 上的討論 來得到一些主意。 一個方法是利用 oracle 來訪問以太坊區塊鏈以外的隨機數函數。
固然, 由於網絡上成千上萬的以太坊節點都在競爭解決下一個區塊,我能成功解決下一個區塊的概率很是之低。 這將花費咱們巨大的計算資源來開發這個獲利方法 — 可是若是獎勵異常地高(好比我能夠在硬幣翻轉函數中贏得 1個億), 那就很值得去攻擊了。
因此儘管這個方法在以太坊上不安全,在實際中,除非咱們的隨機函數有一大筆錢在上面,你遊戲的用戶通常是沒有足夠的資源去攻擊的。
由於在這個教程中,咱們只是在編寫一個簡單的遊戲來作演示,也沒有真正的錢在裏面,因此咱們決定接受這個不足之處,使用這個簡單的隨機數生成函數。可是要謹記它是不安全的。
咱們來實現一個隨機數生成函數,好來計算戰鬥的結果。雖然這個函數一點兒也不安全。
randNonce
的 uint
,將其值設置爲 0。randMod
(random-modulus)。它將做爲internal
函數,傳入一個名爲 _modulus
的 uint,並 returns
一個 uint
。randNonce
加一, (使用 randNonce++ 語句)。zombiehelper.sol
pragma solidity ^0.4.19; 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; } }
咱們的合約已經有了一些隨機性的來源,能夠用進咱們的殭屍戰鬥中去計算結果。
咱們的殭屍戰鬥看起來將是這個流程:
這有一大堆的邏輯須要處理,咱們將把這些步驟分解到接下來的課程中去。
uint
類型的變量,命名爲 attackVictoryProbability
, 將其值設定爲 70。attack
的函數。它將傳入兩個參數: _zombieId
(uint 類型) 以及 _targetId
(也是 uint)。它將是一個 external
函數。zombiehelper.sol
pragma solidity ^0.4.19; import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { uint randNonce = 0; // 在這裏建立 attackVictoryProbability 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 { } }