Ruby Quiz

Ruby Quiz - Challenge #10 - Breeding Kitties - Mix Genes Using the Sooper-Sekret Formula in the GeneSciene CryptoKitties Blockchain Contract

CryptoKitties lets you breed new kitties. Pick a matron and a sire and a new bun is in the oven.

Now how does the “magic” mixing of genes work? What genes do new (offspring) kitties inherit from parents? What about mewtations, that is, new traits not present in a matron or sire?

The bad news is all CryptoKitties contracts are open source EXCEPT the “magic” sooper-sekret gene mixing operation formula in the GeneSciene contract. You can find the byte code in the contract at etherscan.io/address/0xf97e0a5b616dffc913e72455fde9ea8bbe946a2b#code. If you click on “Switch to Opcode” you will see an almost endless stream of to-the-metal stack machine byte code instructions:

PUSH1 0x60
PUSH1 0x40
MSTORE
PUSH1 0x04
CALLDATASIZE
LT
PUSH2 0x006c
JUMPI
PUSH4 0xffffffff
PUSH29 0x0100000000000000000000000000000000000000000000000000000000
PUSH1 0x00
CALLDATALOAD
DIV
AND
PUSH4 0x0d9f5aed
DUP2
EQ
PUSH2 0x0071
JUMPI
DUP1
PUSH4 0x1597ee44
EQ
PUSH2 0x009f
JUMPI
DUP1
PUSH4 0x54c15b82
...

Now the good news - thanks to Sean Soria’s reverse engineering work - see the article “CryptoKitties mixGenes Function” - the code is now “cracked” and an open book. Let’s look at the mixGenes function in pseudocode:

def mixGenes(mGenes[48], sGenes[48], babyGenes[48]):
  # PARENT GENE SWAPPING
  for (i = 0; i < 12; i++):
    index = 4 * i
    for (j = 3; j > 0; j--):
      if random() < 0.25:
        swap(mGenes, index+j, index+j-1)
      if random() < 0.25:
        swap(sGenes, index+j, index+j-1)

  # BABY GENES
  for (i = 0; i < 48; i++):
    mutation = 0
    # CHECK MUTATION
    if i % 4 == 0:
      gene1 = mGenes[i]
      gene2 = sGenes[i]
      if gene1 > gene2:
        gene1, gene2 = gene2, gene1
      if (gene2 - gene1) == 1 and iseven(gene1):
        probability = 0.25
        if gene1 > 23:
          probability /= 2
        if random() < probability:
          mutation = (gene1 / 2) + 16

    # GIVE BABY GENES
    if mutation:
      babyGenes[i] = mutation
    else:
      if random() < 0.5:
        babyGenes[i] = mGenes[i]
      else:
        babyGenes[i] = sGenes[i]

Yes, that’s better (but not quite ruby-esque). The challenge: Code a mixgenes method that passes the RubyQuizTest :-), that is, turn the pseudocode into working code that you can run at your very own computer off the (block)chain with a vanilla scripting language, that is, ruby. Note: The mixgenes methods gets passed in mgenes - the matron’s 48 genes (as an array of integers) and sgenes - the sire’s 48 genes (as an array of integers) and returns babygenes - the new baby’s 48 genes (as an array of integer):

def mixgenes( mgenes, sgenes )  ## returns babygenes
  babygenes = []
  # ...
  babygenes
end

Start from scratch or, yes, use any library / gem you can find.

To qualify for solving the code challenge / puzzle you must pass the test.

Note: To get always the same rand(om) numbers for testing , the RubyQuizTest sets a “deterministic” seed for the rand() method, that is, srand( 123 ). Now if you call rand() #=> 0.6964691855978616, rand() #=> 0.28613933495037946, rand() #=> 0.2268514535642031 you always get the same rand(om) numbers.

require 'minitest/autorun'

class RubyQuizTest < MiniTest::Test

  def test_mixgenes
    mgenes     = [12, 11, 12, 14, 15, 8, 9, 9, 2, 3, 1, 1, 19, 5, 3, 7, 16, 4, 6, 0, 9, 13, 13, 9, 19, 4, 2, 4, 0, 0, 12, 3, 23, 8, 3, 8, 6, 14, 3, 9, 19, 7, 6, 4, 9, 11, 12, 12]
    sgenes     = [9, 9, 11, 14, 23, 15, 8, 14, 3, 7, 6, 5, 3, 19, 6, 6, 4, 6, 3, 5, 6, 6, 14, 8, 2, 4, 7, 2, 0, 0, 12, 0, 15, 15, 8, 10, 6, 14, 14, 6, 5, 4, 4, 5, 20, 9, 8, 11]

    babygenes1 = [9, 9, 11, 14, 23, 8, 9, 14, 3, 3, 5, 1, 3, 19, 3, 7, 16, 4, 6, 5, 9, 6, 14, 8, 19, 4, 2, 4, 0, 0, 12, 3, 23, 15, 8, 10, 6, 14, 3, 9, 19, 5, 6, 5, 20, 9, 11, 8]
    babygenes2 = [12, 9, 11, 11, 15, 23, 9, 9, 5, 2, 3, 6, 3, 19, 5, 6, 4, 4, 3, 5, 9, 6, 13, 9, 19, 4, 7, 4, 0, 0, 12, 12, 15, 3, 8, 10, 6, 3, 14, 9, 19, 5, 5, 4, 9, 9, 11, 8]
    babygenes3 = [12, 12, 14, 11, 15, 23, 8, 14, 3, 1, 3, 1, 19, 3, 7, 6, 16, 5, 6, 3, 6, 6, 13, 9, 19, 4, 2, 4, 0, 0, 0, 12, 23, 3, 8, 8, 6, 6, 14, 14, 4, 19, 6, 7, 9, 12, 9, 11]

    srand( 123 )
    assert_equal babygenes1, mixgenes( mgenes, sgenes )
    assert_equal babygenes2, mixgenes( mgenes, sgenes )
    assert_equal babygenes3, mixgenes( mgenes, sgenes )
  end

end # class RubyQuizTest

Post your code snippets on the “official” Ruby Quiz Channel, that is, the ruby-talk mailing list.

Happy data wrangling and genome genetics bits & bytes slicing with Ruby.