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

Hello,

  It's Friday. Ruby Quiz time! Join us. Let's keep going with a new
Ruby Quiz [1] every fortnight. Here we go:

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
<https://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 [2] -
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 [3].

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.

[1] https://github.com/planetruby/quiz/tree/master/010
[2] https://medium.com/@sean.soria/cryptokitties-mixgenes-function-69207883fc80
[3] https://github.com/planetruby/quiz/blob/master/010/test.rb

Hello,
   FYI: You can find now all sent-in / posted code snippets /
solutions for the Ruby Quiz Code Challenge #10 - Breeding Kitties -
Mix Genes Using the Sooper-Sekret Formula in the GeneSciene
CryptoKitties Blockchain Contract in the solution.rb script [1].
Happy coding with ruby.

[1] https://github.com/planetruby/quiz/blob/master/010/solution.rb