Хабрахабр

Создаем свои криптокотиков (Часть 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

Теги
Показать больше

Похожие статьи

Кнопка «Наверх»
Закрыть