僵尸战斗系统(Zombie Battle System)

僵尸战斗系统(Zombie Battle System)

根据https://blog.csdn.net/weixin_43330666/article/details/107086663 继续学习

ZombieFactory第四课
是时候了,人类……
是时候让你的僵尸战斗了!
但僵尸战不适合胆小的人……



前言

在这一课中,我们将把你在前几章学到的很多概念放在一起,建立一个僵尸战斗功能。我们还将学习支付功能,以及如何构建可以接受玩家付款的dapp。


一、Payable

到目前为止,我们已经介绍了相当多的函数修饰符。
快速回顾一下:

  1. 可见性修饰符 来控制函数何时何地可以被调用:私有意味着它只能被合约内的其他函数调用;Internal类似于private,但也可以被从this继承的合约调用;外部只能在合同之外调用;最后,public可以在任何地方调用,无论是内部还是外部。
  2. 状态修饰符,它告诉我们函数如何与区块链交互:视图告诉我们,通过运行函数,不会保存/更改任何数据。Pure告诉我们,该函数不仅不会将任何数据保存到区块链中,而且也不会从区块链中读取任何数据。如果从合约外部调用它们,这两个都不需要花费gas(但如果由另一个函数在内部调用它们,则需要花费gas)。
  3. 自定义修饰符,例如,我们在第3课中学习过:onlyOwner和abovellevel。对于这些,我们可以定义自定义逻辑来确定它们如何影响函数。

这些修饰符可以像下面这样叠加在函数定义上:

function test() external view onlyOwner anotherModifier { /* ... */ }

在本章中,我们将介绍另一个功能修饰符: payable

payable 修饰符

payable函数是Solidity和以太坊如此酷的部分原因——它们是一种特殊类型的函数,可以接收以太币。
当你在普通的web服务器上调用API函数时,你不能在函数调用的同时发送美元——也不能发送比特币。
但在以太坊中,由于货币(以太币)、数据(交易有效载荷)和合约代码本身都存在于以太坊上,因此您可以调用函数并同时向合约付款。

这允许一些非常有趣的逻辑,比如为了执行一个函数,需要向合约支付一定的费用。

看一个示例:

contract OnlineStore {
  function buySomething() external payable {
    // Check to make sure 0.001 ether was sent to the function call:
    require(msg.value == 0.001 ether);
    // If so, some logic to transfer the digital item to the caller of the function:
    transferThing(msg.sender);
  }
}

这里 msg.value 是一种查看有多少以太币被发送到合约的方式

当有人在(来自DApp的JavaScript前端)web3.js调用这个函数时,会发生如下事件:

// Assuming `OnlineStore` points to your contract on Ethereum:
OnlineStore.buySomething({from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001)})

注意value字段,其中JavaScript函数调用指定要发送多少以太币(0.001)。如果你把交易想象成一个信封,你发送给函数调用的参数就是你放进信封里的信的内容,那么增加一个值就像把现金放进信封里——信和钱一起送到收件人那里。

说明:如果一个函数没有标记为payable,您尝试如上所述向其发送以太币,则该函数将拒绝您的交易。

实战演习

在我们的僵尸游戏里创建一个payable函数
假设我们的游戏有一个功能,用户可以通过支付ETH来升级他们的僵尸。以太币将被存储在你拥有的合同中——这是一个简单的例子,说明你可以如何在你的游戏中赚钱!

  1. 定义一个 uint 类型的 levelUpFee ,设置它的价格为 0.001 ether .
  2. 新建一个函数 levelUp ,接收一个uint参数_zombieId,可见性设为 external ,且带有payable 修饰符
  3. 函数首先使用require校验msg.value levelUpFee相同
  4. 然后增加僵尸的等级 zombies[_zombieId].level++.

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }
  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  function changeName(uint _zombieId, string calldata _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[] memory) {
    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;
  }

}

二、Withdraws

在前一章中,我们学习了如何将以太币发送到合约中。发送之后会发生什么?
当你将以太币发送到合约后,它会被存储在合约的以太坊账户中,并且它会被困在那里——除非你添加一个从合约中提取以太币的功能。
代码如下:

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    address payable _owner = address(uint160(owner()));
    _owner.transfer(address(this).balance);
  }
}

注意,我们使用的是Ownable合约中的owner()和onlyOwner,假设已经导入该包

