Ryan Coleman :
Learning By Simulating Evolution Using Corewars
Abstract
The goal of this project is to prove that corewarriors can learn by simulating
evolution. I will try to show that the corewarriors have learned survival out of
evolution. This will be accomplished using a computer program that simulates
evolution using genetic algorithms. This paper was first prepared and presented
in an Artificial Intelligence class at Wilmington College in the Spring semester
of 1998. The class was taught by Jim Fitzsimmons, Ph. D.
Background
Evolution
Evolution is a concept put forward by Charles Darwin in his The Origin of
Species. After lots of debate, it has become generally accepted among
scientists. (Winston, Patrick Henry, 507). In summary form, the driving
principles of evolution are:
"-Each individual tends to pass on its traits to its offspring.
-Nevertheless, nature produces individuals with different traits.
-The fittest individuals--those with the most favorable traits--tend to have
more offspring than do those with unfavorable traits, thus driving the
population as a whole toward favorable traits.
-Over long periods, variation can accumulate, producing entirely new species
whose traits make them especially suited to particular ecological niches."
(Winston, Patrick Henry, 507)
These theories are very important in many fields of science today. The third
theory is also known as the theory of natural selection, or survival of the
fittest. Today, in genetic algorithms, there are three methods used in genetics
to change the code. (Winston, Patrick Henry, 512) They are mutation,
reproduction, and crossover. Mutation, the simplest of all, involves changing a
single piece of code, or inverting the same piece of code. Reproduction is
rewarding good code with more replicas of itself in the set. Crossover involves
cutting two pieces of code in half at some point, and then switching the tail
end around. Many arguments have been made as to which of these holds the most
importance, but, it is still being debated. (Peters, James A.) Also, many
different methods of each kind have been developed, such as crossover where two
points are picked, and only the code in between are switched.
Corewars
Corewars is a game created by A. K. Dewdney in a series of articles in
Scientific American. (Dewdney, A.K., The Armchair Universe) The 'core' in
corewar was an early name for memory. A core is a linear looping linear memory
with each place in memory able to hold one complete instruction. By linear, I
mean that instructions are referenced by a single number, no matrix or array is
used. The memory is said to loop because after the last instruction in memory
the first instruction can be executed. This could be pictured as a circle. It is
also worth mentioning that there are no absolute references in corewars,
everything is referenced in terms of the instruction executing. There are
seventeen (or nineteen) instructions that can be used in corewars. By far, the
most important instruction in corewar is the DAT instruction. No matter what the
A and B fields contain, a DAT terminates the process if that process executes
the DAT. Notice process is used and not program. A program in corewar can split
into two or more processes by use of the SPL instruction. Two programs are
loaded into random positions in the core where they battle it out. After a
certain number of cycles, if neither program has quit executing, a tie is
declared. If one program terminates, the other is declared the winner.
Basically, if you can make all your opponent's processes execute a DAT, you win.
This is where the 'war' in corewar comes in. A brief description of each
instruction, modifier, and addressing mode is given in Appendix A of this paper.
To fully understand corewars I suggest the Frequently Asked Questions list of
rec.games.corewar. (Durham, Mark, et. al.)
Another aspect of corewars was the early creation of 'King of the Hill'
tournaments. (Durham, Mark, et. al.) The name is based on the game played on a
dirt pile, or hill, where everyone tries to run up and push the person at the
top off. Each hill has a specific set of standards which people write warriors
for. Entries are submitted via e-mail, and a script that will run on a web
-browser is in the works. After the battles are run, you receive results back.
Usually, there are 20 to 25 warriors on the hill that you must score better than
to get on the hill and knock one of them off. Usually, 100 battles take place
between your warrior and each of the other warriors. As far as standards go, the
main hill now has a coresize of 8000, it goes 80000 cycles before a tie, has
8000 processes, a maximum of 100 instructions, awards 3 points for a win, 1 for
a tie, and 0 for a loss. This hill also does fights on a one-on-one basis. There
are other hills, such as a limited process hill, a multiwarrior hill (everyone
fights at the same time), a beginner's hill where 'newbies' are invited to test
their code, and a 'baby' hill. The baby hill is interesting because the number
of instructions are limited to 20, the coresize is 800, and it only goes 8000
processes before a tie. This makes the baby hill run much faster than all its
counterparts.
I should also make mention of the different types of warriors that have been
created over the years. There are three basic types that follow the idea of the
childhood game paper-rock-scissors. A paper is a replicator. It makes many
copies of itself and usually has lots of processes. Papers are usually durable
and usually have a small bombing routine at the end of their code to help kill
the opponent. A rock or stone is a warrior that bombs the core with DATs or
other bombs, hopefully landing a bomb on the opponent. A scissor is a warrior
that scans the core for other warriors, and then bombs them. In general, a paper
beats a stone, a stone beats a scissor, and a scissor beats a paper. It should
also be noted hybrids and many other forms of these warriors exist. No one
strategy dominates corewars today, rather a good warrior uses elements from all
of them to win.
Corewars Lingo
This is a section introducing the reader to the jargon that has become common to
corewars. First, off, as has already been mentioned, bombing. Bombing consists
of using a MOV instruction to move one instruction over another. The instruction
that gets moved is called the bomb. For example, let's look at a very simple
bomber, written by A. K. Dewdney (Dewdney, A. K., The Armchair Universe):
mov.i $3, $7
add.ab #4, $-1
jmp.a $-2, #0
dat.f #0, #0
Execution starts at the MOV instruction. This instruction moves whatever is at
the location 3 places ahead of it, in this case the DAT instruction, to 7
instructions ahead of it. Execution continues to the ADD instruction which adds
4 to the B-field of the instruction before it, the MOV. This changes the 7 to an
11. The JMP instruction sends execution back to the MOV, which bombs again.
Let's look at how two of these warriors would fight against each other and how a
DAT bomb kills.
mov.i $3, $7 <-- execution for this warrior starts here
add.ab #4, $-1
jmp.a $-2, #0
dat.f #0, #0
(empty line)
(empty line)
(empty line)
(empty line)
mov.i $3, $7 <-- execution for this warrior starts here
add.ab #4, $-1
jmp.a $-2, #0
dat.f #0, #0
(empty line)
(empty line)
(empty line)
(empty line)
. . .
First off, the empty lines are initialized as DAT.F #0, #0. By using the empty
line I can better show what each warrior is doing. The MOVs bomb the fourth
empty line and the ADDs change the values of the B-field of the MOV instruction.
Next, the JMPs send execution back the each MOV. The core now looks like this:
mov.i $3, $11 <-- executing here
add.ab #4, $-1
jmp.a $-2, #0
dat.f #0, #0
(empty line)
(empty line)
(empty line)
dat.f #0, #0
(empty line)
mov.i $3, $11 <-- executing here
add.ab #4, $-1
jmp.a $-2, #0
dat.f #0, #0
(empty line)
(empty line)
(empty line)
dat.f #0, #0
. . . (lots more empty lines)
Now, the first warrior puts a DAT right on his opponents JMP. The second
warrior, because of its position this round, has to bomb all the way around the
core before it can bomb the other warrior. Such is chaos. Anyways, after both
warriors bomb and add again, the core looks like this:
mov.i $3, $15
add.ab #4, $-1
jmp.a $-2, #0 <--executing here
dat.f #0, #0
(empty line)
(empty line)
(empty line)
dat.f #0, #0
(empty line)
mov.i $3, $15
add.ab #4, $-1
dat.f #0, #0 <-- executing here
dat.f #0, #0
(empty line)
(empty line)
(empty line)
dat.f #0, #0
(empty line)
(empty line)
(empty line)
dat.f #0, #0
. . . (lots more empty lines)
Now, the first warrior successfully jumps back to it's MOV instruction. The
second warrior executes the DAT statement and that process terminates. If the
second warrior had more than one process, it could still run, but since it only
has one, it has lost. It should be noted you can use other forms of bombing,
such as:
jmp.x #0, #0
Which 'stun' the opponent and all processes that execute the JMP will jump back
to the same line for the rest of the round unless acted upon. This will slow
down and perhaps stop the other warrior from executing harmful statements to
you.
Another piece of jargon--splitting. A program can use the SPL instruction to
create two or more processes. It can also be used to add durability. If we add a
SPL statement to the beginning of Dewdney's warrior, we get:
spl.a #0, #0
mov.i $3, $7
add.ab #4, $-1
jmp.a $-2, #0
dat.f #0, #0
What this does is have one process executing the MOV instruction and one still
executing the SPL instruction. This will always be true. The SPL instruction
will continue to create more and more processes which all execute the
MOV/ADD/JMP part of the warrior. If the SPL instruction gets bombed with a DAT,
the warrior can still execute fine like the previous version. If the JMP
statement gets hit with a DAT bomb, the warrior is still fine because the SPL
creates processes all the time which execute the MOV and ADD statements, then
terminate on the DAT. If one of the MOV or ADD statements gets hit by a DAT
bomb, the warrior still survives, but doesn't do anything useful.
Now let's look at the DJN instruction. This instruction does many things all at
once. A simple warrior that uses only the DJN instruction would look like:
djn.a $0, <-1
What happens when this warrior executes? Let's see how this warrior works:
dat.f #0, #0
djn.a $0, <-1 <--executing here
. . . (empty lines)
The warrior will use the B-field of the DAT instruction before it, as a pointer
to decrement. Because of the .A addressing mode, the A-field of whatever is
pointed to is decremented. In this case, the DAT is decremented. But, because of
< modifier, the pointer also gets decremented. Also, the DJN instruction jumps
to whatever is pointed to by the A-field of the DJN instruction, so it jumps
back to the same place. After a couple executions, the core looks like this:
dat.f #-1, #0
dat.f #-1, #0
dat.f #-1, #-3
djn.a $0, <-1 <--executing here
. . . (empty lines)
So, this warrior will decrement the A-field of every instruction in core
eventually. This could interfere with opponent's code, but what if it doesn't
and this warrior doesn't win after one pass. Let's see what happens after it has
decremented almost every instruction in core. Remember, the core loops around
itself, so eventually, it should be expected that the DJN instruction could
decrement itself:
dat.f #-1, #0
dat.f #-1, #0
dat.f #-1, #-799
djn.a $0, <-1 <--executing here
dat.f #-1, #0
dat.f #-1, #0
dat.f #-1, #0
. . . (empty lines)
The -799 is used because I ran all my simulations with a coresize of 800. If the
coresize were 8000, then -7999 would be here. Now, if it executes this time,
look what happens:
dat.f #-1, #0
dat.f #-1, #0
dat.f #-1, #-800
djn.a $-1, <-1 <--executing here
dat.f #-1, #0
dat.f #-1, #0
dat.f #-1, #0
. . . (empty lines)
Now, the DJN jumps to whatever is pointed to in its A-field. It has previously
been 0, jumping back to the same instruction. But now it is -1. It will jump
back to the DAT statement and it will terminate there. This is called a suicidal
warrior. If this warrior doesn't successfully terminate the opponent, it will
terminate itself.
Throughout this experiment, references will be made as to speed at which
warriors move bombs through the core. c is commonly the variable used for the
speed of light. It is also used in corewars as the variable representing the
speed of light. If a program bombs at 100%c, it bombs one instruction for every
instruction it executes. Dewdney's warrior illustrated earlier bombs at 33%c
because it bombs one instruction for every three instructions it executes.
Another kind of warrior is called a coreclear. A coreclear works by using either
<, >, {, or } to change the position of the bomb each time sequentially through
core. Here's a simple coreclear:
mov.i $2, <-1
jmp.a $-1, #0
dat.f #0, #0
After a few cycles where the pointer is decremented, the core looks like this:
dat.f #0, #0
dat.f #0, #0
dat.f #0, #0
dat.f #0, #0
dat.f #0, #-5
mov.i $2, <-1
jmp.a $-1, #0
dat.f #0, #0
. . . (empty lines)
This will eventually put a DAT bomb everywhere in the core. This kind is called
a backwards coreclear because the bombs are laid down sequentially backwards
using the <. If it was a > and you changed the pointer location to 3, then it
would be a forward coreclear.
Another change in the standard suicidal coreclear is to make it repeating. A
repeating backwards coreclear would look like this:
mov.i $2, <3
jmp.a $-1, #0
dat.f #0, #-4
dat.f #0, #-4
Obviously, the first DAT instruction is bombed throughout the core backwards.
Let's look at what happens when the warrior would commit suicide with the old
instructions:
(more of the same dat.f #0, #-4) . . .
dat.f #0, #-4
mov.i $2, <3
jmp.a $-1, #0
dat.f #0, #-4
dat.f #0, #-799
dat.f #0, #-4
dat.f #0, #-4
. . . (more dat.f #0, #-4)
Now let's see what happens when the MOV executes:
(more of the same dat.f #0, #-4) . . .
dat.f #0, #-4
mov.i $2, <3
jmp.a $-1, #0
dat.f #0, #-4
dat.f #0, #-4
dat.f #0, #-4
dat.f #0, #-4
. . . (more dat.f #0, #-4)
Now the warrior will begin placing DAT instructions before its code. It will
continue to do this until the cycles have run out or it has won.
The next terminology that can be defined is the use of pointers. Let's look at
the following version of a coreclear:
mov.i $2, <-1
jmp.a $-1, <-2
dat.f #0, #0
Now for what happens after the MOV and JMP instructions have been executed once:
. . . (more empty lines)
dat.f #0, #0
dat.f #0, #-2
mov.i $2, <-1
jmp.a $-1, <-2
dat.f #0, #0
. . . (more empty lines)
Notice at this point in a normal coreclear, the DAT pointer would only be -1,
but instead it is -2. Watch how this warrior bombs through core:
. . . (more empty lines)
(empty line)
dat.f #0, #0
(empty line)
dat.f #0, #0
(empty line)
dat.f #0, #0
dat.f #0, #-6
mov.i $2, <-1
jmp.a $-1, <-2
dat.f #0, #0
. . . (more empty lines)
Every other line gets bombed in this example. The ones in between are not
touched, but this kind of warrior is better because it has a better chance of
finding and terminating the opposing warrior before a normal coreclear would.
A self-mutating warrior is meant to change it's own form at specific, hopefully
key instances. Let's change Dewdney's warrior that bombs a DAT every fourth
position to the following:
spl.a #0, #0
mov.i $3, $5
add.ab #4, $-1
jmp.a $-2, #0
dat.f >-1, #0
Now, instead of bombing with a DAT instruction with numbers of 0, it uses those
to it's advantage. Execution is exactly the same, until it has bombed all the
way around the core and is about to bomb over itself. Here is what the core
would look like:
. . .(more empty lines with dat.f >-1, #0 thrown in every fourth position)
dat.f >-1, #0
(empty line)
spl.a #0, #0
mov.i $3, $1 <--- about to execute this instruction
add.ab #4, $-1
jmp.a $-2, #0
dat.f >-1, #0
(empty line)
dat.f >-1, #0
(empty line)
(empty line)
(empty line)
dat.f >-1, #0
. . .(more empty lines with dat.f >-1, #0 thrown in every fourth position)
Now look what happens. The MOV puts the DAT bomb over its own ADD instruction.
Now look how the warrior executes:
. . .(more empty lines with dat.f >-1, #0 thrown in every fourth position)
dat.f >-1, #0
(empty line)
spl.a #0, #0
mov.i $3, $1
dat.f >-1, #0 <--- about to execute this instruction
jmp.a $-2, #0
dat.f >-1, #0
(empty line)
dat.f >-1, #0
(empty line)
(empty line)
(empty line)
dat.f >-1, #0
. . .(more empty lines with dat.f >-1, #0 thrown in every fourth position)
Now look what happens. The DAT terminates the process, but not before it
increments the previous instructions B-field. This just happens to be where the
MOV will throw it's next DAT bomb. This warrior that initially bombed every
fourth position in the core with a DAT bomb turns itself into a 33%c forward
coreclear. This improvement is called self mutation.
It has been mentioned that only a DAT instruction terminates a process. This
isn't always true. Two other instruction can serve to terminate processes. These
are DIV and MOD. Both of these instructions deal with divison. If either of them
are asked to divide by zero, they terminate as this is mathematically
impossible. This could be compared to a Divide by Zero error message you may
receive while debugging programs.
What's been done with Evolutionary Computing Before
Non-corewars
The first mention of the words genetic algorithm occurs in a paper published in
1967 by Bagley. (Goldberg, David E., 92) He used genetic algorithms to develop
game player controls for a 'hexapawn' game, basically a 3 x 3 matrix with each
opponent having three pawns at either end of the board. He used mutation,
reproduction, and crossover to produce strategies for winning this game.
Many other early pioneers worked out genetic algorithms to develop answers to
specific problems only, not applicable to any other field or program. An
excellent list of these and many other exploits into genetic algorithms are
contained in Genetic Algorithms in Search, Optimization, and Machine Learning.
(Goldberg, David E., 126-127)
John H. Holland did the first major series of works on genetic algorithms 23
years ago at the University of Michigan. (Gibbs, W. Wayts, 1) His methods
required using only problems that were analogous to actual chromosomes.
Therefore, the problems that could be tackled were very limited. Even with these
restrictions Holland did quite a large number of tests. (Goldberg, David E.,
126-127)
In 1992, John R. Koza, extended Holland's methods to solve programs of any size
and form. (Gibbs, W. Wayts, 1) The field has virtually exploded. Koza's methods
have inspired a variety of work, at least two Genetic Programming Conferences,
and he claims 51 Ph.D. theses are in progress dealing with genetic computation
of some sort. (Genetic Programming.org Home Page) (Koza, John)
A few examples where evolved programs have been comparable to human-written
programs appeared in (Gibbs, W. Wayts, 1). One example used genetic algorithms
to create a program to control a prosthetic hand. A program has also been
evolved that is capable of maneuvering a spacecraft 10 percent faster than a
solution designed by an expert. Of course these are only a few examples of what
has been done. Over 800 papers have been written since 1992 that deal primarily
with genetic or evolutionary programming. (Langdon, W. B.).
One of the major problems with these evolved programs is their serious lack of
readability. (Gibbs, W. Wayts, 3) The genetic algorithms do not produce programs
that use comment sections, procedures, or any means of understanding the code.
Typically, they become monolithic pieces of code. These take much more time to
read and decipher, and even more time for humans to modify than normal programs.
They may even contain useless instructions that could not be readily separated
from the useful ones.
Corewars
The first published work on evolving corewarriors was a paper written by John
Perry. (Perry, John, 1) In his paper he describes an experiment he did in which
he eventually submitted two of his evolved warriors into the International
CoreWars Society Tournament. His warriors got second to last and third to last.
Perry's experiments were generally inconclusive because he only ran 2
generations and the 'seed' warriors were handwritten proven warriors. He did
another test with 1000 randomly generated seed warriors and ran 3 generations,
but in these he tried to mimic the function of assembly programs. I should point
out that assembly programs are rather different than corewarriors. Although, it
should be noted, when he did his experiment, the processing speed of computers
was very far behind today's standards and he was limited by that. Even though
his experiments were slow, they still showed promise and had good ideas.
The next work that I could find on evolved corewarriors was not a 'paper' but
rather a long piece of source code with a rather long comment section. (Boer,
Jason, ga_war.c) I eventually ran across his homepage devoted to evolving
corewarriors (Boer, Jason, Jason's Corewar Project Page). This system used
mutation and reproduction in a somewhat controllable (by means of a
configuration file) population of warriors. I eventually decided to base my
experiments on his source code, but that comes later. Eventually, with personal
computer processing speeds much higher than ever before dreamed of, Jason was
able to evolve some very tough little warriors. It should be noted these
warriors ran on a 'standard' corewar size of 8000. No outside warriors are ever
tested against the evolved ones to determine fitness, so it is a closed system.
This is something good because no clear evolutionary goal is present, except to
defeat your brothers/sisters. It's drawbacks are speed limitations because of
lots of hard disk accesses. It also takes a longer time to test programs that
wait 80000 cycles until a tie is declared.
It should be noted that the best program Jason Boer ever evolved came in 25th,
at the very bottom of the beginner hill. He ran thousands of generations, and
changed the variables a lot, and could only evolve a semi-decent program for a
'beginner' hill. This was quite an accomplishment in itself, but still leaves
much to be desired.
There are a few people who have used Jason Boer's code to evolve their own
warriors. Besides that, they changed little except varying the mutation rates
and number of warriors in each population. Some have evolved better warriors,
but little has been done to improve results to the point where they could be
competitive with human written warriors.
Why Corewars
In The Selfish Gene, there is a situation called the Prisoner's Dilemma.
(Dawkins, Richard, 209) In it he describes the conditions that must be met for a
good dilemma of this type. They are best represented by the following chart.
A corewarrior can defect by executing offensive code like bombing with DATs. It
can cooperate by keeping to itself, and not interfering with the opponent. This
strategy doesn't occur frequently, because almost all corewarriors are
programmed to defect.
As you may notice, corewars is an excellent tool to use to examine the
consequences of this dilemma. Unfortunately, the game does not rely simply on if
you cooperate or defect, but mainly how well you defect due to the fact that
Defect-Defect cannot occur. In fact, if you notice, the best choice is to defect
no matter what your opponent does. So, the method you use to defect becomes the
factor that determines the best program.
John Perry, in his paper gave several reasons for using corewars. (Perry, John,
1-4) Corewars has many advantages because it does not mimic any natural form of
life. The main similarity between real life and corewars is that in both places,
the main urge is survival. He also believes that Dawkins would have liked a
corewars experiment with randomly seeded code because there is no clear
evolutionary goal. People have used corewars to evolve warriors that function
well against a test warrior, but these warriors often fail miserably when they
fight against a different warrior. Warriors in a closed system like I will be
using have no goal, they may evolve into unpredictable forms.
Setup of Experiment
I used Jason Boer's ga_war.c (Boer, Jason, ga_war.c) as the primary basis for my
experiments. I changed the source code to run under a baby-size hill. This, I
believe, led to much faster results, because in theory it should take 1/10th the
time to run a program that goes 8000 cycles before a tie than one that goes
80000. It also let me reduce the maximum warriors length from 100 to 20. This
reduced the possible number of warriors, and it eliminated more processing time.
I also excluded the instruction using P-space (see Appendix A) because of their
complexity and use only in advanced warriors.
The following is a mathematical representation of the possible numbers of
different warriors for a baby-size hill assuming a maximum number of 100. It
also assumes non-usage of the two P-space instructions:
Number of possible Opcodes per instruction = 17
Number of possible Modifiers per instruction = 14
Number of possible Addressing Modes per instruction = 14
Number of possible numbers -100 to M = 201
--------
Number of possible instructions = 669732
Where x is the number of instructions = x^20 + x^19 + . . . x^2 + x^1
--------------
Total number of possible warriors. 3.296 x 10^116
That's a lot of possible warriors. That is way too many to search then entire
space randomly. Now the same for a standard size hill, here are the same
calculations:
Number of possible Opcodes per instruction = 19
Number of possible Modifiers per instruction = 14
Number of possible Addressing Modes per instruction = 14
Number of possible numbers -1000 to M = 2001
-------
Number of possible instructions = 7451724
Where x is the number of instructions = x^100 + x^99 + . . . x^2 + x^1
--------------
Total number of possible warriors = 1.681 x 10^687
If the number of possible baby warriors was bad, this is much worse. This number
is amazingly huge. To search through the entire space would be nearly
impossible. By reducting the number of warriors to baby hill standards, things
are improved, but even there the number is much too big to work through
entirely. It will be many times more likely to hit upon a very good warrior in a
baby size hill than a standard size hill if you speak only in random terms.
It is necessary to explain what running a generation means with ga_war.c. The
first warrior in the population randomly fights for a certain number of battles
one other warrior. If they tie, they are modified according to the chances
dictated by the configuration file. If one wins outright, the winner replaces
the loser's code with his own code. If one has a limited win, the winner
replaces the loser's code with a modified version of itself. All these variables
are controlled by the configuration file. Each warrior in a population does this
once per generation. If the random selection of opponents is good, this means
that each corewarrior will fight twice per generation.
All of these factors put together led me to develop my corewarriors with baby
size standards rather than normal. Mainly among these was processor and hard
drive speed constraints. The next step was to decide what variables to use for
the configuration file. These can be changed after the program has stopped
running however many generations you told it to. All the options except
population size and number of battles were functions of percentages.
I decided on a population size of 100. I hoped 100 would be big enough to
promote diversity, but not too big to slow down the system. The next variable
was mutation rate, or how often a random piece of instruction would be modified.
Several sources, including Jason's homepage had some suggestions as far as this
goes. (Boer, Jason, Jason's Corewar Project Page) I eventually settled on
initial mutation rates of around 15 and varied them down to 4 or 5 as time went
along. The insertion and removal rates were used to insert or remove additional
individual instructions. I decided they should be kept close together, as to
avoid all warriors having a maximum length of 20 or a minimum length of 1.
Hopefully by keeping the rates for insertion and removal similar and from around
5 to 15 I could keep these two extremes from happening. The resurrection rate
was how often an older version of the warrior would be brought back from the
dead. This was added in case all the current warriors were worse than the
previous ones. The resurrection rate should be kept somewhat low, and to promote
new mutations instead of constantly overwriting any progress the warriors have
made. I kept it below 10.
Again, processor time was a constraint on the number of battles for each warrior
to fight against its brethren. I chose to start at around 30 and vary it up to
50 and even 100 for some of the final generations.
I had two computers on which I did all of these experiments. They are both IBM
compatible PCs. One is MS-DOS based, and the other is Windows '95 based,
although it was restarted in MS-DOS mode to run the simulations. One has a true
Pentium chip, the other has an equally fast 5x86 processor. The 5x86 machine has
40 megabytes of memory and a rather slow hard drive. The Pentium machine has 16
megabytes of memory and a much faster hard drive. Hard drive speed proved to be
important, as the evolution program accesses the disk quite a bit.
I set up both computers to do, originally two separate populations of warriors.
Because of file naming requirements, each set was identified by only two
letters. The set run on the 5x86 machine was called AB. The set run on the
Pentium machine was called AC.
The ga_war.c program uses pMARS to test each warrior. pMARS is a simulator that
loads the programs, runs them, and gives the results. (Durham, Mark, et. al.)
Everyone in the corewars community is indebted to Albert Ma, Nandor Sieben,
Stefan Strack, and Mintardjo Wangsaw for writing pMARS.
I also used a freeware program called MARS Tournament Scheduler to compare all
100 warriors in each generation gap, so to speak, when I would tweak variables.
They were stored in separate directories for comparison later. The tournament
scheduler also accesses pMARS to run the battles. Again thanks go out to Stefan
Strack for writing this handy program.
It should also be noted that the ga_war.c program uses only mutation and
reproduction. It accomplishes mutation by randomly selecting a modifier, number,
addressing mode, or opcode and changing it. Again, I used a mutation rate
between 5% and 15%. It accomplishes reproduction in an unusual way. The
population size remains constant throughout the entire simulation. Instead of
actually reproducing, any corewarrior that can beat its opponent consistently
replaces the opponent with a verbatim copy of itself. If a corewarrior beats its
opponent by some margin, but not a clear victory, it replaces the opponent with
a mutated version of itself. If the two tie, insertion, removal, mutation, or
resurrection can occur.
I feel the exclusion of crossover was a good idea because of the way
corewarriors work in general, and as I found out later, work with evolved
corewarriors. Most warriors either had some non-useful code followed by the
functional code or had functional code followed by code that was never executed.
This is the case for some human written warriors, but every corewarrior I looked
at had this form. Let's do a crossover here:
Warrior 1 Warrior 2
[Good, functional code] [non-useful code]
--------------------------------------Crossover here----------------------------
[non-useful code] [Good, functional code]
The warriors become:
Warrior 1 Warrior 2
[Good, functional code] [non-useful code]
[Good, functional code] [non-useful code]
Now, what do we have? One warrior which will never access the second part of
it's code even though it is useful, and a warrior that will probably die very
quickly. I can see no reason to turn two perfectly decent warriors into one
decent warrior and one that is junk. For that reason, I believe not using
crossover is a good idea for corewarriors.
Results of Experiment
Population 'AB'
This was the population run on the 5x86 machine. I ran 1000 generations of 100
warriors. Using the MARS Tournament Scheduler program I compared the populations
at intervals of 100, 150, 200, 250, 300, 375, 450, 525, 600, 725, 900, and 1000
generations. These numbers were more an arbitrary convenience than planned
setup. The number of generations was set by how long I could leave the computer
running. Instead of providing complete reports, I have chosen to highlight some
of the developments that took place during each run.
After 100 generations, the warriors had learned quite a bit. The first trick
they learned to use was self-splitting by use of the SPL opcode. This usually
took the form of:
spl.a #x, y
It really doesn't matter what numbers you put in. Splitting to a number
addressed using # basically causes the program to execute that instruction and
the one after it unless acted upon. If the one after it is bombed, the SPL can
survive by itself. The second trick was using one of the decrements or
increments <, >, {, or } to bomb consecutively through the core using a mov.i
instruction like:
mov.i $x, <-y
This combined with a SPL bombed backwards through the core once before bombing
itself. Basically, if the other warrior has avoided dying by way of DAT
statement, it wins because this warrior has put DATs over its own code.
One problem with the warriors at this point was the fact that they usually
terminated on a DAT following their MOV line. This slowed down the bombing. They
also usually had to progress through some beginning code that really had little
effect on the outcome except to slow themselves down more.
After 150 generations, the warriors only learned one new trick. This trick was
exemplified by the following line of code after the basic SPL instruction:
mov.i {10, >-21
This piece of code is very interesting. The instruction that is used to bomb
through the core is constantly decremented, so therefore, it is usually a
different instruction. For example, the first time the warrior executes the MOV
line, whatever is pointed to by the A-field of the instruction 10 ahead is used.
Then, it is decremented and the instruction previous to that is used as the
bomb. In an empty core, this leads to bombing DAT everywhere since that is what
the core is initially full of. And, since the location that is bombed is
incremented, the DAT usually will get put over the enemy's code.
More than that, this coreclear is repeating. The only reason for this is because
the first execution moves an instruction with a B-field of 73 to the pointer
location for the position to be bombed. If this pointer got bombed by an
instruction with a B-field number of less than 21, this coreclear would commit
suicide. Barring that and getting a DAT thrown on it by the other program, this
corewarriors will bomb until all 8000 cycles are used up. This has an advantage
just in case the other warrior survived the first pass. It also increases
overall survivability, as most of the previous warriors committed suicide after
bombing once through the core, they bombed themselves and died.
For this warrior, it takes 3 instructions to bomb 1 instruction. It is a 33%c
coreclear. It also decrements one instruction per 3 instructions, which can help
mess up other warriors.
After 200 generations, a major change happened. A warrior emerged which was 13%
better than the next warrior after a complete comparison. Usually before, a
margin of 1% was rare, but now one warrior was consistently beating its
brethren. Here are the functional (the parts that are used more than once) of
this warrior:
spl.f # 9, {-39
mov.i < 11, >-20
dat.ba < 10, @-63
The reason this warrior was so much better has to do with the placement of the
pointers. The MOV line uses a pointer 11 ahead of it, while the DAT line (which
terminates, but still decrements), uses one only 10 ahead of it. Since the DAT
line is immediately after the MOV line, the are using the same line for their
pointer. In effect, the MOV executes, decrementing once, and then the DAT
executes, decrementing a second time. This doesn't improve the speed of the
warrior, it is still 33%c, but it changes the bomb used more frequently. By not
simply copying data around, instead copying every other location to every
location at some other point, the odds of the opponent surviving are
considerably less. Another advantage this warrior had was that it was a forward
coreclear. All other warriors in this population seem to be backward coreclears.
Apparently, the reasons this warrior scored well weren't just in it's code, but
in the code of the rest of the population.
This was an amazing evolutionary jump. This tough little warrior represents the
fact that these warriors can learn better ways to defeat their brethren. This
one did it, and I have decided this warrior learned a new way to defeat anything
else present at that time. And to think, all this after only 200 generations.
After 250 generations, nothing really new happened. Apparently, the advantages
evolved in the previous round was lost when everyone got it, and since it wasn't
helping, it got mutated away. The warriors were still repeating coreclears with
a speed of 33%c.
After 300 generations, another very important piece of code came up. The basic
warrior still consisted of a SPL and MOV line, but instead of terminating on a
DAT, or jumping to random consecutive places through the core, this warrior
evolve a line that made use of that line. It was to use the following:
jmp.f $ -1, #y
The first part is all that really matters. Instead of terminating that process,
it jumps backwards 1 instruction, to the MOV instruction. This does help out
speed, as it approaches an upper limit of 50%c as more and more processes get
caught in the MOV-JMP loop. It also helps out durability. Now if either the
first or last functioning line gets hit, the warrior still functions by
continuing its coreclear. If the SPL line is hit, the processes keep jumping
back to the MOV line. If the new JMP line is hit, the SPL keeps producing new
processes that keep the coreclear going. So, instead of being completely
vulnerable to a single hit, this program can survive and still function properly
as long as the MOV isn't hit. If the MOV is hit by a DAT, the SPL would keep
producing new processes that would terminate on the DAT, keeping it alive.
Now, after 375 generations, the warriors learned a new trick. Instead of
evolving a new line of code, they got rid of one. They kept the new JMP
instruction, but lost the SPL instruction. There is one key advantage to this.
The speed is improved to bombing one location every 2 instructions instead of 3.
This makes the efficiency in the order of 50%c for the entire time. This is just
slightly faster than the best warriors from the previous round. Now, if you care
to figure it out, a program with 50%c efficiency will consistently win over a
program with 33%c or 25%c efficiency. It will even beat out a warrior that has a
speed approaching 50%c. Once again, the corewarriors learned a new, faster
strategy, and took advantage of less fortunate siblings to gain more wins. Of
course, now, the population was saturated with 50%c warriors. Since they won
more, they reproduced more. Something new had to develop, or real stagnation
could occur.
Well, unfortunately, after 450 rounds, no new strategies had evolved. The top 75
warriors represented a difference of less than 10%. The population had surely
become stagnant, and all that could be hoped for was a luck mutation that
somehow helped the warriors.
Well, fortunately for the population, something new did develop after 525
generations. The JMP to the previous instruction was tossed away as something
better was found. It didn't decrease the efficiency, it didn't make it less
durable (or more durable), but it did something completely different. It took
the form of:
djn.a $ -1, < -9
This particular line, when executed, will decrement the value of the A-field
pointed to by the B field, in this case, -9, and decrement the 'B' field. In
this case, whatever is at the -9 location is also decremented, so next time the
same thing will be done to the previous instruction and so on. Next, control is
passed to the previous instruction, as the -1 eludes to. So, it functions the
same as a JMP -1 instruction, but adds the bonus of decrementing a number. Why
decrement? Well, it can help. For instance if the opponent's warrior consisted
of the following:
mov.i $10, <-1
jmp.a $-1, #0
You could defeat him by only using the DJN instruction. This can happen because
if his critical A-field of the JMP instruction is decremented, it changes it to
-2. The next time it is executed, your opponent will jump back 2 instead of 1.
If the previous instruction is a DAT, the he terminates, and you win. If it
isn't, let's say it's a NOP (no operation), just for fun, his efficiency is
decreased to 33%c. If yours is still at 50%c, then your chances of bombing him
first are much higher than his against bombing you.
After 600 generations, there was a major change as far as strategy. The four
functional lines of the best program were:
spl.b # 15, @-62
add.x <-28, { 34
mov.i * 51, < -8
djn.a $ -1, < -9
The first SPL line functions the same way that all of them have so far. The ADD
instruction may at first seem useless and time consuming, but it is not. The MOV
instruction and the DJN instruction function exactly like the previous version
except these two use the same pointer. At first, the MOV bombs an instruction,
the DJN decrements the A-field of the instruction before it, and the MOV bombs
the one before that. At first, the speed of this warrior may seem slow, but as
more and more processes are caught up in the MOV-DJN loop, the speed approaches
an upper limit of 50%c. Since the two primary instruction use the same pointer,
the speed at which they bomb/decrement the core is actually 100%c. This only
goes at this speed for 2/3 of the core before the ADD instruction changes the
DJN string to jump back 2 instead of 1. This causes the speed to drop back to an
upper limit of 33%c. Eventually the ADD instruction also changes the pointer of
the DJN instruction, causing it to start over and decrement every instruction
backwards while the MOV instruction bombs every instruction backwards. In
effect, this is called a self mutation. (Durham, Mark, et. al.) This warrior
actually does not commit suicide like some earlier warriors. It runs several
different passes at the core that all serve to hopefully kill the opponent.
After 725 generations, no major changes happened. The next warrior used a SUB
instruction instead of an ADD. It also never changed the pointers of the MOV and
DJN instructions.
After 900 generations, nothing had happened yet. The warriors hadn't evolved
much at all. I think this may be due to using low mutation rates in the final
rounds like 4 and 5.
After 1000 generations, there were still no new advancements even with a high
number of battles, 100. The final best warrior's functional lines were:
spl.b #-65, # 80
sub.f {-48, < 40
mov.i @ 54, < -8
djn.f $ -1, < -9
The only advantage of this warrior to the previous one is use of numbers in the
SUB fields that don't stop the backwards bomb/decrement coreclear until it has
almost bombed itself.
Population 'AC'
This, again, was the population run on the Pentium machine. I ran 1000
generations of 100 warriors. Using the MARS Tournament Scheduler program I
compared the populations at intervals of 80, 160, 300, 500, 625, 800, 900, 1000
generations. Again, these were more arbitrary numbers that allowed me to check
them before I went somewhere, went to sleep, etc. Instead of providing complete
reports, I have chosen to highlight some of the developments that took place
during each run.
After 80 generations, the best scoring warrior used two of the SPL instructions
described above consecutively. The next instruction was an original:
mov.i # 28, > 29
This amounted to moving the current instruction 29 instructions ahead. The next
time it is executed, it would move 30 instructions and so forth. If any other
warrior executed these, they would create more MOV instructions. The next
instruction was also a MOV, but it worked differently. It effectively was a
waste instruction. It did nothing but move itself onto itself. The next
instruction was an interesting variation on the JMP instruction. It was:
jmp.ba > 27, <-47
This, considering it comes 2 instructions after the MOV instruction, uses the
same pointer. Everywhere a MOV copies itself, the JMP does not jump to. It jumps
only to places the MOV isn't. Anytime another warrior is bombed by this, MOV are
copied over it. If it executes an instruction not bombed, so does this warrior.
Basically this leads to lots of ties. Thanks to this, this warrior won half the
time and tied the other half. Nothing in this generation could beat it very well
as they all consisted of basic SPL/MOV instructions that terminated on DATs.
This warrior was actually a pretty crude replicator. Eventually, it would bomb
itself with MOV instructions. These would then be split to by the JMP statement.
Eventually, this warrior was running all 800 possible processes, but they were
in one third of the core. Why didn't this replicator break the stone barrier?
Well, for one thing it was slow. Good replicators, or papers, rely on having
multiple copies running at completely different places in the core to win. This
one could still be defeated by even the slowest of coreclears.
This was actually, in hindsight, the closest this experiment came to producing a
replicator. Unfortunately, as the stones in the population evolved into faster
forms of themselves, this warrior did not. This replicator had a speed of about
20%c.
After 160 generations, the previous warrior disappeared from the scene as 33%c
SPL/MOV/DAT warriors dominated the killing grounds. They easily killed the slow
replicator/bomber that dominated the last population.
After 300 generations, the best warrior wasn't a three or two line warrior like
the rest. The functional portion was:
spl.x # 31, > 99
mul.ab $-84, >-11
mul.ab { 31, @ 11
spl.ba > 96, $ 81
mul.b * 35, * 50
spl.ab #-61, *-70
mov.i > 26, > 7
mod.x * 73, #-41
This warrior has two of the standard SPL instructions, three more or less
useless MUL instructions, a SPL instruction that could help it tie with other
warriors, one functioning MOV instruction, and a terminating MOD instruction. It
has a very low speed, but seems to do well because it can survive a DJN attack
or a couple DAT hits. The majority of the other warriors in this population seem
to be SPL/MOV/MOD or SPL/MOV/MOV/MOD. The warriors with two MOVs usually only
only have one that bombs with DATs and one that bombs with itself.
Whatever advantage the winning warrior in generation 300 had, it was lost after
generation 500, where the warrior below reigned supreme:
spl.f # 99, $-32
spl.ab # 75, @ 56
mov.i *-23, {-25
mov.i # 28, > 50
mod.f * 77, >-16
The twin SPL instructions provide extra durability, where the twin MOV
instructions give an overall speed of 18%c. The first MOV bombs DATs and the
second bombs itself. the MOD is another terminating instruction. If a process
survives the MOD, it is followed by a DIV which also has the possibility to
terminate. Apparently these warriors are evolving more durably than set 'AB'.
Set 'AB' evolved primarily low durability, fast bombers, where these are evolve
slow bombing, but highly durable warriors.
After 625 generations, the following warrior dominated:
spl.a #-66, <-39
mov.i $100, { -2
mov.i # -9, > 98
mod.ba *-71, > 83
This warrior shed the extra SPL instruction in favor of speed against
durability. It still contains the MOV instruction that only moves itself. I
really wonder why it doesn't evolve a second MOV that functions like the first.
The MOD instruction is the typical terminator for this test set.
After 800 generations, the following warrior dominated:
spl.f #-68, <-48
mov.i <-62, > 4
mov.i <-49, > 3
dat.x < 46, $ 52
This warrior functions with a speed of 50%c, bombing primarily with DATs or
whatever lies at the pointer. The DAT instruction insures termination where the
MOD did not. The twin MOV instructions both use the same pointer, so the core is
bombed forward at twice the rate the MOVs would if the used separate pointers.
In other words, instead of one line of DAT bombs following the other, they are
placed one after another, halving the time it takes to bomb over the opponent.
After 900 generations, the warrior changed a little:
spl.f #-68, <-74
mov.i <-62, > 4
mov.i @-25, > 3
dat.b * 23, > -3
The only major change to this warrior is the fact that the second MOV
instruction uses the same DAT location to bomb with unless interfered with,
unlike the previous version which changed upon execution.
After a final run to 1000 generations, the following warrior came out on top:
spl.x # 61, <-75
mov.i $-65, > 4
mov.i > 57, > 3
jmz.a $ -2, @ 94
This warrior bombs more or less like the last one, except it bombs with a speed
approaching an upper limit of 66%c. This is due to the JMZ instruction which
causes the processes to jump back to the first MOV instruction. As more and more
processes accumulate here, the speed improves. Eventually, if a nonzero
instruction is bombed to a position 94 lines after the JMZ line, the processes
will run whatever comes after this instruction. In most of these types of
warriors, this was another terminating instruction which kept the speed below a
new upper limit of 50%c.
Comparison of the Populations
The final warriors from each generation were compared. The 'AB' warriors
consistently beat the 'AC' warriors. This shows a higher level of sophistication
in the SPL/SUB/MOV/DJN warriors than in the SPL/MOV/MOV/JMZ. In general, the
warriors in the AB population went for speed rather than durability whereas the
warriors in the AC population went for durability rather than speed.
A lot of early evolved corewarriors never developed beyond an imp or a single
DJN instruction. An imp is:
mov.i $0, $1
This warrior copies itself one line ahead, and then goes to the next line. If
you can find it, it is easy to kill. It also has no offensive capability, it can
only tie. A single DJN instruction warrior would look like this:
djn.f $ 0, <-1
This kind of warrior only decrements instruction, which some warriors can
survive, whereas this warrior is suicidal after 800 cycles. Another kind of
evolutionary dead-end I have heard of is a hider like:
jmp.x $0, $0
This little warrior just sits there. His offensive and defensive capability is
zero. Now, my warriors never reached these evolutionary dead-ends. The evolved,
in my opinion, much farther.
However, it should also be noted, all of the warriors developed were stone or
rock types. None of them scanned for opponents, and none replicated themselves.
I haven't heard of any evolved corewarrior breaking this barrier. I think if it
does happen, it will be a paper that evolves. This would be due to the fact that
papers beat stones, so they would win and reproduce more, whereas if a scissor
evolved in a stone population, it would be killed off.
When I did a test consisting of the five best final warriors from each
population, two papers, two scissors, and four stones, the results showed how
these evolved warriors were still not competitive with the best human written
warriors. The papers came in first and second, then a scissors, stone, another
scissor, the three other stones, and then the evolved warriors. However, it
should be noted that one of the stones, which was written with the latest
strategies in mind, barely beat the best of the evolved warriors. Many thanks go
to those that gave me the code for their human written warriors including Philip
Kendall, Brian Haskin, David Matthew Moore, and Franz.
I also submitted some of the best warriors to the baby hill. None of them were
good enough to make it. However, comparing tests of earlier warriors against the
best of 1000 generations warriors showed an improvement of almost doubling the
score. Of course, the bottom warrior on the hill also had twice the scores of
the best evolve warrior I submitted.
Conclusion
The big question here is did these warriors learn. The answer is yes. These
warriors made serious improvements toward learning to survive. Some of the
instructions they used, like using a DJN instruction to loop instead of a simple
JMP were and are still used by good corewarriors today. Also, some warriors had
two sections of executing code, which dramatically improve chances of not being
killed. In my opinion, these corewarriors learned quite a bit in only 1000
generations. They were constantly improving upon themselves, up until the last
couple hundred generations. I believe this was partly my fault, as I kept the
mutation rates lower for the final rounds. I think, to a point, a higher
mutation rate helps more than it hurts.
Even though the results may not be great, I believe they are a great accomplish
for these evolved corewarriors. I don't know how many generations it would take
for them to be competitive with warriors written by humans. However, I do
believe by what they've done in 1000 generations proves that it may be possible.
These warriors went from nothing but a single random instruction to rather
complicated warriors that used every advantage they could to win.
Future Work
I believe there are two things keeping evolved corewarriors from being
competitive with human-written warriors. The first is the lack of a diversity
scoring principle like the Rank-Space method. (Winston, Patrick Henry, 518) With
this help for rewarding diversity, I believe the stone barrier could be broken.
Also, I believe the second thing necessary is processor time. In his experiments
with satellite-control programs, Brian Howley used a fast workstation that took
83 hours of straight computation to evolve a program comparable to human-written
programs. (Gibbs, W. Wayt, 2) If someone could either gain use of or use a
workstation or a supercomputer, I believe this would also help the results. It
would also take some major rewriting of ga_war.c to test for diversity as well
as fitness in a population, which would also slow down the computations. But,
then again, faster processors and hard drives could be the answer.
Appendix A
The following is a brief description of the corewars code set. It was put
together from pieces of two textfiles, which, I am sorry, I do not know where
they came from. This is meant to be used as a very general guide to
understanding the concepts in this paper.
Opcodes:
DAT terminate process
MOV move from A to B
ADD add A to B, store result in B
SUB subtract A from B, store result in B
MUL multiply A by B, store result in B
DIV divide B by A, store result in B if A <> 0, else terminate
MOD divide B by A, store remainder in B if A <> 0, else terminate
JMP transfer execution to A
JMZ transfer execution to A if B is zero
JMN transfer execution to A if B is non-zero
DJN decrement B, if B is non-zero, transfer execution to A
SPL split off process to A
SLT skip next instruction if A is less than B
CMP same as SEQ
SEQ Skip next instruction if A is equal to B
SNE Skip next instruction if A is not equal to B
NOP No operation
LDP (*) Load P-space cell A into core address B
STP (*) Store A-number into P-space cell B
(*) LDP and STP are instructions used in accessing a new feature of corewars, P
-space. Because this paper focuses on warriors for the baby core with a limit of
20 instructions, these instructions were not used in any warriors. Basically,
the baby warriors cannot fit proper P-space routines into 20 instructions. The
instructions are primarily meant for normal size cores with limits of 100
instructions.
Modifiers:
.A Instructions read and write A-fields.
.B Instructions read and write B-fields.
.AB Instructions read the A-field of the A-instruction and the B
-field of the B-instruction and write to B-fields.
.BA Instructions read the B-field of the A-instruction and the A
-field of the B-instruction and write to A-fields.
.F Instructions read both A- and B-fields of the the A- and B
-instruction and write to both A- and B-fields (A to A and B to
B).
.X Instructions read both A- and B-fields of the the A- and B
-instruction and write to both A- and B-fields exchanging
fields (A to B and B to A).
.I Instructions read and write entire instructions.
Addressing modes:
# immediate
$ direct
@ indirect using B-field
< predecrement indirect using B-field
> postincrement indirect using B-field
* indirect using A-field
{ predecrement indirect using A-field
} postincrement indirect using A-field
The complete proper form for a single instruction in corewars is:
(Opcode)(Modifier) (Addressing Mode)Number, (Addressing Mode)Number
| | | |
\_____________________/ \___________________ /
A-field B-field
Reference List
Boer, Jason. (1997). ga_war.c.
http://www.avalon.net/~jboer/projects/corewar/ga_war.c.
An excellent piece of C code to evolve corewarriors. The help introduction
section is very helpful and the code is very readable.
Boer, Jason. (1997). Jason's Corewar Project Page.
http://www.avalon.net/~jboer/projects/corewar/corewar.html.
A very nice homepage devoted to evolving corewarriors using his own code. Lots
of warriors that he has evolved, as well as their results. Also include ideas on
the best settings to use to develop better warriors.
Dawkins, Richard. (1989). The Selfish Gene. (2nd. ed.). Oxford: Oxford
University Press.
An excellent book which details many functions of genetics from the gene-level
instead of the individual. Good reading and brings up several ways evolution
could work to produce diversity among other things.
Dewdney, A. K. (1988). The Armchair Universe: An Exploration of Computer Worlds.
New York: H. Freeman.
This is where corewars began. This publication is actually Dewdney's first
articles put together from their original publication in Scientific American.
Dewdney proposes the rules, instructions, and gives the most readable
introduction to corewars even though many things have changed.
Durham, Mark., et. al. (1998). Core War Frequently Asked Questions
(rec.games.corewar). 1-22. http://www.mcs.vuw.ac.nz/~amarsden/corewars/corewar
-faq.html.
An excellent help guide for anyone interested in designing, testing, and putting
their warriors against others. Also provides many links to warrior and
newsletter archives and personal homepages of many current people in the
corewars field.
Genetic-Programming.org Home Page. (1998). http://www.genetic-programming.org/.
This contains the topics, speakers, and location of the next Genetic Programming
Conference. Also has links to where to submit your papers for this conference,
and links to some of the more prominent members of the genetic programming
field.
Gibbs, W. Wayts. (1996). Programming with Primordial Ooze. Scientific American.
1-3. http://www.sciam.com/1096issue/1096techbus3.html.
A summary of what has been done in recent years with genetic algorithms.
Contains examples of ways programs made by genetic algorithms have proved
themselves equal to or better than human written programs.
Goldberg, David E. (1989). Genetic Algorithms in Search, Optimization, and
Machine Learning. Amsterdam: Addison-Wesley.
The official guide to using genetic algorithms. Contains dozens of samples and
code to do your own tests with. Also written as a textbook, so it contains very
clear explanations of everything.
Koza, John. (1998). John Koza's Home Page. http://www-cs
-faculty.stanford.edu/~koza/index.html#anchor5393849.
An excellent list of resources, contacts, current and future projects from the
forerunner in the genetic programming field. This homepage contains hundreds if
not thousands of links all related to Genetic Programming and what is being done
with it.
Langdon, W. B. (1997). Genetic Programming Bibliography.
http://www.cs.bham.ac.uk/~wbl/biblio/gp-bibliography.html.
A comprehensive list of papers that have been done using genetic programming. If
it's been done, I'm pretty sure it is here. They claim over 800 resources, and I
believe it.
Perry, John. (198?). Core Wars Genetics: The Evolution of Predation. 1-12.
http://www.koth.org/evolving_warriors.html.
The first paper done using genetic algorithms to evolve corewarriors. Although
it was done awhile ago, and many things have changed, it is still an excellent
introduction to evolving corewarriors, although not very good at explaining
corewars.
Peters, James A. (1959). Classic Papers in Genetics. Englewood Cliffs, N. J.:
Prentice-Hall.
A collection of papers that form the basis for genetics today. Contains
everything from basics like Gregor Mendel's Plant Hybridization to the genetic
results of atomic bombs of Hiroshima and Nagasaki.
Winston, Patrick Henry. (1993). Artificial Intelligence. (3rd. ed.). Milan:
Addison-Wesley. 505-528.
An excellent textbook containing a complete overview of the Artificial
Intelligence field. Contains lots of examples and problems. Brief section of
Learning by Simulating Evolution which is a very basic introduction to genetic
algorithms.
Bibliography
Boer, Jason. (1997). ga_war.c.
http://www.avalon.net/~jboer/projects/corewar/ga_war.c.
An excellent piece of C code to evolve corewarriors. The help introduction
section is very helpful and the code is very readable.
Boer, Jason. (1997). Jason's Corewar Project Page.
http://www.avalon.net/~jboer/projects/corewar/corewar.html.
A very nice homepage devoted to evolving corewarriors using his own code. Lots
of warriors that he has evolved, as well as their results. Also include ideas on
the best settings to use to develop better warriors.
Dawkins, Richard. (1989). The Selfish Gene. (2nd. ed.). Oxford: Oxford
University Press.
An excellent book which details many functions of genetics from the gene-level
instead of the individual. Good reading and brings up several ways evolution
could work to produce diversity among other things.
Dewdney, A. K. (1988). The Armchair Universe: An Exploration of Computer Worlds.
New York: H. Freeman.
This is where corewars began. This publication is actually Dewdney's first
articles put together from their original publication in Scientific American.
Dewdney proposes the rules, instructions, and gives the most readable
introduction to corewars even though many things have changed.
Dewdney, A. K. (1990). The Magic Machine: A Handbook of Computer Sorcery. New
York: H. Freeman.
More updates and new products of corewars appear in this compilation. Also
details on the first International Core Wars Society tournament and the better
scoring warriors.
Durham, Mark., et. al. (1998). Core War Frequently Asked Questions
(rec.games.corewar). 1-22. http://www.mcs.vuw.ac.nz/~amarsden/corewars/corewar
-faq.html.
An excellent help guide for anyone interested in designing, testing, and putting
their warriors against others. Also provides many links to warrior and
newsletter archives and personal homepages of many current people in the
corewars field.
Genetic-Programming.org Home Page. (1998). http://www.genetic-programming.org/.
This contains the topics, speakers, and location of the next Genetic Programming
Conference. Also has links to where to submit your papers for this conference,
and links to some of the more prominent members of the genetic programming
field.
Gibbs, W. Wayts. (1996). Programming with Primordial Ooze. Scientific American.
1-3. http://www.sciam.com/1096issue/1096techbus3.html.
A summary of what has been done in recent years with genetic algorithms.
Contains examples of ways programs made by genetic algorithms have proved
themselves equal to or better than human written programs.
Goldberg, David E. (1989). Genetic Algorithms in Search, Optimization, and
Machine Learning. Amsterdam: Addison-Wesley.
The official guide to using genetic algorithms. Contains dozens of samples and
code to do your own tests with. Also written as a textbook, so it contains very
clear explanations of everything.
Koza, John. (1998). John Koza's Home Page. http://www-cs
-faculty.stanford.edu/~koza/index.html#anchor5393849.
An excellent list of resources, contacts, current and future projects from the
forerunner in the genetic programming field. This homepage contains hundreds if
not thousands of links all related to Genetic Programming and what is being done
with it.
Langdon, W. B. (1997). Genetic Programming Bibliography.
http://www.cs.bham.ac.uk/~wbl/biblio/gp-bibliography.html.
A comprehensive list of papers that have been done using genetic programming. If
it's been done, I'm pretty sure it is here. They claim over 800 resources, and I
believe it.
Perry, John. (198?). Core Wars Genetics: The Evolution of Predation. 1-12.
http://www.koth.org/evolving_warriors.html.
The first paper done using genetic algorithms to evolve corewarriors. Although
it was done awhile ago, and many things have changed, it is still an excellent
introduction to evolving corewarriors, although not very good at explaining
corewars.
Peters, James A. (1959). Classic Papers in Genetics. Englewood Cliffs, N. J.:
Prentice-Hall.
A collection of papers that form the basis for genetics today. Contains
everything from basics like Gregor Mendel's Plant Hybridization to the genetic
results of atomic bombs of Hiroshima and Nagasaki.
Winston, Patrick Henry. (1993). Artificial Intelligence. (3rd. ed.). Milan:
Addison-Wesley. 505-528.
An excellent textbook containing a complete overview of the Artificial
Intelligence field. Contains lots of examples and problems. Brief section of
Learning by Simulating Evolution which is a very basic introduction to genetic
algorithms.