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

в 6:03, , рубрики: blockchain, cryptocurrencies, erc20, Ethereum, solidity, token, биллинговые системы, монетизация игр, ненормальное программирование, Программирование, разработка игр

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

Автор: Сергей

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js