要注意,您不能将以太币转移到一个地址,除非该地址是可支付地址类型。但是_owner变量的类型是uint160,这意味着我们必须显式地将它转换为addres payable
一旦你将地址从uint160转换为addres payable,你就可以使用transfer函数将以太币转移到该地址。address(this).balance 将返回存储在合约上的总余额。因此,如果100个用户向我们的合约支付了1个以太币,address(this).balance 余额等于100以太币。

您可以使用transfer将资金发送到任何以太坊地址。
例如,您可以通过transfer将以太币转回给

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);

或者在买方和卖方的合同中,您可以将卖方的地址保存在存储中,然后当有人购买他的物品时,将买方支付的费用转给超额支付的msg.sender他:seller.transfer(msg.value)

这些例子使以太坊编程可以实现不受任何人控制的去中心化市场。

实战演习

  1. 在我们的合约中创建一个withdraw函数,与上面的GetPaid示例相同。

  2. 以太币的价格在过去一年中上涨了10倍以上。在撰写本文时,0.001 ETH约为1美元,如果它再次上涨10倍,0.001 ETH将变为10美元,我们的游戏将变得更加昂贵。
    所以最好创建一个函数,让我们作为合约的所有者来设置levelUpFee。
    a. 新建一个函数 setLevelUpFee ,接收一个uint参数_fee,可见性设为 external ,且带有onlyOwner 修饰符

    b. 函数将levelUpFee的值设置为_fee

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 1. Create withdraw function here
  function withdraw() external onlyOwner{
    address payable _owner = address(uint160(owner()));
    _owner.transfer(address(this).balance);
  }

  // 2. Create setLevelUpFee function here
  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 calldata _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[] memory) {
    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;
  }

}

三、Zombie Battles

现在我们已经了解了payable功能和合约余额,是时候为僵尸战斗添加功能了!
按照前几章的格式,我们将通过创建一个新的文件/合约来组织我们的代码,该文件/合约是从之前的合约中导入的攻击功能。

实战演习

让我们复习一下如何创建一个新合同

  1. 在文件的顶部声明我们使用的Solidity版本 >=0.5.0 <0.6.0.
  2. import from zombiehelper.sol.
  3. 声明一个名为ZombieAttack的新合约,它继承自ZombieHelper。暂时将合同主体保留为空。

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {

}

四、Random Numbers

太棒了!现在让我们弄清楚战斗逻辑。

所有优秀的游戏都需要一定程度的随机性。那么我们如何在Solidity中生成随机数呢?
真正的答案是,不能。好吧,至少你不能安全地做这件事。

让我们来看看为什么。

通过keccak256生成随机数

// Generate a random number between 1 and 100:
uint randNonce = 0;
uint random = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;
randNonce++;
uint random2 = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;

它做的是获取当前时间戳, msg.sender,和一个递增的nonce(一个只使用一次的数字,所以我们不会用相同的输入参数两次运行相同的哈希函数)。
然后,“打包”输入并使用keccak将它们转换为随机哈希。接下来,它会将该散列转换为int,然后使用% 100只接受最后两位数字。这将给我们一个0到99之间的完全随机数。

这种方法很容易受到不诚实节点的攻击

在以太坊中,当你在合约上调用一个函数时,你会将其作为交易广播到网络上的一个或多个节点。然后,网络上的节点收集一堆交易,试图成为第一个解决计算密集型数学问题的节点,作为“工作量证明”,然后将这组交易以及他们的工作量证明(PoW)作为块发布给网络的其他节点。

一旦一个节点解决了PoW,其他节点停止尝试解决PoW,验证另一个节点的交易列表是有效的,然后接受该块并继续尝试解决下一个块。

这使得我们的随机数函数可以被利用。

假设我们有一个抛硬币的合约,正面你的钱翻倍,背面你失去一切。假设它使用上面的随机函数来确定正面或反面。(random>= 50是正面,random< 50是反面)。

如果我正在运行一个节点,我可以只将事务发布到我自己的节点,而不共享它。然后,我可以运行抛硬币函数,看看我是否赢了——如果我输了,选择不把这笔交易包含在我要解决的下一个区块中。我可以无限期地这样做,直到我最终赢得了抛硬币并解决了下一个区块,并从中获利。

那么我们如何在以太坊中安全地生成随机数呢?

