第1章: 可支付
截至目前糠赦,我們只接觸到很少的 函數(shù)修飾符盐数。 要記住所有的東西很難边琉,所以我們來個概覽:
我們有決定函數(shù)何時和被誰調(diào)用的可見性修飾符:
private
意味著它只能被合約內(nèi)部調(diào)用抑进;internal
就像private
但是也能被繼承的合約調(diào)用戈泼;external
只能從合約外部調(diào)用骂倘;最后public
可以在任何地方調(diào)用眼滤,不管是內(nèi)部還是外部。我們也有狀態(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)堰塌。然后我們有了自定義的
modifiers
,例如在第三課學習的:onlyOwner
和aboveLevel
分衫。 對于這些修飾符我們可以自定義其對函數(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將存儲在你擁有的合約中 —— 一個簡單明了的例子,向你展示你可以通過自己的游戲賺錢空免。
定義一個 uint 空另,命名為 levelUpFee, 將值設定為 0.001 ether。
定義一個名為 levelUp 的函數(shù)蹋砚。 它將接收一個 uint 參數(shù) _zombieId扼菠。 函數(shù)應該修飾為 external 以及 payable。
這個函數(shù)首先應該 require 確保 msg.value 等于 levelUpFee坝咐。
然后它應該增加僵尸的 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
合約中的 owner
和 onlyOwner
咪辱,假定它已經(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)演習
在我們的合約里創(chuàng)建一個
withdraw
函數(shù)庇勃,它應該幾乎和上面的GetPaid
一樣。-
以太的價格在過去幾年內(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
— 不過最好先試著做一下旧困,檢查一下你掌握的情況。
在文件開頭定義 Solidity 的版本
^0.4.19
.import
自zombiehelper.sol
.聲明一個新的
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ù)一點兒也不安全渠抹。
給我們合約一個名為
randNonce
的uint
樱报,將其值設置為0
葬项。建立一個函數(shù),命名為
randMod
(random-modulus)迹蛤。它將作為internal
函數(shù)民珍,傳入一個名為_modulus
的uint
,并returns
一個uint
盗飒。這個函數(shù)首先將為
randNonce
加一嚷量, (使用randNonce++
語句)。最后逆趣,它應該 (在一行代碼中) 計算
now
,msg.sender
, 以及randNonce
的keccak256
哈希值并轉(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)演習
給我們合約一個
uint
類型的變量岸霹,命名為attackVictoryProbability
, 將其值設定為70
。創(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姚糊。
-
創(chuàng)建一個
modifier
, 命名為ownerOf
授舟。它將傳入一個參數(shù)救恨,_zombieId
(一個uint
)。它的函數(shù)體應該
require msg.sender
等于zombieToOwner[_zombieId]
释树, 然后繼續(xù)這個函數(shù)剩下的內(nèi)容肠槽。 如果你忘記了修飾符的寫法,可以參考zombiehelper.sol
奢啥。 將這個函數(shù)的
feedAndMultiply
定義修改為其使用修飾符ownerOf
秸仙。現(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)演習
修改
changeName()
使其使用ownerOf
修改
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)演習
將
ownerOf
修飾符添加到attack
來確保調(diào)用者擁有_zombieId
.-
我們的函數(shù)所需要做的第一件事就是獲得一個雙方僵尸的
storage
指針拟杉, 這樣我們才能很方便和它們交互:a. 定義一個
Zombie storage
命名為myZombie
庄涡,使其值等于zombies[_zombieId]
。b. 定義一個
Zombie storage
命名為enemyZombie
搬设, 使其值等于zombies[_targetId]
穴店。 我們將用一個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)體中,將其命名為 winCount
和 lossCount
夜畴。
我們跳回 zombiefactory.sol
, 將這些屬性添加進 Zombie
結(jié)構(gòu)體.
實戰(zhàn)演習
-
修改
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)税灌,否則我們就是安全的均函。
-
現(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章: 僵尸勝利了 ??
有了 winCount
和 lossCount
粘秆,我們可以根據(jù)僵尸哪個僵尸贏了戰(zhàn)斗來更新它們了如迟。
在第六章我們計算出來一個0到100的隨機數(shù)。現(xiàn)在讓我們用那個數(shù)來決定那誰贏了戰(zhàn)斗翻擒,并以此更新我們的狀態(tài)氓涣。
實戰(zhàn)演習
創(chuàng)建一個 if 語句來檢查 rand 是不是 小于或者等于 attackVictoryProbability牛哺。
-
如果以上條件為
true
, 我們的僵尸就贏了劳吠!所以:a. 增加
myZombie
的winCount
引润。b. 增加
myZombie
的level
。 (升級了啦!!!!!!!)c. 增加
enemyZombie
的lossCount
. (輸家!!!!!! ?? ?? ??)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)演習
-
添加一個
else
語句。 若我們的僵尸輸了:a. 增加
myZombie
的lossCount
缀旁。b. 增加
enemyZombie
的winCount
记劈。 在
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)斗之后:
你的僵尸將會升級
你僵尸的
winCount
將會增加你將為你的僵尸大軍獲得一個新的僵尸
繼續(xù)測試戰(zhàn)斗,玩夠了以后點擊下一章來完成本課距贷。
示例