How to Code Your Own CryptoKitties-Style Game on Ethereum

James
Loom Network
Published in
11 min readDec 4, 2017

--

CryptoKitties has done a great job of demonstrating what blockchains can be used for beyond just simple financial transactions.

I’m hoping we’ll start to see a lot more innovative uses of the blockchain for games like this in the future, so I wanted to write a quick walkthrough of the code behind CryptoKitties to show how it was implemented beneath the surface.

This article is written for developers, and while it’s not an absolute beginner’s introduction to Solidity, I tried to include links to documentation to make it as accessible as possible to developers of all levels.

Let’s dive in…

CryptoKitties Source Code

Almost all of the CryptoKitties code is open source, so the best way to figure out how it works is to read through the source code.

It’s around 2,000 lines in total, so in this article, I’m going to be walking through only the sections I think are most important. But here’s a copy of the full contract code on EthFiddle, if you want to read through it on your own:

High Level Overview

If you’re not familiar with what CryptoKitties is, it’s basically a game for buying, selling, and breeding digital cats. Each cat has a unique appearance that is defined by its genes, and when you breed 2 cats together, their genes combine in a unique way to produce an offspring, which you can then breed or sell.

The code for CryptoKitties is split into a number of smaller contracts in order to keep related code bundled together without having one single giant file with everything in it.

The subcontract inheritance for the main kitty contract looks like this:

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

So KittyCore is ultimately the contract address the app points to, and it inherits all the data and methods from the previous contracts.

Let’s walk through these contracts one by one.

1. KittyAccessControl: Who Controls the Contract?

This contract manages the various addresses and constraints for operations that can be executed only by specific roles. Namely CEO, CFO, and COO.

This contract is for management of the contract, and isn’t related to the game mechanics at all. It basically has ‘setter’ methods for the “CEO”, “COO”, and “CFO”, which are Ethereum addresses that have special ownership and control over particular functions of the contract.

KittyAccessControl defines some function modifiers like onlyCEO (which restricts a function so that only the CEO can perform it), and adds methods to do things like pause / unpause the contract or withdraw funds:

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

The pause() function was probably added so that the developers could update it with a newer version in case there were any unforeseen bugs… But as my colleague Luke pointed out, this would actually allow the developers to freeze the contract completely, making it so no one can transfer, sell, or breed their kitties! Not that they would ever want to do so — but it’s interesting to point out, since most people assume that a DApp is totally decentralized just because it’s on Ethereum.

Moving on…

2. KittyBase: What is a Kitty, Really?

This is where we define the most fundamental code shared throughout the core functionality. This includes our main data storage, constants and data types, plus internal functions for managing these items.

KittyBase defines a lot of the core data of the app. First, it defines a Kitty as a struct:

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

So a kitty is really just a bunch of unsigned integers… I guess.

Breaking down each piece of this:

  • genes — a 256 bit integer representing the cat’s genetic code, the core piece of data that determines what a cat looks like
  • birthTime — a timestamp of the block when the cat was born
  • cooldownEndBlock — the minimum timestamp after which this cat can engage in breeding again
  • matronId & sireId — the ID of the cat’s mother and father, respectively
  • siringWithId — this is set to the ID of the father if the cat is currently pregnant, zero otherwise
  • cooldownIndex — the current cool down duration for this cat (how long the cat has to wait after breeding before it can breed again)
  • generation — the “generation number” of this cat. The first cats minted by the contract have generation 0; the generation for new cats is the larger of their parents generations, plus 1.

Note that in CryptoKitties, cats are asexual and any 2 cats are allowed to reproduce together — thus a cat does not have a gender.

The KittyBase contract then goes on to declare an array of theseKitty structs:

Kitty[] kitties;

This array holds the data of all the Kitties in existence, so it’s kind of like a master Kitty DB. Whenever a new cat is created, it is added to this array, with it’s index in the array becoming the cat’s ID, like Genesis here having an ID of ‘1’:

My array index is “1”!

This contract also contains a mapping from the cat’s ID to the address of its owner to keep track of who a kitty is owned by:

mapping (uint256 => address) public kittyIndexToOwner;

