Создаем свои криптокотиков (Часть 2)

    Это статья — вторая (и заключительная) часть из серии о создании своих криптокотиков. В первой части мы узнали, что из себя представляет каждый Криптокотик, кто контролирует ход игры и как сделать котика в виде токена. Но для по-настоящему прорывного приложения нам необходимо определелить для них механизм размножения, а главное — рыночной торговли, чтобы участники могли выкупать друг у друга самых породистых котят.image

    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;
    }
    

    Разработчики решились на этот ход, чтобы игра не была слишком уж простой — если бы вы могли просто прочитать, чем определяется ДНК котика, было бы гораздо легче узнать, каких котиков скрещивать, чтобы получить породистого котика «fancy».


    Этот внешний контракт geneScience впоследствии будет использован в функции giveBirth() (скоро мы с ней познакомимся) для определения ДНК нового котика.


    Теперь посмотрим, что происходит, когда мы скрещиваем двух котиков:


    /// @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);
    }
    

    Эта функция берет учетные записи матери и отца, ищет их в основном кошачьем массиве и устанавливает в учетной записи отца показатель siringWithId с отсылкой на мать. (Если показатель 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(), делает владельца матери также владельцем нового котика и запрашивает функцию _createKitty(), которую мы уже видели в контракте KittyBase.


    Обратите внимание, что функция geneScience.mixGenes() — это «кот в мешке», потому что код закрыт. Так что мы не знаем наверняка, как определяются гены ребенка, но мы знаем, что на них влияют функции генов матери, генов отца и временная метка отдыха матери cooldownEndBlock.


    5. KittyAuctions: покупаем, продаем и выставляем котиков на торги


    Здесь у нас находятся открытые методы выставления котиков на аукционы по продаже и вязке котиков. Сам функционал аукциона осуществляется в двух отдельных контрактах (один для продаж, второй для вязки), а создание аукционов и торги происходит через этот раздел основного контракта.



    Разработчики разделили функционал аукционов на независимые контракты, потому что, по их словам: «логика аукционов довольно сложна и всегда существует риск появления мелких багов. Если они будут храниться в своих собственных контрактах, мы сможем их обновлять, не мешая работе основного контракта, который отслеживает права собственности на котиков».


    Так что в контракте KittyAuctions содержатся функции setSaleAuctionAddress() и setSiringAuctionAddress(), которые, как и setGeneScienceAddress(), могут быть вызваны только пользователем CEO. С их помощью можно указать адреса внешних контрактов, которые будут выполнять эти функции.


    Примечание: «Вязка» означает выставление услуг вашего котика на аукцион. Вы выступаете своего рода сутенером, ведь другие пользователи будут платить вам эфир за возможность скрестить своего котика с вашим.



    Это значит, что даже если сам по себе контракт CryptoKitties является неизменным, у CEO есть возможность позже изменить адрес этого контракта аукциона, и это автоматически изменит правила. Опять же, это не всегда плохо, потому что разработчикам периодически нужно исправлять ошибки. Просто примите этот факт во внимание.


    Не будем углубляться в рассуждения о логике аукционов и торгов, иначе эта статья рискует стать слишком длинной (а она и так уже очень длинная!), вы можете сами посмотреть код на сайте EthFiddle (по ключевому слову KittyAuctions).


    6. KittyMinting: фабрика по производству котиков поколения 0


    Последняя часть контракта содержит функционал, который мы используем для создания котиков поколения 0. Мы можем сделать до 5000 «промо-котиков», которых можно отдать (это особенно важно для нового сообщества), а других придется создавать и сразу же выставлять на аукцион, а стартовая цена будет определяться особым алгоритмом. Вне зависимости от способа создания, существует строгое ограничение в 50 тысяч котиков поколения 0. А после уж придется размножаться, размножаться и еще раз размножаться!



    В этом контракте строго прописано количество промо-котиков и котиков поколения 0, которое вы можете создать:


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

    А вот код, в котором пользователь “COO” может создавать промо-котиков и котиков поколения 0:


    /// @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 может создавать новых котиков с любыми понравившимися ему генами, а также передавать котика кому угодно (всего он может создать 5000 таких котиков). Предполагаю, что такая функция используется для первых тестировщиков, друзей, родственников. Также бесплатные котики раздаются с целью продвижения игры.


    Но это также означает, что ваш котик может и не быть таким уникальным, как вам кажется, ведь COO может наштамповать целых 5000 его клонов!


    В функции createGen0Auction() пользователь COO также указывает генетический код нового котенка. Но вместо отсылки на адрес конкретного пользователя он создает аукцион, в котором пользователи будут делать ставки в эфириумах, чтобы купить котенка.


    7. KittyCore: Главный контракт


    Это основной контракт игры CryptoKitties, составленный и запущенный в блокчейне Ethereum. Именно этот контракт собирает все остальные воедино.


    Так как структура контрактов последовательна, последний собирает данные всех контрактов, с которыми мы познакомились ранее, и добавляет к ним несколько финальных методов, например, эту функцию для получения всех данных котика с помощью его учетной записи:


    /// @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;
    }
    

    Это публичный метод, который покажет все данные конкретного котика из блокчейна. Я так думаю, что для отображения котиков на сайте сервер игры запрашивает именно эту команду.


    Подождите… Я не вижу данных образа. Что же определяет внешний вид котика?


    Как мы можем видеть из кода выше, котик сводится к 256-битному неподписанному целому числу, которое представляет собой генетический код.


    В контрактном коде Solidity нет данных о внешнем виде котика, нет его описания или данных, которые определяют значение 256-битного целого числа. Интерпретация генетического кода котика осуществляется на веб-сервере CryptoKitty.


    И хотя это довольно грамотная демонстрация игры в блокчейне, она не на 100% размещена в блокчейне. Если сайт игры сломается и никто не сохранит резервную копию всех образов, у нас останутся лишь бессмысленные 256-битные целые числа.


    В контрактном коде я нашел контракт под названием ERC721Metadata, но он нигде не используется. Так что я предполагаю, что изначально разработчики планировали хранить все в блокчейне, но позднее передумали (возможно, хранить такое количество данных на платформе Ethereum слишком дорого?), так что было решено хранить образы на веб-сервере.


    Подведем итог


    Что мы узнали:


    • Как котики представляют собой структуру данных
    • Как все существующие котики хранятся в одном смарт-контракте и как этот контракт следит за тем, какой пользователь какими котиками владеет
    • Как создаются котики поколения 0
    • Как котики скрещиваются для получения новых котят

    За помощь в переводе большое спасибо Саше Ивановой!

    Если вы хотите получить более подробное руководство по созданию своей собственной игры,
    то рекомендую посетить ресурс CryptoZombies

    • +10
    • 4,1k
    • 3
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 3

      0
      За помощь в переводе большое спасибо Саше Ивановой!

      А где ссылка на оригинал?
        0
        Первая часть опубликована как перевод, там же и ссылка. Сейчас уже не могу перевести статью в формат перевода, Хабр не дает :(

        На всякий случай — оригинал на medium.com
        0
        Если погуглить криптозомби, то можно найти набор уроков по созданию подобных проектов

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое