摘要:接上篇文章,這里繼續學習高級理論。將這個函數的定義修改為其使用修飾符。我們將用一個到的隨機數來確定我們的戰斗結果。在這個教程中,簡單起見我們將這個狀態保存在結構體中,將其命名為和。在第六章我們計算出來一個到的隨機數。
接上篇文章,這里繼續學習Solidity高級理論。一、重構通用邏輯
不管誰調用我們的 attack 函數 —— 我們想確保用戶的確擁有他們用來攻擊的僵尸。如果你能用其他人的僵尸來攻擊將是一個很大的安全問題。
你能想一下我們如何添加一個檢查步驟來看看調用這個函數的人就是他們傳入的 _zombieId 的擁有者么?
想一想,看看你能不能自己找到一些答案。
花點時間…… 參考我們前面課程的代碼來獲得靈感。
答案我們在前面的課程里面已經做過很多次這樣的檢查了。 在 changeName(), changeDna(), 和 feedAndMultiply()里,我們做過這樣的檢查:
require(msg.sender == zombieToOwner[_zombieId]);
這和我們 attack 函數將要用到的檢查邏輯是相同的。 正因我們要多次調用這個檢查邏輯,讓我們把它移到它自己的 modifier 中來清理代碼并避免重復編碼。
實戰演練我們回到了 zombiefeeding.sol, 因為這是我們第一次調用檢查邏輯的地方。讓我們把它重構進它自己的 modifier。
1、創建一個 modifier, 命名為 ownerOf。它將傳入一個參數, _zombieId (一個 uint)。
它的函數體應該 require msg.sender 等于 zombieToOwner[_zombieId], 然后繼續這個函數剩下的內容。 如果你忘記了修飾符的寫法,可以參考 zombiehelper.sol。
2、將這個函數的 feedAndMultiply 定義修改為其使用修飾符 ownerOf。
3、現在我們使用 modifier了,你可以刪除這行了: require(msg.sender == zombieToOwner[_zombieId]);
zombiefeeding.sol
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. 在這里創建 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. 在函數定義時增加 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"); } }更多重構
在 zombiehelper.sol 里有幾處地方,需要我們實現我們新的 modifier—— ownerOf。
實戰演練:
1、修改 changeName() 使其使用 ownerOf
2、修改 changeDna() 使其使用 ownerOf
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); _; } 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` 修改這個函數: function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) { // require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; } // 2. 對這個函數做同樣的事: 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; } }二、回到攻擊
重構完成了,回到我們上節博文學習的游戲對戰 zombieattack.sol。
繼續來完善我們的 attack 函數, 現在我們有了 ownerOf 修飾符來用了。
實戰演練1、將 ownerOf 修飾符添加到 attack 來確保調用者擁有_zombieId.
2、我們的函數所需要做的第一件事就是獲得一個雙方僵尸的 storage 指針, 這樣我們才能很方便和它們交互:
a. 定義一個 Zombie storage 命名為 myZombie,使其值等于 zombies[_zombieId]。
b. 定義一個 Zombie storage 命名為 enemyZombie, 使其值等于 zombies[_targetId]。
3、我們將用一個0到100的隨機數來確定我們的戰斗結果。 定義一個 uint,命名為 rand, 設定其值等于 randMod 函數的返回值,此函數傳入 100作為參數。
zombieattack.sol
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. 在這里開始定義函數 Zombie storage myZombie = zombies[_zombieId]; Zombie storage enemyZombie = zombies[_targetId]; uint rand = randMod(100); } }三、輸贏排行榜
對我們的僵尸游戲來說,我們將要追蹤我們的僵尸輸贏了多少場。有了這個我們可以在游戲里維護一個 "僵尸排行榜"。
有多種方法在我們的DApp里面保存一個數值 — 作為一個多帶帶的映射,作為一個“排行榜”結構體,或者保存在 Zombie 結構體內。
每個方法都有其優缺點,取決于我們打算如何和這些數據打交道。在這個教程中,簡單起見我們將這個狀態保存在 Zombie 結構體中,將其命名為 winCount 和 lossCount。
我們跳回 zombiefactory.sol, 將這些屬性添加進 Zombie 結構體.
實戰演練實戰演習
1、修改 Zombie 結構體,添加兩個屬性:
a. winCount, 一個 uint16
b. lossCount, 也是一個 uint16
注意: 記住, 因為我們能在結構體中包裝uint, 我們打算用適合我們的最小的 uint。 一個 uint8 太小了, 因為 2^8 = 256 —— 如果我們的僵尸每天都作戰,不到一年就溢出了。但是 2^16 = 65536 (uint16)—— 除非一個僵尸連續179年每天作戰,否則我們就是安全的。
2、現在我們的 Zombie 結構體有了新的屬性, 我們需要修改 _createZombie() 中的函數定義。
修改僵尸生成定義,讓每個新僵尸都有 0 贏和 0 輸。
zombiefactory.sol
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. 在這里修改修改新僵尸的創建: 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); } }四、更新輸贏狀態
有了 winCount 和 lossCount,我們可以根據僵尸哪個僵尸贏了戰斗來更新它們了。
在第六章我們計算出來一個0到100的隨機數。現在讓我們用那個數來決定那誰贏了戰斗,并以此更新我們的狀態。
實戰演練1、創建一個 if 語句來檢查 rand 是不是 小于或者等于 attackVictoryProbability。
2、如果以上條件為 true, 我們的僵尸就贏了!所以:
a. 增加 myZombie 的 winCount。
b. 增加 myZombie 的 level。 (升級了啦!!!!!!!)
c. 增加 enemyZombie 的 lossCount. (輸家!!!!!!)
d. 運行 feedAndMultiply 函數。 在 zombiefeeding.sol 里查看調用它的語句。 對于第三個參數 (_species),傳入字符串 "zombie". (現在它實際上什么都不做,不過在稍后, 如果我們愿意,可以添加額外的方法,用來制造僵尸變的僵尸)。
zombieattack.sol
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"); } } }五、失敗觸發冷卻
我們已經編寫了你的僵尸贏了之后會發生什么, 該看看 輸了 的時候要怎么做了。
在我們的游戲中,僵尸輸了后并不會降級 —— 只是簡單地給 lossCount 加一,并觸發冷卻,等待一天后才能再次參戰。
要實現這個邏輯,我們需要一個 else 語句。
else 語句和 JavaScript 以及很多其他語言的 else 語句一樣。
if (zombieCoins[msg.sender] > 100000000) { // 你好有錢!!! } else { // 我們需要更多的僵尸幣... }實戰演練
1、添加一個 else 語句。 若我們的僵尸輸了:
a. 增加 myZombie 的 lossCount。
b. 增加 enemyZombie 的 winCount。
2、在 else 最后, 對 myZombie 運行 _triggerCooldown 方法。這讓每個僵尸每天只能參戰一次。
zombieattack.sol
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); } } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24130.html
摘要:第一個例子,在你把智能協議傳上以太坊之后,它就變得不可更改這種永固性意味著你的代碼永遠不能被調整或更新。允許將合約所有權轉讓給他人。為何要來驅動以太坊就像一個巨大緩慢但非常安全的電腦。 通過前邊的 Solidity 基礎語法學習,我們已經有了Solidity編程經驗,在這節就要學學 Ethereum 開發的技術細節,編寫真正的 DApp 時必知的:智能協議的所有權,Gas的花費,代碼優...
摘要:接上篇文章,這里繼續學習高級理論。實戰演練我們來寫一個返回某玩家的整個僵尸軍團的函數。但這樣每做一筆交易,都會改變僵尸軍團的秩序。在這里開始五可支付截至目前,我們只接觸到很少的函數修飾符。 接上篇文章,這里繼續學習Solidity高級理論。 一、深入函數修飾符 接下來,我們將添加一些輔助方法。我們為您創建了一個名為 zombiehelper.sol 的新文件,并且將 zombiefee...
摘要:和比特幣協議有所不同的是,以太坊的設計十分靈活,極具適應性。超級賬本區塊鏈的商業應用超級賬本超級賬本是基金會下的眾多項目中的一個。證書頒發機構負責簽發撤 showImg(https://segmentfault.com/img/bV2ge9?w=900&h=385); 從比特幣開始 一個故事告訴你比特幣的原理及運作機制 這篇文章的定位會比較科普,盡量用類比的方法將比特幣的基本原理講出來...
摘要:以太坊開發高級語言學習。地址以太坊區塊鏈由賬戶組成,你可以把它想象成銀行賬戶。使用很安全,因為它具有以太坊區塊鏈的安全保障除非竊取與以太坊地址相關聯的私鑰,否則是沒有辦法修改其他人的數據的。 以太坊開發高級語言學習。 一、映射(Mapping)和地址(Address) 我們通過給數據庫中的僵尸指定主人, 來支持多玩家模式。 如此一來,我們需要引入2個新的數據類型:mapping(映射)...
閱讀 2167·2021-11-24 09:39
閱讀 2781·2021-07-29 13:49
閱讀 2322·2019-08-29 14:15
閱讀 2233·2019-08-29 12:40
閱讀 3312·2019-08-26 13:42
閱讀 632·2019-08-26 12:13
閱讀 2065·2019-08-26 11:41
閱讀 3345·2019-08-23 18:32