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一个非常好玩的网站,我做的这三个合约也是从中学习而来,希望能帮助到大家: 编游戏的同时学习以太坊 DApp 开发.
|