• 如何在以太坊上編寫自己的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風險。 通過保留它們自己的合約,我們可以升級它們而不會中斷追蹤小貓所有權的主合約。“
    因此,這個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小貓如何生產
    • 小貓如何在一起繁殖,形成新的小貓

    原創文章,如若轉載,請注明出處:http://www.42652650.com/2018/03/322983

    發表評論

    電子郵件地址不會被公開。

    關注微信
    新疆体育彩票11选5