第1章: 可支付
截至目前,我們只接觸到很少的 函數(shù)修飾符。 要記住所有的東西很難,所以我們來個概覽:
我們有決定函數(shù)何時和被誰調用的可見性修飾符:
private意味著它只能被合約內(nèi)部調用;internal就像private但是也能被繼承的合約調用;external只能從合約外部調用;最后public可以在任何地方調用,不管是內(nèi)部還是外部。我們也有狀態(tài)修飾符, 告訴我們函數(shù)如何和區(qū)塊鏈交互:
view告訴我們運行這個函數(shù)不會更改和保存任何數(shù)據(jù);pure告訴我們這個函數(shù)不但不會往區(qū)塊鏈寫數(shù)據(jù),它甚至不從區(qū)塊鏈讀取數(shù)據(jù)。這兩種在被從合約外部調用的時候都不花費任何gas(但是它們在被內(nèi)部其他函數(shù)調用的時候將會耗費gas)。然后我們有了自定義的
modifiers,例如在第三課學習的:onlyOwner和aboveLevel。 對于這些修飾符我們可以自定義其對函數(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將存儲在你擁有的合約中 —— 一個簡單明了的例子,向你展示你可以通過自己的游戲賺錢。
定義一個 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ā)送以太之后,它將被存儲進以合約的以太坊賬戶中, 并凍結在哪里 —— 除非你添加一個函數(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ù),這樣我們就不會對相同的輸入值調用一次以上哈希函數(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ù)一點兒也不安全。
給我們合約一個名為
randNonce的uint,將其值設置為0。建立一個函數(shù),命名為
randMod(random-modulus)。它將作為internal函數(shù),傳入一個名為_modulus的uint,并returns一個uint。這個函數(shù)首先將為
randNonce加一, (使用randNonce++語句)。最后,它應該 (在一行代碼中) 計算
now,msg.sender, 以及randNonce的keccak256哈希值并轉換為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)演習
給我們合約一個
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章: 重構通用邏輯
不管誰調用我們的 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。
-
創(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章: 更多重構
在 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章: 回到攻擊!
重構完成了,回到 zombieattack.sol。
繼續(xù)來完善我們的 attack 函數(shù), 現(xiàn)在我們有了 ownerOf 修飾符來用了。
實戰(zhàn)演習
將
ownerOf修飾符添加到attack來確保調用者擁有_zombieId.-
我們的函數(shù)所需要做的第一件事就是獲得一個雙方僵尸的
storage指針, 這樣我們才能很方便和它們交互:a. 定義一個
Zombie storage命名為myZombie,使其值等于zombies[_zombieId]。b. 定義一個
Zombie storage命名為enemyZombie, 使其值等于zombies[_targetId]。 我們將用一個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 結構體中,將其命名為 winCount 和 lossCount。
我們跳回 zombiefactory.sol, 將這些屬性添加進 Zombie 結構體.
實戰(zhàn)演習
-
修改
Zombie結構體,添加兩個屬性:a.
winCount, 一個uint16b.
lossCount, 也是一個uint16
注意: 記住, 因為我們能在結構體中包裝uint, 我們打算用適合我們的最小的 uint。 一個 uint8 太小了, 因為 2^8 = 256 —— 如果我們的僵尸每天都作戰(zhàn),不到一年就溢出了。但是 2^16 = 65536 (uint16)—— 除非一個僵尸連續(xù)179年每天作戰(zhàn),否則我們就是安全的。
-
現(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章: 僵尸勝利了 ??
有了 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里查看調用它的語句。 對于第三個參數(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)斗,玩夠了以后點擊下一章來完成本課。
示例