• 瑞典民众庆祝世界杯胜利时发生枪击 致4人受伤 2019-05-13
  • 《高能》张一山组“恐龙战队” 宋威龙贴心保护徐璐 2019-05-13
  • 女性之声——全国妇联 2019-05-12
  • 十二生肖的起源与传说:你又知道多少? 2019-05-11
  • 江西省节能宣传周活动启动 2019-05-11
  • 气候异常易让人得10种病:癌症心脑血管疾病 2019-05-10
  • 上海抚州商会返乡考察团来抚考察 2019-05-10
  • “一带一路”论坛 值得世界期待 2019-05-09
  • 国丰新能源有限公司获第十二届人民企业社会责任奖年度环保奖 2019-05-08
  • 移动助力建设智慧新疆 2019-05-07
  • 山西晋中学院制定办法规范在线课程 2019-05-06
  • 谈具体的吧,别装模作样了。客观事实与观察事实、科学事实有什么不同? 2019-05-06
  • 人民网评:跨越生态文明建设的“三期”关口 2019-05-05
  • 谈判与加关税的不同在于,后者没有给小左唱赞歌的机会 2019-05-05
  • 骑士老板竟称没詹姆斯也能争冠 无脑言论或把詹皇逼走 2019-05-04
  • 重庆时时彩三星组选:如何在以太坊上编写自己的CryptoKitties风格的游戏

    如果你不了解CryptoKitties是什么,它基本上是一个购买,销售和繁殖数字猫的游戏。 每只猫都有一个独特的外观,由它的基因所定义,当你通过两只猫繁殖时,它们的基因以一种独特的方式结合在一起产生一个后代,然后你可以繁殖或出售它。

    CryptoKitties?做了很棒的工作,他展示除了简单的金融交易之外还可以使用区块链做什么。

    我希望将来我们会看到更多创新的区块链用法,所以我想快速浏览CryptoKitties背后的代码,以展示它背后是如何实现的。

    本文是为开发人员编写的,虽然这不是一个绝对的初学者对Solidity的介绍,但是我试图包含文档的链接,以便尽可能适合所有开发者。

    让我们开始…

    CryptoKitties源码

    几乎所有的CryptoKitties代码都是开源的,因此找出它的工作原理的最好方法是阅读源代码。

    总共大约有2000行,所以在这篇文章中,我只会讲解我认为最重要的部分。 但是,如果您想单独阅读,请参阅EthFiddle上的完整合约代码副本:

    CryptoKitties Source Code:https://ethfiddle.com/09YbyJRfiI

    总概:

    如果你不了解CryptoKitties是什么,它基本上是一个购买,销售和繁殖数字猫的游戏。 每只猫都有一个独特的外观,由它的基因所定义,当你通过两只猫繁殖时,它们的基因以一种独特的方式结合在一起产生一个后代,然后你可以繁殖或出售它。

    CryptoKitties 的代码分为许多相关的较小的合约, 而不是一个单一的包含所有东西的巨大文件

    子合约像下面这样继承主kitty合约:

    contract KittyAccessControl
    contract KittyBase is KittyAccessControl
    contract KittyOwnership is KittyBase, ERC721
    contract KittyBreeding is KittyOwnership
    contract KittyAuction is KittyBreeding
    contract KittyMinting is KittyAuction
    contract KittyCore is KittyMinting

    所以KittyCore是最终应用程序指向的合约地址,他继承了前面合约的所有的属性和方法
    让我们一个一个的看看这些合约:

    1. KittyAccessControl:谁控制合约?

    这个合约管理只能由特定角色执行操作的各种地址和约束。这些角色叫CEO, CFO and COO.
    这个合约是为了管理合约,根本不涉及到游戏的机制。他为CEO, COO 和CFO提供有“setter”方法, 他们(CEO, COO, CFO)是对合约具有特殊所有权和控制权的以太坊地址。
    KittyAccessControl 定义一些modifier函数例如 onlyCEO(只有CEO才能执行),还有暂停/恢复合约的方法或者提现方法

    modifier onlyCLevel() {
     ? ?require(
     ? ? ? ?msg.sender == cooAddress ||
     ? ? ? ?msg.sender == ceoAddress ||
     ? ? ? ?msg.sender == cfoAddress
     ? ?);
     ? ?_;
    }
    //...some other stuff
    // Only the CEO, COO, and CFO can execute this function:
    function pause() external onlyCLevel whenNotPaused {
     ? ?paused = true;
    }

    pause() 函数可能被添加,以便开发人员可以更新一个新的版本,以防有任何不可预见的错误… 但正如我的同事Luke指出,这实际上将允许开发人员完全冻结合约,使其没有人可以转让,出售或繁殖他们的小猫! 并不是说他们会这么做 – 但是有趣的是,由于大多数人认为DApp完全是去中心化的,只是因为它在以太坊上。

    继续。。。

    2. KittyBase: Kitty是什么?

    这是我们定义在整个核心功能中共享的最基本代码的地方。 这包括我们的主要数据存储,常量和数据类型,以及用于管理这些数据的内部函数。

    KittyBase 定义了应用程序的很多核心数据。首先它将Kitty定义为一个结构体:

    struct Kitty {
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndBlock;
    uint32 matronId;
    uint32 sireId;
    uint32 siringWithId;
    uint16 cooldownIndex;
    uint16 generation;
    }

    所以一只kitty实际上只是一串无符号的整数…

    展开每个属性:
    ?genes—代表猫的遗传密码的256位整数。 这是决定猫的长相的核心数据。
    ?birthTime—猫出生时的时间戳
    ?cooldownEndBlock—之后这只猫可以再次繁殖的最小时间戳
    ?matronId&sireId—分别是猫的母亲和父亲的ID
    ?siringWithId—如果猫当前怀孕,则设置为父亲的ID,否则为零
    ?cooldownIndex—目前这只猫的冷却时间(猫需要等待多久才能繁殖)
    ?generation—这只猫的“世代号”。 第一只猫被合约创造是0代,新一代的猫是他们的父母一代中较大的一个,再加上1.

    请注意,在Crypto Kitties中,猫是无性的,任何2只猫都可以一起繁殖 – 因此猫没有性别。

    KittyBase 合约定义了一个kitty 数据结构的数据

    Kitty[] kitties;

     

    这个数组包含了所有Kitty的数据,所以它就像一个Kitty的数据库一样。 无论何时创建一个新的猫,它都会被添加到这个数组中,数组的索引成为猫的ID,就像这个 ID为’1’的创世喵:

    该合约还包含从猫的ID到其拥有者地址的映射,以跟踪拥有猫的人:

    mapping (uint256 => address) public kittyIndexToOwner;

    还有一些其他的映射也被定义,但为了保持这篇文章的合理长度,我不会仔细研究每一个细节。
    每当小猫从一个人转移到下一个时,这个kittyIndexToOwner映射就会被更新以反映新的所有者:

    /// @dev Assigns ownership of a specific Kitty to an address.
    function _transfer(address _from, address _to, uint256 _tokenId) internal {
    // Since the number of kittens is capped to 2^32 we can’t overflow this
    ownershipTokenCount[_to]++;
    // transfer ownership
    kittyIndexToOwner[_tokenId] = _to;
    // When creating new kittens _from is 0x0, but we can’t account that address.
    if (_from != address(0)) {
    ownershipTokenCount[_from]–;
    // once the kitten is transferred also clear sire allowances
    delete sireAllowedToAddress[_tokenId];
    // clear any previously approved ownership exchange
    delete kittyIndexToApproved[_tokenId];
    }
    // Emit the transfer event.
    Transfer(_from, _to, _tokenId);
    }

    转移所有权 设置Kitty的ID指向接收人_to的地址。
    现在我们来看看在创建一个新的kitty时会发生什么:

    function _createKitty(
    uint256 _matronId,
    uint256 _sireId,
    uint256 _generation,
    uint256 _genes,
    address _owner
    )
    internal
    returns (uint)
    {
    // These requires are not strictly necessary, our calling code should make
    // sure that these conditions are never broken. However! _createKitty() is already
    // an expensive call (for storage), and it doesn’t hurt to be especially careful
    // to ensure our data structures are always valid.
    require(_matronId == uint256(uint32(_matronId)));
    require(_sireId == uint256(uint32(_sireId)));
    require(_generation == uint256(uint16(_generation)));// New kitty starts with the same cooldown as parent gen/2
    uint16 cooldownIndex = uint16(_generation / 2);
    if (cooldownIndex > 13) {
    cooldownIndex = 13;
    }Kitty memory _kitty = Kitty({
    genes: _genes,
    birthTime: uint64(now),
    cooldownEndBlock: 0,
    matronId: uint32(_matronId),
    sireId: uint32(_sireId),
    siringWithId: 0,
    cooldownIndex: cooldownIndex,
    generation: uint16(_generation)
    });
    uint256 newKittenId = kitties.push(_kitty) – 1;

    // It’s probably never going to happen, 4 billion cats is A LOT, but
    // let’s just be 100% sure we never let this happen.
    require(newKittenId == uint256(uint32(newKittenId)));

    // emit the birth event
    Birth(
    _owner,
    newKittenId,
    uint256(_kitty.matronId),
    uint256(_kitty.sireId),
    _kitty.genes
    );

    // This will assign ownership, and also emit the Transfer event as
    // per ERC721 draft
    _transfer(0, _owner, newKittenId);

    return newKittenId;
    }

    这个函数传递了母亲和父亲的ID,小猫的世代号码,256位遗传密码和所有者的地址。 然后创建小猫,将其加入到Kitty数组,然后调用_transfer() 将其分配给它的新所有者。

    Cool – 现在我们可以看到CryptoKitties如何将一只猫咪定义为一种数据类型,它如何将所有小猫都存储在区块链中,以及如何跟踪谁拥有哪些小猫。

    3. KittyOwnership: Kitties代币化

    这提供了遵循ERC-721规范草案的基本不可互换令牌交易所需的方法。
    CryptoKitties符合ERC721代币规范,这是一种不可替换的代币类型,它非常适合在MMORPG中跟踪数字收集游戏(如数字扑克牌或稀有物品)的所有权。
    关于Fungibility的说明:Ether是可互换的,因为任何5个ETH都与其他5个ETH一样好。 但是像CryptoKitties这样的是非可互换代币,并不是每只猫都是平等的,所以它们不能互相交换。
    您可以从合约定义中看出,KittyOwnership继承了ERC721合约:

    contract KittyOwnership is KittyBase, ERC721 {

    而所有ERC721令牌都遵循相同的标准,所以KittyOwnership合约实现了以下功能:

    /// @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens
    /// @author Dieter Shirley <[email protected]>(https://github.com/dete)</[email protected]>
    contract ERC721 {
    // Required methods
    function totalSupply() public view returns (uint256 total);
    function balanceOf(address _owner) public view returns (uint256 balance);
    function ownerOf(uint256 _tokenId) external view returns (address owner);
    function approve(address _to, uint256 _tokenId) external;
    function transfer(address _to, uint256 _tokenId) external;
    function transferFrom(address _from, address _to, uint256 _tokenId) external;// Events
    event Transfer(address from, address to, uint256 tokenId);
    event Approval(address owner, address approved, uint256 tokenId);// Optional
    // function name() public view returns (string name);
    // function symbol() public view returns (string symbol);
    // function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds);
    // function tokenMetadata(uint256 _tokenId, string _preferredTransport) public view returns (string infoUrl);

    // ERC-165 Compatibility (https://github.com/ethereum/EIPs/issues/165)
    function supportsInterface(bytes4 _interfaceID) external view returns (bool);
    }

     

    由于这些方法是公开的,这就为用户提供了一个标准的方式来与CryptoKitties令牌进行交互,就像他们与任何其他ERC721令牌进行交互一样。 您可以通过直接与以太坊区块链上的CryptoKitties合约进行交互,而不必通过他们的Web界面来将您的代币转让给其他人,所以从这个意义上说,您真的拥有自己的小猫。 (除非CEO暂停合约)。

    我不会解读所有这些方法的实现,但是你可以在EthFiddle上查看它们(搜索“KittyOwnership”)。

    4. KittyBreeding:猫的繁殖

    这个文件包含了将猫一起繁殖所必需的方法,包括跟踪繁殖提供者,并依靠外部基因组合合约。
    “外部基因组合合约”(geneScience)存储在一个不是开源的单独合约中。
    KittyBreeding 合约包含一个方法,让CEO设置这个外部基因组合约地址:

    /// @dev Update the address of the genetic contract, can only be called by the CEO.
    /// @param _address An address of a GeneScience contract instance to be used from this point forward.
    function setGeneScienceAddress(address _address) external onlyCEO {
    GeneScienceInterface candidateContract = GeneScienceInterface(_address);// NOTE: verify that a contract is what we expect – https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117
    require(candidateContract.isGeneScience());// Set the new contract address
    geneScience = candidateContract;
    }

    他们这样做是为了让游戏变得不那么容易 – 如果你能够读懂一只小猫的DNA是如何确定的,那么就知道为了得到一只“奇特的猫”而跟哪只猫繁殖会容易得多。
    这个外部 geneScience合约之后会在theGiveBirth() 函数(我们稍后会看到)中使用,以确定新猫的DNA。
    现在让我们看看当两只猫在一起时会发生什么:

    /// @dev Internal utility function to initiate breeding, assumes that all breeding
    /// ?requirements have been checked.
    function _breedWith(uint256 _matronId, uint256 _sireId) internal {
    // Grab a reference to the Kitties from storage.
    Kitty storage sire = kitties[_sireId];
    Kitty storage matron = kitties[_matronId];// Mark the matron as pregnant, keeping track of who the sire is.
    matron.siringWithId = uint32(_sireId);// Trigger the cooldown for both parents.
    _triggerCooldown(sire);
    _triggerCooldown(matron);

    // Clear siring permission for both parents. This may not be strictly necessary
    // but it’s likely to avoid confusion!
    delete sireAllowedToAddress[_matronId];
    delete sireAllowedToAddress[_sireId];

    // Every time a kitty gets pregnant, counter is incremented.
    pregnantKitties++;

    // Emit the pregnancy event.
    Pregnant(kittyIndexToOwner[_matronId], _matronId, _sireId, matron.cooldownEndBlock);
    }

    这个函数需要母亲和父亲的ID,在kitties数组中查找它们,并将母亲上的siringWithId设置为父亲的ID。 (当siringWithId不为零时,表示母亲怀孕)。
    它也执行父母双方的triggerCooldown函数,这会使他们在一段时间内不能再一次繁殖。
    接下来,有一个公开的 giveBirth() 函数 创建一个新的猫:

    /// @notice Have a pregnant Kitty give birth!
    /// @param _matronId A Kitty ready to give birth.
    /// @return The Kitty ID of the new kitten.
    /// @dev Looks at a given Kitty and, if pregnant and if the gestation period has passed,
    /// ?combines the genes of the two parents to create a new kitten. The new Kitty is assigned
    /// ?to the current owner of the matron. Upon successful completion, both the matron and the
    /// ?new kitten will be ready to breed again. Note that anyone can call this function (if they
    /// ?are willing to pay the gas!), but the new kitten always goes to the mother’s owner.
    function giveBirth(uint256 _matronId)
    external
    whenNotPaused
    returns(uint256)
    {
    // Grab a reference to the matron in storage.
    Kitty storage matron = kitties[_matronId];// Check that the matron is a valid cat.
    require(matron.birthTime != 0);// Check that the matron is pregnant, and that its time has come!
    require(_isReadyToGiveBirth(matron));

    // Grab a reference to the sire in storage.
    uint256 sireId = matron.siringWithId;
    Kitty storage sire = kitties[sireId];

    // Determine the higher generation number of the two parents
    uint16 parentGen = matron.generation;
    if (sire.generation > matron.generation) {
    parentGen = sire.generation;
    }

    // Call the sooper-sekret gene mixing operation.
    uint256 childGenes = geneScience.mixGenes(matron.genes, sire.genes, matron.cooldownEndBlock – 1);

    // Make the new kitten!
    address owner = kittyIndexToOwner[_matronId];
    uint256 kittenId = _createKitty(_matronId, matron.siringWithId, parentGen + 1, childGenes, owner);

    // Clear the reference to sire from the matron (REQUIRED! Having siringWithId
    // set is what marks a matron as being pregnant.)
    delete matron.siringWithId;

    // Every time a kitty gives birth counter is decremented.
    pregnantKitties–;

    // Send the balance fee to the person who made birth happen.
    msg.sender.send(autoBirthFee);

    // return the new kitten’s ID
    return kittenId;
    }

    代码是非常明显的。 基本上,代码首先执行一些检查,看看母亲是否准备好生孩子。 然后使用geneScience.mixGenes()确定孩子的基因,将新基因的所有权分配给母亲,然后调用我们在KittyBase中的函数_createKitty()。

    请注意,geneScience.mixGenes()函数是一个黑匣子,因为该合约是闭源的。 所以我们实际上并不知道孩子的基因是如何决定的,但我们知道这是母亲基因和父亲基因的功能,还有母亲的cooldownEndBlock。

    5. KittyAuctions: 买卖和繁殖服务(出台)

    在这里,我们有公开的方法来拍卖猫或招标猫或繁殖猫。 实际的拍卖功能是在两个兄弟合约(一个用于买卖,一个用于繁殖)中处理的,而拍卖的创建和投标主要是通过核心合约。

    根据开发者的说法,他们将这个拍卖功能分为“兄弟”合约,是因为“他们的逻辑有点复杂,总是存在微妙的bug风险。 通过保留它们自己的合约,我们可以升级它们而不会中断追踪小猫所有权的主合约?!?br /> 因此,这个KittyAuctions合约包含函数setSaleAuctionAddress() 和setSiringAuctionAddress(),像 setGeneScienceAddress() 只能由CEO调用,并设置处理这些函数的外部合约的地址。

    注意:“Siring”指的是把你的猫拉出来 – 把它拍卖,在那里另一个用户可以付钱给你以太,让你的猫与他们一起繁殖。哈哈。

    这意味着,即使CryptoKitties合约本身是不可变的,首席执行官也可以灵活地改变这些拍卖合约的地址,从而改变拍卖规则。 同样,不一定是坏事,因为有时候开发人员需要修正bug,但是这是要注意的事情。

    我不打算详细讨论如何处理拍卖和出价逻辑,以防止这篇文章过长(已经够长了?。?,但是您可以在EthFiddle中查看代码(搜索KittyAuctions)。

    6. KittyMinting: 创世猫工厂

    最后一个方面包含我们用来创建新的gen0猫的功能。 我们最多可以制作5000只可以赠送的“营销”猫(在社区初期的时候尤为重要),其他所有的猫只能通过算法确定的起始价格创建,然后立即投入拍卖。 不管它们是如何创造的,都有50k gen0猫的硬性极限。 之后,社群就要繁殖,繁殖,繁殖!

    合约能够创建的promo cats和gen0 cat的数量在这里是硬编码的:

    uint256 public constant PROMO_CREATION_LIMIT = 5000;
    uint256 public constant GEN0_CREATION_LIMIT = 45000;

    这里是“COO”可以创建营销小猫和gen0小猫的代码:

    /// @dev we can create promo kittens, up to a limit. Only callable by COO
    /// @param _genes the encoded genes of the kitten to be created, any value is accepted
    /// @param _owner the future owner of the created kittens. Default to contract COO
    function createPromoKitty(uint256 _genes, address _owner) external onlyCOO {
    address kittyOwner = _owner;
    if (kittyOwner == address(0)) {
    kittyOwner = cooAddress;
    }
    require(promoCreatedCount < PROMO_CREATION_LIMIT);promoCreatedCount++;
    _createKitty(0, 0, 0, _genes, kittyOwner);
    }/// @dev Creates a new gen0 kitty with the given genes and
    /// ?creates an auction for it.
    function createGen0Auction(uint256 _genes) external onlyCOO {
    require(gen0CreatedCount < GEN0_CREATION_LIMIT);

    uint256 kittyId = _createKitty(0, 0, 0, _genes, address(this));
    _approve(kittyId, saleAuction);

    saleAuction.createAuction(
    kittyId,
    _computeNextGen0Price(),
    0,
    GEN0_AUCTION_DURATION,
    address(this)
    );

    gen0CreatedCount++;
    }

    所以通过createPromoKitty(),看起来COO可以用任何他想要的基因创建一个新的kitty,然后发送给任何他想要给的人(最多5000个kitty)。 我猜测他们是为了早期测试者,朋友和家人,为了促销目的而赠送免费的小猫咪等等。
    但是这也意味着你的猫可能并不像你想象的那样独一无二,因为他可能会有5000个相同的副本!
    对于createGen0Auction(),COO也提供新基因的遗传密码。 但不是将其分配给特定的人的地址,而是创建一个用户可以出价购买小猫的拍卖。

    7. KittyCore: 主合约

    这是主要的CryptoKitties合约,编译和运行在以太坊区块链上。 这份合约把所有东西联系在一起。
    由于继承结构,它继承了我们之前所看到的所有合约,并增加了几个最终的方法,就像这个使用ID来获取所有的Kitty数据的函数:

    /// @notice Returns all the relevant information about a specific kitty.
    /// @param _id The ID of the kitty of interest.
    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
    ) {
    Kitty storage kit = kitties[_id];// if this variable is 0 then it’s not gestating
    isGestating = (kit.siringWithId != 0);
    isReady = (kit.cooldownEndBlock <= block.number);
    cooldownIndex = uint256(kit.cooldownIndex);
    nextActionAt = uint256(kit.cooldownEndBlock);
    siringWithId = uint256(kit.siringWithId);
    birthTime = uint256(kit.birthTime);
    matronId = uint256(kit.matronId);
    sireId = uint256(kit.sireId);
    generation = uint256(kit.generation);
    genes = kit.genes;
    }

    这是一个公共方法,它返回区块链中特定小猫的所有数据。 我想这是他们的Web服务器在网站上显示的猫的查询。

    等等…我没有看到任何图像数据。 什么决定了小猫的样子?

    从上面的代码可以看出,一个“小猫”基本上归结为一个256位的无符号整数,代表其遗传密码。

    Solidity合约代码中没有任何内容存储猫的图像或其描述,或者确定这个256位整数的实际含义。 该遗传密码的解释发生在CryptoKitty的网络服务器上。

    所以虽然这是区块链上游戏的一个非常聪明的演示,但实际上并不是100%的区块链。 如果将来他们的网站被脱机,除非有人备份了所有的图像,否则只剩下一个毫无意义的256位整数。

    在合约代码中,我找到了一个名为ERC721Metadata的合约,但它永远不会被用于任何事情。 所以我的猜测是,他们最初计划将所有内容都存储在区块链中,但之后却决定不要这么做(在Ethereum中存储大量数据的代价太高),所以他们最终需要将其存储在Web服务器上。

    总结一下:

    • 小猫如何表现为数据
    • 现存的所有小猫如何存储在一个智能合约中,以及如何跟踪谁拥有什么
    • gen0小猫如何生产
    • 小猫如何在一起繁殖,形成新的小猫

    如若转载,请注明出处://www.tijt.com.cn/2018/03/322983/

    发表评论

    电子邮件地址不会被公开。 必填项已用*标注

    关注微信
  • 瑞典民众庆祝世界杯胜利时发生枪击 致4人受伤 2019-05-13
  • 《高能》张一山组“恐龙战队” 宋威龙贴心保护徐璐 2019-05-13
  • 女性之声——全国妇联 2019-05-12
  • 十二生肖的起源与传说:你又知道多少? 2019-05-11
  • 江西省节能宣传周活动启动 2019-05-11
  • 气候异常易让人得10种病:癌症心脑血管疾病 2019-05-10
  • 上海抚州商会返乡考察团来抚考察 2019-05-10
  • “一带一路”论坛 值得世界期待 2019-05-09
  • 国丰新能源有限公司获第十二届人民企业社会责任奖年度环保奖 2019-05-08
  • 移动助力建设智慧新疆 2019-05-07
  • 山西晋中学院制定办法规范在线课程 2019-05-06
  • 谈具体的吧,别装模作样了。客观事实与观察事实、科学事实有什么不同? 2019-05-06
  • 人民网评:跨越生态文明建设的“三期”关口 2019-05-05
  • 谈判与加关税的不同在于,后者没有给小左唱赞歌的机会 2019-05-05
  • 骑士老板竟称没詹姆斯也能争冠 无脑言论或把詹皇逼走 2019-05-04