Some other mappings are also defined, but for the sake of keeping this article a reasonable length, I’m not going to go over every detail.

Whenever a kitty is transferred from one person to the next, this kittyIndexToOwner mapping is updated to reflect the new owner:

Transfering ownership sets `kittyIndexToOwner` of the Kitty’s ID to the recipient’s `_to` address.

Now let’s look at what happens when a new kitty is created:

So this function is passed the IDs of the mother and father, the kitty’s generation number, the 256-bit genetic code, and the address of the owner. It then creates the kitty, pushes it to the master Kitty array, and then calls _transfer() to assign it to its new owner.

Cool — so now we can see how CryptoKitties defines a kitty as a data type, how it stores all the kitties on the blockchain, and how it keeps track of who owns which kitties.

3. KittyOwnership: Kitties as Tokens

This provides the methods required for basic non-fungible token transactions, following the draft ERC721 spec.

CryptoKitties conforms to the the ERC721 token spec, a non-fungible token type that lends itself really well to tracking ownership of digital collectables like digital playing cards or rare items in an MMORPG.

Note on Fungibility: Ether is fungible, because any 5 ETH is just as good as any other 5 ETH. But with non-fungible tokens like CryptoKitties, not every cat is created equal, so they’re not interchangeable with each other.

You can see from its contract definition that KittyOwnership inherits from an ERC721 contract:

