Brainstorming the SnailLife gene system

In the original PHP prototype of SnailLife there exists a very rudimentary genetics system. Snails had what I called “visual traits” and “functional traits”. Visual traits had genes associated with them. The genes were all stored in the snail’s table and each gene had two “alleles”. During breeding I’d make virtual Punnett Squares of sorts to pass on genes from parents to offspring. But the system was inflexible - each allele for each gene and each gene type were hard coded in the snails table. This left little room for surprise emergent behaviour (ie is it possible for a snail to accidentally get more or fewer variations of a gene than expected? I’m not sure, but I want a system to support that) without having to modify db columns).

Functional traits were passed down from parents, but had no formal gene definition - they were just a number and that number from each parent, with a small degree of randomization to simulate “mutation”, got passed down to the offspring.

I knew that in the rewrite I wanted to tackle this in a better way and really think it through. When I wrote my previous post about building the snail one organ at a time, I sort of optimistically left out one part - each organ needs to have genes associated with its various properties.

Problem: I know nothing about genetics and my recollection of my biology class is…limited.

Anyway, after some brainstorming and light reading about how chromosomes work (and I mean light), I came up with a very rough first iteration of how the genetics system might work with the Go version of SnailLife. Everything is likely to change, but I outline progress so far below.

Genes

The idea this time around is to have a single gene potentially influence the expression of various properties in many different organs. No longer is a “functional trait” just a number and no longer is a “visual trait” just associated with one visual thing. There may be no “eye color” gene, but there may be a “Gene X” and “Gene Y” both of which have some influence over a snail’s eye color.

A Gene struct is defined as follows in the organism package:

package organism

type Gene struct {
	Id            string
	Label         string
	Alleles       Alleles
	DominanceType DominanceType
}

type DominanceType int

const (
	Complete   DominanceType = iota
	Incomplete DominanceType = iota
	Co         DominanceType = iota
)

Alleles are variations of a gene. Each snail will have one of each snail gene, but the thing stored in the database will actually be the Alleles, ie the variations of the gene. Each snail, if the organism is functioning is planned, will have two variations of every gene. An Allele is defined as follows:

package organism

type Allele struct {
	Id             int
	GeneId         string
	Modifier       float64
	DominanceLevel int
}

type Alleles []Allele

func (a Alleles) Len() int {
	return len(a)
}

func (a Alleles) Less(i, j int) bool {
	return a[i].DominanceLevel > a[j].DominanceLevel
}

func (a Alleles) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

Gene expression

As mentioned, I want each gene to optionally influence any number of organs without necessarily knowing anything about the organs. The gene is just a gene, it has no idea what other parts the organism is made up of. My idea right now is to have an interface associated with each gene, and whatever organs implement the interface will have the gene expressed in some way. I am starting with a basic “size” gene to experiment with and calling it SizeA1 (for now):

package gene

import (
	"gitlab.com/drakonka/gosnaillife/common/domain/organism"
	"gitlab.com/drakonka/gosnaillife/common/domain/snail/organ"
)

type SizeA1 struct {
	organism.Gene
}

func (g *SizeA1) Init() {
	g.Id = "size_a1"
	g.DominanceType = organism.Complete
	g.Alleles = g.getVariations()

}

func (g *SizeA1) getVariations() organism.Alleles {
	// Variations are "Big" and "Small"
	bigA := organism.Allele{
		GeneId:         g.Id,
		Modifier:       1.00,
		DominanceLevel: 1,
	}

	smallA := organism.Allele{
		GeneId:         g.Id,
		Modifier:       -1.00,
		DominanceLevel: 0,
	}

	return organism.Alleles{bigA, smallA}
}

type SizeA1Expresser interface {
	ExpressSizeA1(o *organ.Organ) error
}

So above we give the gene an ID and define the possible variations of the gene. In this case, the variations are “big” and “small”. The big variation is dominant. We also specify the DominanceType for the gene (the possible types right now are complete dominance, incomplete dominance, and co-dominance). The Modifier is the thing that will actually change the phenotype of the organism in some way (in this case size of the organ).

“Expressers”

Up above we defined a SizeA1Expresser interface for the SizeA1 gene with a single method - express the gene - which takes the organ we are influencing. Now, each tick we can express the gene in relation to whatever organ types implement the expresser for the interface. I started with the outer_shell organ type for this original brainstorm:

package organ

import (
	"gitlab.com/drakonka/gosnaillife/common/domain/organism"
	"sort"
)

type outershell struct {
	organism.Type
}

func NewOuterShell() Organ {
	t := outershell{}
	t.Init()
	o := newOrgan(&t)
	return o
}

func (t *outershell) Init() error {
	t.IdealQuant = 1
	t.Id = "outer_shell"
	return nil
}

func (t *outershell) ExpressSizeA1(o *Organ) error {
	owner, err := o.Owner()
	if err != nil {
		return err
	}
	if owner.IsMature() {
		return nil
	}
	// Find both SizeA1 genes and express according to dominance
	gene := owner.Genes()["size_a1"]
	alleles := gene.Alleles
	// Sort alleles by dominance
	sort.Sort(alleles)

	// Handle different dominance types
	// The organ type shouldn't really have to care what TYPE
	// of dominance the gene is set to..it should handle whatever cases
	// are possible..maybe...not sure yet.
	switch gene.DominanceType {
	case organism.Complete:
		o.WeightMg += int(alleles[0].Modifier)
		return nil
	case organism.Incomplete:
		t.expressIncompleteDominance(alleles, o)
		return nil
	}

	return nil
}

func (t *outershell) expressIncompleteDominance(alleles organism.Alleles, o *Organ) {
	highestDom := alleles[0].DominanceLevel
	for _, a := range alleles {
		if a.DominanceLevel < highestDom {
			break
		}
		o.WeightMg += int(a.Modifier)
	}
}

Nothing actually happens yet - I don’t even have a table for the snail’s gene variations. I think I’ll keep going down this route for a while and see where it takes me. Everything is kind of a mess right now, but I think this may be an OK general direction.

comments powered by Disqus