Greetings to you, habrazhiteli!
In continuation of the
post “Analog of the game“ Life ”- Evo” I would like to give a more detailed description of the teams of the “gene language” that is used in
Evo , and share my thoughts on the methods of crossing individuals in this game.
Genetic code
The genetic code of individuals in
Evo is a sequence of bytes of almost unlimited length. In addition, each byte is interpreted as a command for a cellular automaton. The machine has 2 tapes: commands and memory. Accordingly, there are two
cmd_ptr and
mem_ptr pointers . As the animal's RAM, the variable
data is used , with a dimension of 1 byte (8 bits).
Currently there are the following commands:
- 9 commands to manage the execution of the "genetic" code:
- nop - do nothing
- move_cmd_left - move the command pointer to the left
- move_cmd_right - move the command pointer to the right
- jump_to - move the command pointer to the right by X positions (the following byte is treated as a signed char, that is, negative values move the pointer to the left)
- jump_to_ifz is the same as jump_to, but under the condition that the value data == 0
- jump_to_ifnz - the same as jump_to, but provided that the value data! = 0
- start is the same as nop , just a label for the start of a separate piece (the beginning of a function)
- restart - resets the cmd_ptr value to 0
- end - the end of the function, make the search further on the tape the start label, from which to continue execution (if there is no label, then cmd_ptr = 0 )
- 8 commands that allow living creatures to look around themselves (the results are stored in the living animals RAM, in the data variable):
- eye_up_distance - get the distance to the nearest object up
- eye_down_distance - get the distance to the nearest object down
- eye_left_distance - get the distance to the nearest object to the left
- eye_right_distance - get the distance to the nearest object to the right
- touch_up - get the type of the nearest object up
- touch_down - get the type of the nearest object down
- touch_left - get the value of the type of the nearest object to the left
- touch_right - get the value of the type of the nearest object to the right
- 10 live memory management commands:
- move_mem_left - move the mem_ptr pointer to the left
- move_mem_right - move the mem_ptr pointer to the left
- save_to_mem - save the contents of live animals RAM ( data ) to the current cell
- load_from_mem - load the value from the current cell into the living creature RAM ( data )
- add_mem - add to data the value of the current memory cell
- sub_mem - subtract from data the value of the current memory cell
- set_mem_ptr - set mem_ptr equal to the value of the RAM
- data_clear - clear RAM
- data_inc - increment the value in RAM
- data_dec - decrement the value in RAM
- 12 commands for actions that animals can do (transmit signals to the World object):
- action_move_left - living creatures moving left
- action_move_right - wildlife moves left
- action_move_up - wildlife moves to the left
- action_move_down - wildlife moves left
- action_eat_left - animals trying to eat the object on the left
- action_eat_right - animals trying to eat the object on the left
- action_eat_up - animals trying to eat the object on the left
- action_eat_down - animals trying to eat the object on the left
- action_wait - animal waiting, skipping
- action_suicide - animals self-destruct
- action_split - animal life is shared by cloning
- action_split_mutate - animal life is divided by cloning, but with some mutations
Sample program
In principle, such a set of commands is enough to make loops and branches. Another question is that describing algorithms in such a language is not as convenient as in C ++, for example.
But for example, I’ll show you a program that makes animals move in a square spiral:
start action_split load_from_mem //load from var1 move_mem_right //save var2 data_dec save_to_mem move_mem_right //save to var3 data_dec save_to_mem //right action_move_right data_dec jump_to_ifnz AnimalCommand(-3) //up load_from_mem data_dec save_to_mem action_move_up data_dec jump_to_ifnz AnimalCommand(-3) //left load_from_mem data_dec save_to_mem action_move_left data_dec jump_to_ifnz AnimalCommand(-3) //down load_from_mem data_dec save_to_mem action_move_down data_dec jump_to_ifnz AnimalCommand(-3) //dec var2 move_mem_left load_from_mem data_dec save_to_mem jump_to_ifnz AnimalCommand(-33) move_mem_left // restore var1 end;
Three values are loaded into memory: 10, 0, 0. I will not explain the algorithm. I think it's pretty obvious.
This code can be found in
mainwindow.cpp : 117.
About mutations
Mutations in the game are now implemented as follows. At the request of the organism to multiply by cloning with a mutation, a clone is created. The clone undergoes mutations in an amount of from 0 to 9 (at the discretion of the great random house). For each mutation, the great random chooses one of 4 options:
- 1 arbitrary byte of code is replaced by an arbitrary value.
- random byte is added to the DNA
- DNA is bitten off byte in arbitrary position
- an arbitrary byte is inserted into the DNA in an arbitrary position (fresh commit )
')
About crossbreeding
We now turn to reflections on how we cross two organisms. Their code may be of different lengths, they may have different memory.
- The first thing that comes to mind is simply to make 2 DNAs into one longer. But in this case, the second part of the algorithm may not function at all, or it may work extremely rarely. Well, well - it will actually be a recessive gene.
- We can take, say, 3 arbitrary points on each DNA and assemble a new one according to the principle: piece 0-1 from the first DNA, 1-2 from the second, 2-3 from the first, 3-4 from the second. And the pieces can be zero length.
- You can search for start | end tags and sculpt new DNA by simply putting together functions from different organisms. This will force the living creatures to try to somehow structure the code.
- There is an option to take a loop on a byte from each organism until their DNA runs out. It turns out some such Frankinstein, but its properties will be very interesting.
Here, perhaps, and all the options that immediately come to my head. Over the inheritance of memory is not thinking.
BUT! I forgot about something else. What do you think is better?
- At the end of the X rounds, take the Y-most-most and cross them. At the same time kill the entire population, populate the world with these Y individuals and their descendants.
- Every X rounds, take Y (from 2 to 5, say) the strongest and cross them, adding their offspring to the population.
- The third option - the one that you come up with and write in the comments.
Thoughts on the subject are not thick, of course, but I hope for the rich imagination of the audience.
Thank. Waiting for feedback.