contract KittyOwnership is KittyBase, ERC721 {

And all ERC721 tokens follow the same standard, so the KittyOwnership contract fills in the implementation of the following functions:

Since these methods are public, this provides a standard way for users to interact with CryptoKitties tokens in the same way they’d interact with any other ERC721 token. You can transfer your tokens to someone else by interacting directly with the CryptoKitties contract on the Ethereum blockchain without having to go through their web interface, so in this sense you really own your kitties. (Unless the CEO pauses the contract 😉).

I won’t go over the implementation of all these methods, but you can check them out on EthFiddle (search for “KittyOwnership”).

Intermission: Want to Build Your Own Ethereum Game?

This article received a ton of positive feedback when it was first published, so we built CryptoZombies: an interactive tutorial for building your own games on Ethereum. It will walk you step by step through learning to code Solidity, and building your own Ethereum game. If you’re enjoying the article, check it out!

4. KittyBreeding: Cats Get Down and Dirty

This file contains the methods necessary to breed cats together, including keeping track of siring offers, and relies on an external genetic combination contract.

The “external genetic combination contract” (geneScience) is stored in a separate contract, which is not open source.

The KittyBreeding contract contains a method for the CEO to set the address of this external contract:

They did it this way so the game would not be too easy — if you could just read how a kitty’s DNA was determined, it would be a lot easier to know which cats to breed in order to get a “fancy cat”.

This external geneScience contract is later used in the giveBirth() function (which we’ll see in a bit) to determine the new cat’s DNA.

Now let’s see what happens when two cats are bred together:

So this function takes the ID of the mother and father, looks them up in the master kitties array, and sets the siringWithId on the mother to the father’s ID. (When siringWithId is non-zero, it indicates that the mother is pregnant).

It also executes triggerCooldown on both parents, which makes it so they can’t breed again for a set amount of time.

Next, we have a public giveBirth() function which creates a new cat:

The in-line commenting on the code is pretty self-explanatory. Basically the code first performs some checks to see if the mother is ready to give birth. It then determines the child’s genes using geneScience.mixGenes(), assigns ownership of the new kitty to the mother’s owner, then calls the _createKitty() function we looked at in KittyBase.

Note that the geneScience.mixGenes() function is a black box, since that contract is closed-source. So we don’t actually know how the child’s genes are determined, but we know it’s some function of the mother’s genes, the father’s genes, and the mother’s cooldownEndBlock.

5. KittyAuctions: Buying, Selling, and Pimpin’ of Cats

Here we have the public methods for auctioning or bidding on cats or siring services. The actual auction functionality is handled in two sibling contracts (one for sales and one for siring), while auction creation and bidding is mostly mediated through this facet of the core contract.

According to the devs, they split this auction functionality into “sibling” contracts because “their logic is somewhat complex and there’s always a risk of subtle bugs. By keeping them in their own contracts, we can upgrade them without disrupting the main contract that tracks kitty ownership.”

So this KittyAuctions contract contains the functions setSaleAuctionAddress() and setSiringAuctionAddress(), which like setGeneScienceAddress() can only be called by the CEO, and sets the address of an external contract that handles these functions.

Note: “Siring” is referring to pimping out your cat — putting it up for auction, where another user can pay you Ether for your cat to breed with theirs. Lols.

This means that even if the CryptoKitties contract itself were immutable, the CEO has the flexibility to change the address of these auction contracts down the line, which would change the rules of the auctions. Again, not necessarily a bad thing since sometimes developers need to fix bugs, but something to be aware of.

I’m not going to go in depth into how exactly the auction & bidding logic is handled to keep this article from getting too long (it’s already long enough!), but you can check out the code in EthFiddle (search for KittyAuctions).

6. KittyMinting: The Gen0 Cat Factory

This final facet contains the functionality we use for creating new gen0 cats. We can make up to 5000 “promo” cats that can be given away (especially important when the community is new), and all others can only be created and then immediately put up for auction via an algorithmically determined starting price. Regardless of how they are created, there is a hard limit of 50k gen0 cats. After that, it’s all up to the community to breed, breed, breed!

The number of promo cats and gen0 cats the contract is able to create is hard-coded here:

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

And here’s the code where the “COO” can create promo kitties and gen0 kitties:

So with createPromoKitty(), it looks like the COO can create a new kitty with whatever genes he wants, and send it to whoever he wants (up to a max of 5000 kitties). I’m guessing they use this for early beta testers, friends and family, to give away free kitties for promotion purposes, etc.

But this also means that your cat may not be as unique as you think, since he can potentially print 5000 identical copies of it!

For createGen0Auction(), the COO also provides the genetic code for the new kitty. But instead of assigning it to a specific person’s address, it creates an auction where users would bid Ether to buy the kitty.

7. KittyCore: The Master Contract

This is the main CryptoKitties contract, which is what is compiled and running on the Ethereum blockchain. This contract ties everything together.

Because of the inheritance structure, it inherits from all the contracts we looked at prior to this, and adds a few more final methods, like this function for getting all a Kitty’s data using its ID:

This is a public method that will return all the data for a specific kitty from the blockchain. I imagine this is what is queried by their web server for displaying the cats on the web site.

Wait… I don’t see any image data. What determines what a kitty looks like?

As we can see from the code above, a “kitty” basically comes down to a 256-bit unsigned integer that represents its genetic code.

There’s nothing in the Solidity contract code that stores a cat’s image, or its description, or that determines what that 256-bit integer actually means. The interpretation of that genetic code happens on CryptoKitty’s web server.

So while this is a really clever demonstration of a game on the blockchain, it’s not actually 100% blockchain-based. If their website was taken offline in the future, unless someone had backed up all the images, you would be left with only owning a meaningless 256-bit integer.

In the contract code, I did find a contract called ERC721Metadata, but it never ends up getting used for anything. So my guess is they originally planned on storing everything in the blockchain, but later decided against it (too cost prohibitive to store a lot of data in Ethereum?) and so they ended up needing to store it on their web server instead.

Tying It All Together

Ok… So to sum everything up, here we’ve looked at:

  • How kitties are represented as data
  • How all kitties in existence are stored in a single smart contract, and how it keeps track of who owns what
  • How gen0 kitties are produced
  • How kitties are bred together to form new kitties

This was just a top-level overview. If you’re looking for a more in-depth tutorial on how to build your own game, you may be interested in an interactive code school we’ll be releasing within the next couple weeks.

UPDATE: We released the interactive code school. Check it out at CryptoZombies.io!

Loom Network is the multichain interop platform for scaling high-performance dapps — already live in production, audited, and battle-tested.

Deploy your dapp to Loom’s Basechain once and reach the widest possible user base across all major blockchains today.

New to Loom? Start here.

Want to stake your LOOM tokens and help secure Basechain? Find out how.

Like what we’re doing here? Stay in the loop by signing up for our private mailing list.

--

--

James
Loom Network

Entrepreneur turned investor. Writer, musician, adventurer, student of the world.