Evolution and natural selection are fundamental principals on which science bases the existence and origin of all natural living beings. The fundamental rules of this process are very simple but provide a vastly powerful means by which adaptation to a particular environment can take place. Evolution is covered to varying depths in biology text books, explaining how these principals influence life on this planet.
This blog looks at some of the principals behind evolution and selection and how this paradigm started to find its way into a relatively new field of computer science, artificial intelligence.
In his book “The Blind Watchmaker”, Richard Dawkins explores the mechanism of pure random development and contrasts this with guided random development that uses incremental selection. He then proceeds to turn these observations into a computer program that uses randomness and selection to evolve graphical shapes called biomorphs.
What I find nice about his approach is that it demonstrates evolution in an interactive fashion, allowing the user to take an active role in forming the shape of the biomorphs. Many advanced evolutionary programming techniques used in software development do not provide this kind of visual feedback.
The LiveCode application demonstrated in this blog shows how to create visual shapes similar to the biomorphs Richard Dawkins created. It also uses similar principles of data representation and data visualisation, but I call the figures LiveMorphs. You can construct the application by copying the functions and commands into the card script of a LiveCode stack and invoking the openCard handler, for example, by saving your stack and then reopening it or by calling the openStack handler from the message box.
The idea behind the application is that each LiveMorph has an underlying representation, similar to a gene that stores the unique representation of a living being. This representation or genotype is then converted into a physical representation that draws the LiveMorph. The physical representation is also referred to as phenotype. Separating the encoding of the representation from the actual representation is very important. It allows us to choose a genotype that can easily be exposed to modifications that are based on evolutionary rules. Purists may argue that the genotype should, as closely as possible, represent the genetic encoding of base pairs, and there clearly are valid reasons for doing so. I prefer to choose the genetic representation and the type of modification depending on what feels best for the application. The genotype for the LiveMorphs is a list of 13 integers that is set in the openCard handler and is represented as follows:
"10,10,10,10,10,10,10,10,10,10,10,10,10"
Using mutation, this is the default genotype from which all future generations are evolved:
function mutateGene pGene
local tGene, tItem
// Cycle through each inter in the genotype
repeat with tItem = 1 to the number of items in pGene
if tItem is a number then
// Mutate each integer and ensure the value is within a set range
put min (max (item tItem of pGene + random (21) - 11, -50), 50) & comma after tGene
end if
end repeat
return tGene
end mutateGene
We use min and max to restrict the values in the mutation. Using a more purist binary representation would remove the need for this limit at the mutation stage, allowing us to focus more on true evolutionary rules. You can also see that we only use mutation to change information between subsequent LiveMorphs. Evolution clearly provides other options that can be applied, for example, when genes from more than one organism are combined, but that is beyond the scope of this blog post.
We now have the functional mechanism to represent and change the genotype of our LiveMorphs. The next consideration should be to decide on how this genetic representation is transformed into the physical representation. We have a lot of freedom to consider how to generate the phenotype. In this example, I use the graphic of buttons to represent the phenotype of LiveMorphs:
on genotypeToPhenotype pBtn
local tPhenotype, tGenotype, tX, tY
// Get the gene that is to be translated into the graphical shape
put the cGene of button pBtn into tGenotype
// Get the location where the shape is to be placed
put item 1 of the location of button pBtn into tX
put item 2 of the location of button pBtn into tY
put tX & comma & tY & return into tPhenotype
// Create the first side of the phenotype
repeat with tItem = 1 to the number of items in tGenotype step 2
put tX + item tItem of tGenotype & comma & tY + item tItem + 1 of tGenotype & return after tPhenotype
end repeat
put line 1 of tPhenotype & return after tPhenotype
// Create the second side of the phenotype
repeat with tItem = 1 to the number of items in tGenotype step 2
put tX - item tItem of tGenotype & comma & tY + item tItem + 1 of tGenotype & return after tPhenotype
end repeat
// Ensure the shape is closed
put line 1 of tPhenotype after tPhenotype
// Display the shape
set the points of graphic pBtn to tPhenotype
end genotypeToPhenotype
The main operation lies in two repeat loops that cycle through the genotype. The first repeat loop generates one side of the visual representation and the second loop generates the other side of the visual representation. This gives us a symmetric phenotype, similar to most living beings we are used to seeing in real life.
We now have the main building blocks for evolving LiveMorphs. Let us now add the remaining LiveCode sections that complete this application.
The openCard handler initialises the environment and ensures that all operational components are available for when we use the application:
on openCard
local tItem
// Ensure that any past operation of this code are removed
repeat with tItem = 1 to the number of controls
delete control 1
end repeat
// Set the parameters of the templateButton for our use
set the showName of templateButton to false
set the height of templateButton to 200
set the width of templateButton to 200
set the width of this stack to 400
set the height of this stack to 400
set the style of templateGraphic to "polygon"
set the opaque of templateGraphic to true
// Create four buttons that are to display the LiveMorphs
repeat with tItem = 1 to 4
new button tItem
new graphic tItem
// Add random colour to each LiveMorph being displayed
set the backgroundColor of graphic tItem to random (50)
end repeat
// Place the buttons
set the location of button "1" to 100,100
set the location of button "2" to 300,100
set the location of button "3" to 100,300
set the location of button "4" to 300,300
// Set up the default gene of each LiveMorph
set the cGene of this card to "10,10,10,10,10,10,10,10,10,10,10,10,10"
mouseUp
end openCard
First, possible previous execution content of the application is removed. Default button information is then set up that allows the buttons to appear in the appropriate size to display graphical objects. Next, four buttons are created and placed in a grid, presenting us with the user interface for the application. At this point we also add a random colour that is part of the LiveMorph on the particular button. Finally, we add the default genetic representation of the LiveMorphs and tell the application to mutate this information, using the code we discussed earlier in this blog.
The remaining section of code implements the selection of the parent whose gene is to move forward into the next generation to create new LiveMorphs. This is possibly the main aspect of the code as it provides part of the fitness function in which all LiveMorphs compete for selection. The user selects which LiveMorph is fit for selection by pressing the button of the LiveMorph that is visually most appealing. After selection, the gene of the chosen LiveMorph is used to create the next generation of four LiveMorphs from which yet again a new parent can be selected for mutation. This feature is implemented in a mouseUp handler as follows:
on mouseUp pButton
local tBtn, tParentGene
if target begins with "graphic" then exit mouseUp
put the cGene of target into tParentGene
repeat with tBtn = 1 to 4
set the cGene of button tBtn to mutateGene (tParentGene)
genotypeToPhenotype tBtn
end repeat
end mouseUp
This blog introduces an interesting aspect of Artificial Intelligence, using keywords that allow you to explore this field further in an appropriate Internet search engine. I also hope that the code demonstrated here is sufficiently short to allow you to make your own modifications and generate customized LiveMorphs with much altered properties.
Join the conversation