Solidity实现智能合约——宠物进食系统(二)
在上一节当中我们实现了创建宠物的功能,接下来将继续完善功能,让我们的宠物可以进食。
为了存储宠物的所有权,我们会使用到两个映射:一个记录宠物拥有者的地址,另一个记录某地址所拥有宠物的数量。
创建一个叫做 AnimalToOwner 的映射。其键是一个uint(我们将根据它的 id 存储和查找宠物),值为 address。映射属性为public。
创建一个名为 ownerAnimalCount的映射,其中键是 address,值是 uint。
mapping (uint => address) public AnimalToOwner;
mapping (address => uint) ownerAnimalCount;
修改上一节当中的 _createAnimal方法,将宠物分配给函数调用者
在得到新的宠物 id 后,更新 AnimalToOwner 映射,在 id 下面存入 msg.sender。然后,我们为这个 msg.sender 名下的 ownerAnimalCount 加 1。
function _createAnimal(string _name,uint _dna) internal{
uint animalId = animals.push(Animal(_name,_dna))-1;
// 将当前地址对应此时的id
AnimalToOwner[animalId] = msg.sender;
// 这个地址下的宠物数量加一
ownerAnimalCount[msg.sender]++;
NewAnimal(animalId, _name, _dna);
}
我们不希望用户通过反复调用 createRandomAnimal 来创建无限多个宠物 ,我们可以使用了 require 来确保这个函数只有在每个用户第一次调用它的时候执行,用以创建初始宠物。在 createRandomAnimal 的前面放置 require 语句。 使得函数先检查 ownerAnimalCount[msg.sender]的值为 0 ,不然就抛出一个错误。
function createRandomAnimal(string _name) public {
// 用户只能创建一次初始宠物
require(ownerAnimalCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
_createAnimal(_name, randDna);
}
接下来为了不让代码过于冗余,我们在AnimalFeeding.sol当中新建一个合约 AnimalFeeding并让它继承上一节当中的AnimalFactory合约
pragma solidity ^0.4.19;
contract AnimalFeeding is AnimalFactory {
}
.将 AnimalFactory.sol 导入到我们的新文件AnimalFeeding.sol 中。
pragma solidity ^0.4.19;
import "./AnimalFactory.sol";
contract AnimalFeeding is AnimalFactory{
}
这时候我们就可以给宠物增加“猎食”和“繁殖”功能了!当一个宠物进食时,它自身的DNA将与食物的DNA结合在一起,形成一个新的宠物DNA。
创建一个名为feedAndGrow 的函数。 使用两个参数: _AnimalId( uint类型 )和_targetDna (也是 uint 类型)。 设置属性为 public 的。
我们不希望别人用我们的宠物去进食。 首先,我们确保对自己宠物的所有权。 通过添加一个require 语句来确保 msg.sender 只能是这个宠物的主人(类似于我们在 createRandomAnimal 函数中做过的那样)。
为了获取这个宠物的DNA,我们的函数需要声明一个名为 myAnimal 数据类型为Animal的本地变量(这是一个 storage 型的指针)。 将其值设定为在animals 数组中索引为_AnimalId所指向的值。
function feedAndGrow(uint _AnimalId,uint _targetDna)public {
// 确保当前的宠物是自己的
require(msg.sender == AnimalToOwner[_AnimalId]);
// 获取这个宠物的DNA
Animal storage myAnimal = animals[_AnimalId];
}
当获取食物的DNA时,我们就可以让宠物进食,让宠物DNA和食物DNA结合生成新的宠物DNA,另外我们也可以给新宠物起名。这里只是将宠物DNA和食物DNA算了平均值并把最后两位数字替换成了99,最后宠物的新名字为"No-one"。
// 实现进食功能 宠物 食物DNA
function feedAndGrow(uint _AnimalId,uint _targetDna)public {
// 确保当前的宠物是自己的
require(msg.sender == AnimalToOwner[_AnimalId]);
// 获取这个宠物的DNA
Animal storage myAnimal = animals[_AnimalId];
_targetDna = _targetDna % dnaLength;
uint newDna = (myAnimal.dna + _targetDna) / 2;
newDna = newDna - newDna % 100 + 99;
_createAnimal("No-one", newDna);
}
上面的feedAndGrow函数需要传入的参数为宠物ID和食物DNA,宠物ID我们可以拿到,那么食物DNA我们该如何获取呢,接下来就要创建一个生成食物DNA的函数。这个函数的功能很简单,调用这个函数传入uint值,通过一个hash函数将这个uint生成一个hash值,而我们最后只会取这个hash值的后16位作为食物的DNA
function _catchFood(uint _name) internal pure returns (uint){
uint rand = uint(keccak256(_name));
return rand;
}
最后就是通过一个feedOnFood函数来调用上面的俩个部件来实现我们想要的功能:宠物进食可以生成新的宠物名和新的DNA(末尾俩位数字为99),而且我们初始只能创建一个宠物。
最后贴上我们的完整代码
pragma solidity ^0.4.19;
import "./AnimalFactory.sol";
contract AnimalFeeding is AnimalFactory{
// 实现进食功能 宠物 食物DNA
function feedAndGrow(uint _AnimalId,uint _targetDna)public {
// 确保当前的宠物是自己的
require(msg.sender == AnimalToOwner[_AnimalId]);
// 获取这个宠物的DNA
Animal storage myAnimal = animals[_AnimalId];
_targetDna = _targetDna % dnaLength;
uint newDna = (myAnimal.dna + _targetDna) / 2;
newDna = newDna - newDna % 100 + 99;
_createAnimal("No-one", newDna);
}
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);
}
}
接下来给大家演示一下代码的功能
部署成功之后我们可以先创建我们的宠物drogon
此时我们还可以继续尝试初始创建第二个宠物cat,这时候你就会发现初始化失败,说明我们定义的初始化一个宠物的功能实现了。
接下来给我们的宠物进食。发现喂食成功,此时我们可以来查看我们的数组当中的宠物
我们的第一个初始化宠物drogon
drogon进食后进化成的第二个宠物No-one,而且我们可以发现新宠物的DNA最后俩位成功变成了99
到这里我们的功能就都实现了,大家也可以去尝试玩一玩。
下一节我们进一步对这个合约进行功能优化:链接: Solidity实现智能合约——Solidity高级理论(三)