由于区块链的整个内容对所有参与者都是可见的,因此这是一个难题,其解决方案超出了本教程的范围。你可以阅读StackOverflow线程来获得一些想法。一个想法是使用oracle从以太坊区块链外部访问随机数函数。

当然,由于网络上成千上万的以太坊节点正在竞争解决下一个区块,解决下一个区块的几率非常低。利用这一盈利将花费大量的时间或计算资源——但如果回报足够高(就像我可以在掷硬币功能上下注1亿美元),就值得去攻击。

因此,虽然这种随机数生成在以太坊上并不安全,但在实践中,除非我们的随机函数在线上有很多钱,否则你的游戏用户可能没有足够的资源来攻击它。

因为在本教程中我们只是为了演示目的而创造一款简单的游戏,并且没有真正的资金投入,所以我们将接受使用易于实现的随机数生成器的权衡,并知道它并非完全安全。

在未来的课程中,我们可能会介绍使用oracle(一种从以太坊外部提取数据的安全方法)从区块链外部生成安全随机数。

实战演习

让我们实现一个随机数函数,我们可以用它来决定战斗的结果,即使它不是完全安全的攻击。

  1. 在合约里新建一个 uintrandNonce 赋值为 0.
  2. 新建一个internal的函数 randMod (random-modulus). 接收一个 uint 类型的参数 _modulus, 返回值类型为uint.
  3. 该函数首先给randNonce 递增(通过randNonce++ 语法)
  4. 最后在一行代码内实现: 计算uint 类型的abi.encodePacked(now,msg.sender,randNonce)keccak256哈希值,将返回值取% _modulus.
    (哟! !这太拗口了。如果你不明白,看看上面的例子,我们生成了一个随机数(逻辑非常相似)。

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  // Create attackVictoryProbability here

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;
  }
}

五、僵尸攻击

现在我们的合约中有了一些随机性,我们可以在僵尸战斗中使用它来计算结果。

我们的僵尸战将会是这样的:

  • 你选择一个你的僵尸,并选择一个对手的僵尸来攻击。
  • 如果你是攻击僵尸,你将有70%的机会获胜。防守僵尸将有30%的机会获胜。
  • 所有僵尸(攻击和防御)将有一个winCount和一个lossCount,将根据战斗的结果增加。
  • 如果攻击僵尸获胜,它将升级并生成一个新的僵尸。
  • 如果它输了,什么也不会发生(除了它的lossCount递增)。
  • 无论它是赢还是输,攻击僵尸的冷却时间都会被触发。

这需要实现很多逻辑,所以我们将在接下来的章节中逐个实现。

实战演习

  1. 给我们的合约一个名为 attackVictoryProbabilityint变量,并将其设置为70
  2. 创建一个名为attack的函数。它将接受两个参数:_zombieId (uint类型)和_targetId(也是uint类型)。它应该是一个外部函数。
    现在让函数体为空

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external {
  }
}

六、重构公共逻辑

无论谁调用我们的攻击函数,我们都要确保用户实际上拥有他们正在攻击的僵尸。如果你用别人的僵尸来攻击,就会引起安全问题!

你能想到我们如何添加一个检查来检查调用这个函数的人是否是他们传入的_zombieId的所有者吗?

思考一下,看看你是否能自己想出答案。
花点时间……参考我们之前的一些课程的代码的想法…

The answer

在之前的课程中我们已经做过很多次了。 在 changeName(), changeDna(), and feedAndMultiply(), 我们使用了以下检查:

require(msg.sender == zombieToOwner[_zombieId]);

这和attack函数的逻辑是一样的。由于我们多次使用相同的逻辑,让我们将其移到它自己的modifier中,以清理代码并避免重复。

实战演习

回到zombiefeeding.sol 我们最早使用这种逻辑的地方,将其重构成它自己的modifier

  1. 创建一个modifier命名为ownerOf,接收一个参数_zombieIduint
    函数体校验msg.senderzombieToOwner[_zombieId]相同,函数继续。可以参考zombiehelper。如果您不记得修饰符的语法。
  2. 修改函数feedAndMultiply的定义 使其使用ownerOf
  3. 使用modifier,可以删除 require(msg.sender == zombieToOwner[_zombieId])

合约修改

pragma solidity >=0.5.0 <0.6.0;

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 {
    address _owner = owner();
    _owner.transfer(address(this).balance);
  }

  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  // 1. Modify this function to use `ownerOf`:
  function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

  // 2. Do the same with this function:
  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[] memory) {
    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;
  }

}

