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.