赞
踩
Solidity实现智能合约——Solidity高级理论(三)
在上一节当中我们实现了一个可以初始化创建宠物,并让它进食生成一个新宠物的功能,接下来我们继续对这个系统功能进行完善。
在这一节当中我们将会完成以下的功能:为宠物添加俩个新属性等级和冷却时间,不知道大家有没有发现上一节宠物可以不限制的进食,在这一节当中我们来给它们做一个进食时间的限制,此外我们可以花费gas让我们的宠物升级,对于达到一定等级的宠物我们可以增加一些新权限:对自己宠物进行更改名字或者是更改DNA。
话不多说,我们直接开始吧。
Ownable 合约:
1,合约创建,构造函数先行,将其 owner 设置为msg.sender(其部署者)
2,为它加上一个修饰符 onlyOwner,它会限制陌生人的访问,将访问某些函数的权限锁定在 owner 上。
3,允许将合约所有权转让给他人。
contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); function Ownable() public { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner); _; } function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } }
首先,我们新建一个 ownable.sol文件,将上面的代码复制到这个文件当中,然后修改 AnimalFactory 合约, 让它继承自 Ownable,记得要将 ownable.sol导入 AnimalFactory.sol文件当中。
回到AnimalFactory.sol,为 Animal 结构体 添加两个属性:level(uint32)和readyTime(uint32)。因为希望同类型数据打成一个包,所以把它们放在结构体的末尾。
struct Animal{
string name;
uint dna;
uint32 level;
uint32 readyTime;
}
声明一个名为 cooldownTime 的uint,并将其设置为30 seconds,我们给 Animal 结构体中添加 level 和 readyTime 两个参数,所以现在创建一个新的 Animal 结构体时,需要修改 _createAnimal(),在其中把新旧参数都初始化一下。
修改 animals.push那一行, 添加2个参数:1(表示当前的 level )和uint32(now + cooldownTime)(现在+冷却时间,表示下次允许攻击的时间 readyTime)。
注意:必须使用 uint32(…) 进行强制类型转换,因为now返回类型 uint256。所以我们需要明确将它转换成一个uint32 类型的变量。
uint dnaDigits = 16; //宠物DNA位数
uint dnaLength = 10**dnaDigits;
uint cooldownTime = 30 seconds;
function _createAnimal(string _name,uint _dna) internal{
uint animalId = animals.push(Animal(_name,_dna,1,uint32(now + cooldownTime)))-1;
// 将当前地址对应此时的id
AnimalToOwner[animalId] = msg.sender;
// 这个地址下的宠物数量加一
ownerAnimalCount[msg.sender]++;
NewAnimal(animalId, _name, _dna);
}
接下来我们回到AnimalFeeding.sol文件当中
先定义一个 _triggerCooldown 函数。它要求一个参数, _Animal,表示一某个宠物的存储指针。这个函数可见性设置为 internal。
在函数中,把 _Animal.readyTime设置为 uint32(now + cooldownTime)。
接下来,创建一个名为 _isReady 的函数。这个函数的参数也是名为 _Animal 的Animal storage。这个功能只具有 internal 可见性,并返回一个 bool 值。
函数计算返回(_Animal.readyTime <= now),值为 true 或 false。这个功能的目的是判断下次允许猎食的时间是否已经到了。
// 重新计算冷却时间
function _triggerCooldown(Animal storage _Animal) internal {
_Animal.readyTime = uint32(now + cooldownTime);
}
// 是否到了宠物的冷却时间
function _isReady(Animal storage _Animal) internal view returns (bool) {
return (_Animal.readyTime <= now);
}
feedAndGrow 过程需要参考 cooldownTime。首先,在找到 myAnimal 之后,添加一个 require 语句来检查 _isReady() 并将 myAnimal 传递给它。这样用户必须等到宠物的冷却周期结束后才能执行feedAndGrow功能。
在函数结束时,调用 _triggerCooldown( myAnimal),标明进食行为触发了宠物新的冷却周期。
// 实现进食功能 宠物 食物DNA function feedAndGrow(uint _AnimalId,uint _targetDna) internal { // 确保当前的宠物是自己的 require(msg.sender == AnimalToOwner[_AnimalId]); // 获取这个宠物的DNA Animal storage myAnimal = animals[_AnimalId]; //必须等到宠物的冷却周期 require(_isReady(myAnimal)); _targetDna = _targetDna % dnaLength; uint newDna = (myAnimal.dna + _targetDna) / 2; newDna = newDna - newDna % 100 + 99; _createAnimal("No-one", newDna); // 触发了宠物新的冷却周期 _triggerCooldown(myAnimal); }
在AnimalHelper中,创建一个名为 aboveLevel 的modifier,它接收2个参数,_level(uint类型) 以及_AnimalId(uint类型),运用函数逻辑确保宠物animals[AnimalId].level大于或等于_level。修饰符的最后一行为“;”表示修饰符调用结束后返回,并执行调用函数剩下的部分。
modifier aboveLevel(uint _level, uint _AnimalId) {
require(animals[_AnimalId].level >= _level);
_;
}
添加一些使用aboveLevel修饰符的函数,作为达到level的奖励。激励玩家们去升级他们的宠物。
// 当宠物等级达到2级时就可以自己改名
function changeName(uint _AnimalId, string _newName) external aboveLevel(2,_AnimalId){
require(msg.sender == AnimalToOwner[_AnimalId]);
animals[_AnimalId].name = _newName;
}
// 当宠物等级达到4级时就可以自己改DNA
function changeDna(uint _AnimalId, uint _newDna) external aboveLevel(4,_AnimalId) {
require(msg.sender == AnimalToOwner[_AnimalId]);
animals[_AnimalId].dna = _newDna;
}
创建一个名为getAnimalsByOwner 的新函数。它有一个名为 _owner 的 address 类型的参数。将其申明为 external view 函数,这样当玩家从 web3.js 中调用它时,不需要花费任何 gas,函数需要返回一个uint []声明一个result的uint[] memory(内存变量数组),将其设置为一个新的 uint 类型数组。数组的长度为该 _owner 所拥有的宠物数量,这可通过调用 ownerAnimalCount [_owner] 来获取。
function getAnimalsByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerAnimalCount[_owner]);
uint counter = 0;
for (uint i = 0; i < animals.length; i++) {
if (AnimalToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}
最后我们需要实现的效果:展示宠物的冷却周期,成功实现getAnimalsByOwner函数
接下来我将完整的AnimalHelper.sol中的合约代码贴在下方
pragma solidity ^0.4.19; import "./AnimalFeeding.sol"; contract AnimalHelper is AnimalFeeding { modifier aboveLevel(uint _level, uint _AnimalId) { require(animals[_AnimalId].level >= _level); _; } // 当宠物等级达到2级时就可以自己改名 function changeName(uint _AnimalId, string _newName) external aboveLevel(2,_AnimalId){ require(msg.sender == AnimalToOwner[_AnimalId]); animals[_AnimalId].name = _newName; } // 当宠物等级达到4级时就可以自己改DNA function changeDna(uint _AnimalId, uint _newDna) external aboveLevel(4,_AnimalId) { require(msg.sender == AnimalToOwner[_AnimalId]); animals[_AnimalId].dna = _newDna; } function getAnimalsByOwner(address _owner) external view returns(uint[]) { uint[] memory result = new uint[](ownerAnimalCount[_owner]); uint counter = 0; for (uint i = 0; i < animals.length; i++) { if (AnimalToOwner[i] == _owner) { result[counter] = i; counter++; } } return result; } }
AnimalFeeding.sol的完整代码
pragma solidity ^0.4.19; import "./AnimalIncubators.sol"; contract AnimalFeeding is AnimalFactory{ // 重新计算冷却时间 function _triggerCooldown(Animal storage _Animal) internal { _Animal.readyTime = uint32(now + cooldownTime); } // 是否到了宠物的冷却时间 function _isReady(Animal storage _Animal) internal view returns (bool) { return (_Animal.readyTime <= now); } // 实现进食功能 宠物 食物DNA function feedAndGrow(uint _AnimalId,uint _targetDna) internal { // 确保当前的宠物是自己的 require(msg.sender == AnimalToOwner[_AnimalId]); // 获取这个宠物的DNA Animal storage myAnimal = animals[_AnimalId]; //必须等到宠物的冷却周期 require(_isReady(myAnimal)); _targetDna = _targetDna % dnaLength; uint newDna = (myAnimal.dna + _targetDna) / 2; newDna = newDna - newDna % 100 + 99; _createAnimal("No-one", newDna); // 触发了宠物新的冷却周期 _triggerCooldown(myAnimal); } function _catchFood(uint _name) internal pure returns (uint){ uint rand = uint(keccak256(_name)); return rand; } function feedOnFood(uint _AnimalId,uint _FoodId) public{ uint foodDna = _catchFood(_FoodId); feedAndGrow(_AnimalId,foodDna); } }
AnimalFactory.sol中的完整代码
pragma solidity ^0.4.19; import "./ownable.sol"; contract AnimalFactory is Ownable { uint dnaDigits = 16; //宠物DNA位数 uint dnaLength = 10**dnaDigits; uint cooldownTime = 30 seconds; struct Animal{ string name; uint dna; uint32 level; uint32 readyTime; } Animal [] public animals; mapping (uint => address) public AnimalToOwner; mapping (address => uint) ownerAnimalCount; // 孵化宠物函数 // function hatchAnimal(string name,uint dna) public{ // animals.push(Animal(name,dna)); // } event NewAnimal(uint AnimalId,string name,uint dna); function _createAnimal(string _name,uint _dna) internal{ uint animalId = animals.push(Animal(_name,_dna,1,uint32(now + cooldownTime)))-1; // animals.push(Animal(_name,_dna)); // 将当前地址对应此时的id AnimalToOwner[animalId] = msg.sender; // 这个地址下的宠物数量加一 ownerAnimalCount[msg.sender]++; NewAnimal(animalId, _name, _dna); } function _generateRandomDna(string _str) private view returns(uint){ uint rand = uint(keccak256(_str)); return rand % dnaLength; } function createRandomAnimal(string _name) public { // 用户只能创建一次初始宠物 require(ownerAnimalCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); _createAnimal(_name, randDna); } }
下一章我会给大家继续完善这个系统的功能,实现一个宠物战斗的功能
链接: Solidity实现智能合约——宠物战斗(四).
最后给大家介绍一下学习solidity一个非常好玩的网站,我做的这三个合约也是从中学习而来,希望能帮助到大家: 编游戏的同时学习以太坊 DApp 开发.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。