七、继续重构

`zombiehelper.sol `还有一些需要使用新`modifier ownerOf`的地方.

实战演习

  1. Update changeName() to use ownerOf
  2. Update changeDna() to use ownerOf

合约修改

pragma solidity >=0.5.0 <0.6.0;

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 {
    address _owner = owner();
    _owner.transfer(address(this).balance);
  }

  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 calldata _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].dna = _newDna;
  }

  function getZombiesByOwner(address _owner) external view returns(uint[] memory) {
    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
我们将继续定义攻击函数,现在我们有了要使用的ownerOf修饰符。

实战演习

  1. ownerOf modifier 添加至 确保调用自己的
  2. 我们的函数应该做的第一件事是获得两个僵尸的storage指针,这样我们就可以更容易地与它们交互:
    a. Declare a Zombie storage named myZombie, and set it equal to zombies[_zombieId].
    b. Declare a Zombie storage named enemyZombie, and set it equal to zombies[_targetId].
  3. 我们将使用0到99之间的一个随机数来决定我们战斗的结果。因此,声明一个名为randint,并将其设置为以100为参数的randMod函数的结果。

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(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);
  }
}

九、僵尸的胜负

对于我们的僵尸游戏,我们想要记录僵尸赢了多少次,输了多少次。这样我们就可以在游戏状态中保持“僵尸排行榜”。

我们可以在DApp中以多种方式存储这些数据——作为单独的映射,作为排行榜结构,或者在Zombie结构本身中

每个都有自己的好处和权衡,这取决于我们打算如何与数据交互。在本教程中,为了简单起见,我们将把统计数据存储在我们的Zombie结构体中,并将它们称为winCountlossCount

让我们回到zombiefactory.sol,。并将这些属性添加到我们的Zombie结构体中。

实战演习

  1. 修改Zombie结构体,新增两个属性:
    a. winCount, a uint16
    b. lossCount, also a uint16
    注意:请记住,因为我们可以将单元打包到结构体中,所以我们希望使用我们可以使用的最小的单元。uint8太小了,因为2^8 = 256——如果我们的僵尸每天攻击一次,它们可能在一年内溢出这个值。但2^16等于65536——所以除非179年来每天都有用户输赢,否则我们在这里应该是安全的
  2. 既然我们在Zombie结构体上有了新属性,我们需要更改 _createZombie()中的函数定义。
    更改僵尸创建定义,以便创建每个新僵尸时0胜0负。

合约修改

pragma solidity >=0.5.0 <0.6.0;

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;
      uint16 winCount;
      uint16 lossCount;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string memory _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string memory _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}

十、僵尸胜利

现在我们有了winCountlossCount,我们可以根据哪个僵尸赢得战斗来更新它们。
在第六章中,我们计算了一个从0到100的随机数。现在让我们用这个数字来决定谁赢了这场比赛,并相应地更新我们的统计数据。

实战演习

(目前它并没有做任何事情,但如果我们愿意的话,之后我们可以添加额外的功能来生成基于僵尸的僵尸)。

  1. 创建一个if检查语句,判断rand<= attackVictoryProbability
  2. 如果判断条件为真,我们的僵尸胜利,于是:
    a. 增加myZombie's winCount
    b. 增加myZombie's level
    c.增加enemyZombie's lossCount
    d.运行feedAndMultiply函数。检查zombiefeeding.sol查看调用它的语法。对于第三个参数(_species),传递字符串“zombie”

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(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和许多其他语言一样

if (zombieCoins[msg.sender] > 100000000) {
  // You rich!!!
} else {
  // We require more ZombieCoins...
}

实战演习

添加else语句。如果我们的僵尸输了
a.增加myZombielossCount
b.增加敌人僵尸的winCount
c.在myZombie上运行_triggerCooldown函数。这样僵尸每天只能攻击一次。(记住,_triggerCooldown已经在feedAndMultiply内部运行了。所以僵尸的冷却时间会被触发,不管他是赢还是输。)

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(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)
    }
  }
}

十二、包装

恭喜你!第四课结束了。
继续,测试你的战斗功能的权利!

领取你的奖励

赢得战役后:
你的僵尸会升级
你的僵尸将增加他的winCount
你将生成一个新的僵尸来加入你的军队!
继续尝试战斗,然后进入下一章完成这一课
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值