a Little Corewar Evolver

LCE8 is a small warrior evolver written in QBasic with the following features...

It does not have crossover or starting location mutation, and doesn't have a user interface other than editing an INI, printing battle results and exiting if a key is pressed. LCE8 warriors are very simple, just an assert line followed by redcode. The INI file format is crude, raw data values are placed on specific lines.

Here's the "LCE8qb.bas" file...

n=256:l=20:ra=0:spl=0:cs=8000:pmars$="pmars -s 8000 -p 8000 -c 80000 -l 100 -d 100 -b -r 20"
RANDOMIZE TIMER:m$=".i .i .i .a .a .b .b .x .f .ab.ba":em=11:d$="$ $ $ # @ * < > { }":ed=10
i$ = "mov mov mov mov mov mov spl spl spl spl spl djn djn dat add sub mul div mod sne seq slt jmn jmz jmp":ei=25
inl=.005:del=.007:ins=.01:inm=.03:adr=.03:chd=.05:idd=.02:dup=.1:sn=.5:ON ERROR GOTO a:OPEN "LCE8.ini" FOR INPUT AS #1
ON ERROR GOTO x:INPUT #1,n,l,ra,spl,cs,pmars$,i$,ei,m$,em,d$,ed,inl,del,ins,inm,adr,chd,idd,dup,sn:CLOSE:z8=-(m$="88")
m$=LEFT$(" ",z8*3)+m$:em=em*(1-z8)+z:IF n>5000 OR l>500 OR LCASE$(LEFT$(pmars$,2))<>"pm" THEN GOTO x ELSE GOTO b
prcl:IF k<l THEN PRINT #2,a$:k=k+1 'setup.. n=# files,l=length,ra=0 grid >0 range,spl=0 rnd 1 hint,cs=coresize for #
mrcl:a$=MID$(i$,INT(RND(1)*ei)*4+1,3)+MID$(m$,INT(RND(1)*em)*3+1,3)+" "+MID$(d$,INT(RND(1)*ed)*2+1,1):x=-(RND(1)<sn)
a$=a$+RIGHT$(" "+STR$(x*INT(RND(1)*l*2-l)+(1-x)*INT(RND(1)*cs)),5)+","+MID$(d$,INT(RND(1)*ed)*2+1,1):x=-(RND(1)<sn)
a$=a$+RIGHT$(" "+STR$(x*INT(RND(1)*l*2-l)+(1-x)*INT(RND(1)*cs)),5):GOSUB ck88:IF bad88 THEN GOTO mrcl ELSE RETURN
ck88:IF z8=0 THEN bad88=0:RETURN ELSE p$=LEFT$(a$,2):q$=MID$(a$,8,1):r$=MID$(a$,15,1):ta=(p$="jm")+(p$="dj")+(p$="sp")
bad88=ta*(q$="#")+(ta=0)*(r$="#")+((q$="$")+(q$="@")+(r$="$")+(r$="@"))*(p$="da")+(r$="#")*((p$="cm")+(p$="sl")):RETURN
a:RESUME b 'uncomment RESUME for QBasic, comment for FreeBasic (to compile for Linux requires library with fixed SHELL)
b:ON ERROR GOTO c:j=(SIN(TIMER)+2)*100:FOR k=1 TO j:z=RND(1):NEXT k:g=INT(SQR(n)):OPEN "1.red" FOR INPUT AS #1:CLOSE:GOTO m
c:FOR k=1 TO n:OPEN LTRIM$(STR$(k))+".red" FOR OUTPUT AS #1:? #1,";assert 1":IF spl THEN ? #1,"spl $ 0,$ 0"
FOR j=1-(spl<>0) TO INT(RND(1)*l+1):GOSUB mrcl:? #1,a$:NEXT j:CLOSE:NEXT k:RESUME m 'uncomment for QB, comment for FB
m:ON ERROR GOTO x:w1=INT(RND(1)*n)+1:IF ra>0 THEN w2=(w1+(1+INT(RND(1)*ra))*((RND(1)<.5)*2+1)) MOD n:IF w2<1 THEN w2=w2+n
IF ra<=0 THEN w2=w1:WHILE w2=w1:w2=w1+INT(RND(1)*3-1)+(INT(RND(1)*3-1)*g):WEND:w2=w2 MOD n:IF w2<1 THEN w2=w2+n
? STR$(w1);" vs";STR$(w2),:SHELL pmars$+STR$(w1)+".red"+STR$(w2)+".red >sc.txt":OPEN "sc.txt" FOR INPUT AS #1
INPUT #1,a$:z=INSTR(a$, "scores "):s1=VAL(MID$(a$,z+7)):INPUT #1,a$:z=INSTR(a$, "scores "):s2=VAL(MID$(a$,z+7)):CLOSE
? STR$(s1);STR$(s2);" ",:IF s1>s2 THEN s=w1:d=w2:? STR$(s);" -->";STR$(d) ELSE s=w2:d=w1:? STR$(s);" -->";STR$(d)
OPEN LTRIM$(STR$(s))+".red" FOR INPUT AS #1:OPEN LTRIM$(STR$(d))+".red" FOR OUTPUT AS #2:INPUT #1,a$:PRINT #2,a$:k=0
WHILE NOT EOF(1):LINE INPUT #1,a$:w$=a$:x1=-(RND(1)<.5):x2=-(RND(1)<.5) ' .---------------------------------------------.
IF RND(1)<del THEN IF NOT EOF(1) THEN LINE INPUT #1,a$:w$=a$ '|Config'd-|' | ======= Little Corewar Evolver v0.8 ======= |
IF RND(1)<inl THEN z$=a$:GOSUB prcl:IF RND(1)<dup THEN a$=z$ '|--for----|' | Requires pmars. 6/10/09 WTN (public domain) |
IF RND(1)<ins THEN a$=MID$(i$,INT(RND(1)*ei)*4+1,3)+MID$(a$,4)'|-QBasic--|' | Grid or ring topology, many mutate options |
IF RND(1)<inm THEN a$=LEFT$(a$,3)+MID$(m$,INT(RND(1)*em)*3+1,3)+MID$(a$,7)' | Now does true 88. See LCE8.ini for settings |
IF RND(1)<adr THEN a$=LEFT$(a$,7)+MID$(d$,INT(RND(1)*ed)*2+1,1)+MID$(a$,9)' `---------------------------------------------'
IF RND(1)<chd THEN x=-(RND(1)<sn):a$=LEFT$(a$,8)+RIGHT$(" "+STR$(x*INT(RND(1)*l*2-l)+(1-x)*INT(RND(1)*cs)),5)+MID$(a$,14)
IF RND(1)<idd THEN a$=LEFT$(a$,8)+RIGHT$(" "+STR$(x1*(VAL(MID$(a$,9,5))+1)+(1-x1)*(VAL(MID$(a$,9,5))-1)),5)+MID$(a$,14)
IF RND(1)<adr THEN a$=LEFT$(a$,14)+MID$(d$,INT(RND(1)*ed)*2+1,1)+MID$(a$,16) '--> Supplied as-is and without warranty <---.
IF RND(1)<chd THEN x=-(RND(1)<sn):a$=LEFT$(a$,15)+RIGHT$(" "+STR$(x*INT(RND(1)*l*2-l)+(1-x)*INT(RND(1)*cs)),5)'|run on a|
IF RND(1)<idd THEN a$=LEFT$(a$,15)+RIGHT$(" "+STR$(x2*(VAL(MID$(a$,16,5))+1)+(1-x2)*(VAL(MID$(a$,16,5))-1)),5)'|ram disk|
IF z8 THEN GOSUB ck88:IF bad88 THEN a$=w$ 'other variables... m$ d$ i$ defines code, em ed ei = # entries `--------'
w:GOSUB prcl:WEND:CLOSE:IF INKEY$<>"q" GOTO m 'mutation... inl=insert,del=delete,ins=instruction,inm=modifier,adr=mode,
x:CLOSE:SYSTEM 'chd=random data,idd=inc/dec data,dup=dup line if insert,sn=small # ratio

Here's the "LCE8.ini" file...

256
20
0
0
8000
pmars -s 8000 -p 8000 -c 80000 -l 100 -d 100 -b -r 20
mov mov mov mov mov mov spl spl spl spl spl djn djn dat add sub mul div mod sne seq slt jmn jmz jmp
25
.i .i .i .a .a .b .b .x .f .ab.ba
11
$ $ $ # @ * < > { }
10
.005
.007
.01
.03
.03
.05
.02
.1
.5

Little Corewar Evolver v0.8 INI file...

Don't change formatting of the entries, INI read is very very simple.
Most parms are not validated, if wrong it'll create bad code or crash.

Explanation of INI file entries... (nominal values in parenthesis)
Line 1 - soup size (256) must be > range and <= 5000
Line 2 - maximum warrior length (20) must be <= 500 and <= max length in pmars
Line 3 - battle selection range (0) how far apart in soup, < soup size
If range is 0 then uses grid topology, otherwise uses ring topology
Line 4 - enable start spl (0) if 0 pure random if 1 start with spl $0,$0
Line 5 - core size for big numbers (8000)
Line 6 - pmars command line, default is set for coresize 8000...
pmars -s 8000 -p 8000 -c 80000 -l 100 -d 100 -b -r 20
First two characters of the command on line 6 must be "pm" (any case)
Line 7 - weighted list of base instructions, default...
mov mov mov mov mov mov spl spl spl spl spl djn djn dat add sub mul div mod sne seq slt jmn jmz jmp
Line 8 - number of items in instruction list (25)
Line 9 - list of instruction modifiers (.i .i .i .a .a .b .b .x .f .ab.ba)
Last modifier must be 3-char field or specify an extra character, as in .i .a .b .f .
For '88 style instructions specify 88 on line 9 (line 10 ignored, assumed 1 of " ")
Note - for true-88 limit to "$ # @ <" and "mov spl djn jmp jmn jmz cmp slt add sub dat"
Line 10 - number of items in modifier list (11)
Line 11 - list of addressing modes ($ $ $ # @ * < > { })
Line 12 - number of items in mode list (10)
Line 13 - chance of inserting a new line (.005)
Line 14 - chance of deleting a line (.007)
Line 15 - chance of changing a base instruction (.01)
Line 16 - chance of changing an instruction modifier (.03)
Line 17 - chance of changing an address mode (.03)
Line 18 - chance of randomly changing a data field (.05)
Line 19 - chance of incrementing or decrementing a data field (.02)
Line 20 - if line inserted, chance of duplicate of previous line (.1)
Line 21 - if data is changed randomly, chance of a +/- small number (.5)

(end of LCE8.ini file)

Here's the "LCE8.txt" file with usage notes...

Notes for the Little Corewar Evolver version 0.8...

In this text, line number notes refer to lines in the LCE8.ini file.

The LCE8 program requires a server version of pmars to run the battles, for
Windows the SDL pmars-server.exe should work, rename to pmars.exe or edit
line 6 and change pmars to pmars-server. For Linux the pmars binary should
be put in a path directory such as /usr/local/bin. For safety reasons the
first two letters of the pmars command line must be "pm" (any case), if not
on path for dos/win must be in current, for Linux in "pm" dir from current.

LCE8 is written in "intense" BASIC and can be run under QBasic, or with
minor changes (comment the RESUME statements) compiled using FreeBasic for
Windows or Linux. Note.. compiling for Linux requires fixed SHELL command,
modified fblib.a file at: http://newton.freehostia.com/fb020shellmod.tar.gz
Note.. writes to numerous files in the current directory while evolving,
to avoid wear on the system disk run from a ram disk or external hard drive.
Note.. uses 100% available CPU cycles while running, don't run on a fast
system with inadequate cooling. To test in QBasic run: a:IF INKEY$="" GOTO a

To run in QBasic from a dos prompt: qbasic /run LCE8qb (or whatever name is)
This requires qbasic.exe, commonly found on the 'net in the qbasic11.zip file.

To compile with FreeBasic edit the source to add ' in front of each of the two
RESUME statements and at a command prompt: fbc -e -lang qb LCE8.bas
add a path in front of fbc if fbc or link to it is not in a path directory,
for Windows: "C:\Program files\FreeBasic\fbc" -e -lang qb LCE8.bas

Notes for Linux.. make sure fblib.a supports infinite shelling (see above).
Don't double-click the resulting LCE8 binary! typically that'll create a bunch
of redcode in your home dir and evolve with no text output and no easy way to
stop besides killing the task - not desirable.. instead like any command-line
program run from a terminal or a script that launches in xterm, konsole, etc,
for example save the following as say "Run_LCE8.sh" (and make executable):

#!/bin/bash
cd `dirname $0`
xterm -e ./LCE8

Windows should automatically change to the current directory and run in a
command shell so a Windows-compiled binary should be runable as-is.
If using the QBasic version, to avoid having to open a dos prompt
can be run from say a "Run_LCE8qb.bat" batch file:

@echo off
mode con lines=25
qbasic /run LCE8qb

When LCE8 is started it reads parameters from LCE8.ini if it exists (otherwise
uses built-in defaults), if 1.red doesn't exist creates a new random soup, and
starts evolving. Press q to quit. Can be stopped and restarted to continue a
run. Once a run has started the soup size cannot be increased or any warriors
past 1.red deleted or an error will occur. To restart a run delete 1.red file
or if reducing the soup size, delete all of the .red files.

Adjusting the INI settings...

Chances are from 0 (never change) to 1 (always change), for example if
instruction chance is .01 there is a 1% chance of changing. Chances are
applied evenly to all items so if instruction chance is .01 and there are
20 instructions in the warrior there is a 20% chance at least one instruction
will be changed. Typically mutation rates are fairly low to avoid breaking
code too much, the idea is to make small tweaks and keep the better tweaks.

Range (line 3) sets soup topology, if 0 a square grid is used with imperfect
wraparound on all sides. If >0 then a ring topology is used where range sets
the maximum soup distance between battle partners (from 1 to range).

The number of battle rounds (the number after -r in the pmars command line)
affects strenth - higher numbers tend to produce stronger code. However the
increased accuracy may prevent papers from forming so usually I set for lower
rounds (-r 20) at first then increase (-r 50 to -r 100) once interesting code
appears in the soup. If nothing interesting appears after a reasonable time,
start over - what arises heavily depends on what is created in the beginning.
A larger soup size increases the chances of a good start but takes longer to
evolve into stronger warriors. For coresize 8000 don't expect great strength,
a mutation-only evolver like this tops out around 80 or 90 Wilkies.

To generate coresize 800 warriors change line 5 to 800 and line 6 to...
pmars -s 800 -p 800 -c 8000 -l 20 -d 20 -b -r 50
To generate coresize 80 warriors change line 5 to 80 and line 6 to...
pmars -s 80 -p 80 -c 800 -l 5 -d 5 -b -r 50

Better coresize 80 results might be obtained if instructions limited
to only SPL MOV and DJN, perhaps try changing lines 7 and 8 to...
spl spl mov mov djn
5

Settings that made true-88 papers in coresize 8000...
(next line must be line 1 - modify settings as needed)
100
20
2
0
8000
pmars -s 8000 -p 8000 -c 80000 -l 100 -d 100 -b -r 20 -8
mov mov mov mov spl spl spl djn jmp jmn jmz cmp slt add sub dat
16
88
0
$ $ $ # @ <
6
(remaining lines the same - can make line 16 = 0 but doesn't matter)

Mutation settings are a matter of evolving art, in general smaller coresizes
require somewhat higher mutation settings, lower settings find stronger code
with larger soups but it takes longer and produces more duplicates. Usually
instruction mutation rate is lowest followed my modifier and address modes,
with data rates the highest. Experiment to find best rate for inc/dec and
ratios of dup lines and small numbers.

More notes are at: http://newton.freehostia.com/cw/LCE.html

(end of LCE8 notes file)

LCE8 is the latest iteration of a series of small evolvers written to explore various techniques without the distraction and bloat of a big evolver, where changing one little thing potentially affects code on multiple pages. Originally the goal was (and still is) to see how small a functional corewar evolver could be, but once it began functioning rather well it became a serious tool for trying out new ideas. Although the code is highly compacted, I learned to program on the ZX81, Coco, C64 and 8-bit Atari (and a Wang mainframe) and back then this sort of code was the norm, not the exception, doesn't bother me and it's rather fun reducing complex IF/THEN logic to linear mathematical expressions - in BASIC when computed (a < b) or (c$ = "string") returns -1 if true or 0 if false. When applied to a relatively small program such as this one, I found it easier to write and debug code when the entire program can be seen at once without paging back and forth to check variable names, side effects etc.

If you actually want to run LCE8, it should be compatible with most Windows and x86 Linux systems but heed the notes about running from a ram disk and using 100% CPU. This is a fairly file-intensive program that runs fine on a proper setup but can cause issues for some consumer-grade "high-performance" PC's and laptops that aren't actually capable of sustaining the advertised speed (shocking but quite common these days). Setting up a ram disk is quite easy for WinXP and Linux - for the former look for "A R ram disk", for the latter the ability is built-in, just have to write scripts to enable it - see the RedMixer docs for solutions I've use.

I had trouble copying text from this page using Internet Explorer so here's a zip version of LCE containing the above files, plus the FreeBasic version of the source and a readme.txt containing the psuedocode. To compile for Linux requires a patched libfb.a library, and requires a Linux version of pMARS such as the one of the binaries included in this archive. LCE can be run interpreted in QBasic under versions of Windows that support dos such as XP and 95/98, or compiled using the Windows version of FreeBasic (it's not necessary to install all the extra libraries to compile plain console programs like this). I use the SDL version of pMARS on my XP systems.

A bit of history... the first 3 versions were variations of a concept where a number of warriors fought at once - that concept didn't work so well. Version 4 implemented a "fuzzy ring" topology where battling warriors were selected to be more or less near each other in the soup, wrapping on the ends to form a ring, and also had an INI file for changing the settings without editing code. Version 5 added sanity-checking to the INI, fixed bugs, and added an option to generate instructions without modifiers (88-style). Version 6 added a mutation option to randomly increment or decrement existing values, as a separate mutation chance rather than a proportion as in some of my other evolving programs. Version 7 adds support for battling neighboring warriors on a grid, or using the fuzzy ring topology. Version 8 adds code for 88-standard rules provided the instruction and mode strings are correctly specified. That was tricky! Added 4 lines to the program, and over 25 lines to the psuedocode listing.

LCE8 is a pure evolver - it creates code from random beginnings with nothing to guide the process except for the warriors in the soup battling each other and replacing the losers with mutated copies of the winners. The closest it comes to incorporating hand-specified code is an optional hint mode that starts the initial warriors with an SPL instruction. It supports string weighting by specifying duplicates, which can influence the type of code produced, but order is not specified, and combinations are specified only as needed to produce 88-compatible code when set for that. There is an option to insert duplicate lines but it doesn't say what or when, the dup chance helps build SPL and MOV blocks for larger warriors and is often found in other evolvers. LCE8 isn't quite as strong as RedMixer but it's roughly on par with REBS, Fizzle, RedMaker, and readily generates papers for coresize 8000, even using '88 rules. They're not that strong but it's all relative, coresize 8000 is quite difficult to evolve for without invoking "special" methods. My evolvers are more focused on discovering and applying GA methods for evolving warriors for corewar, absolute strength is nice but isn't the primary goal. For more competitive results from this evolver set it for a smaller coresize (such as nano) but often warriors that do well on the SAL Nano hill are "mad bombers", exciting when they place near the top of the hill but kind of boring to study. If expecting warriors that can compete with hand-coded warriors for coresize 8000 then look elsewhere - this one would be doing well to exceed 100 Wilkies.

How the LCE8 program works

Like most of my evolvers, the basic idea is...

    Pick two warriors and battle them, collecting the scores
    Copy the winner over the loser while making random changes
    Keep doing that until stopped

This one is a bit different in that it creates a soup of random warriors first (if it doesn't already exist) then enters the evolving loop, rather than creating a random warrior for any selected warrior that doesn't exist. The nature of "pick two warriors" determines the topology of the soup, and the nature of the "random changes" determines the overall effectiveness of the evolution process. In this evolver, a tie is considered the same as a loss, only clear winners are replicated.

Here's a psuedo-code representation of the LCE8 program, with most of the nitty-gritty details...

  Set default parameters...
SoupSize - how many warriors in the soup
MaxLen - maximum evolved size of a warrior
Range - maximum soup distance for battle pairs
Hint - true to begin initial warriors with SPL, false for all random
Coresize - size of core for choosing big numbers
pmars command line - passed to the OS to run warrior battles
instruction string and count - base instructions
modifier string and count - instruction modifiers
mode string and count - address mode symbols
insert chance - chance of inserting a random or duplicate line
delete chance - chance of deleting a line
instruction chance - chance of changing the instruction
nodifier chance - chance of changing the modifier
address chance - chance of changing an A or B address mode
data chance - chance of randomly changing a A or B value
incdec chance - chance of incrementing or decrementing an A or B value
dup line chance - if line insert, chance of being a duplicate line
small number chance - if random data, chance of choosing a small offset
Randomize the random number generator using the system timer as a seed
If the INI file exists Then
Read replacement parameters from the INI file
If modifier string = "88" Then
modifier string = " "
modifier count = 1
88mode = true
If SoupSize>5000 Or MaxLen>500 Or Left(pmars,2) <> "pm" Then exit program
gridoffset = INT(SquareRoot(SoupSize))
temploops = INT((Sine(system timer) + 2) * 100)
Call Rnd temploops times to further randomize the random number generator
If warrior 1.red doesn't exist create a random soup...
For warrior = 1 to SoupSize
Open Str(warrior)+".red" for output
Write ";assert 1" to output file
If hint Then write "spl $ 0,$ 0" to output file
Number of lines = INT(Rnd * MaxLen + 1)
If hint Then Start line = 2 Else Start line = 1
For line = Start line to Number of lines (only if more lines to add)
Call Random line sub
Write redcode line to output file
Next line
Close file
Next warrior
Loop
warrior1number = INT(Rnd * SoupSize + 1)
If Range > 0 Then (ring topology)
warrior2offset = INT(Rnd * Range + 1)
If Rnd < .5 Then
warrior2number = warrior1number + warrior2offset
Else
warrior2number = warrior1number - warrior2offset
Else (select by grid topology)
Loop
warrior2number = warrior1number + INT(Rnd * 3 - 1)
warrior2number = warrior2number + INT(Rnd * 3 - 1) * gridoffset
Keep looping until warrior2number <> warrior1number
warrior2number = warrior2number MOD SoupSize
If warrior2number < 1 Then warrior2number = warrior2number + SoupSize
Print warrior1number " vs " warrior2number (tab) (no crlf)
warrior1 = Str(warrior1number)+".red"
warrior2 = Str(warrior2number)+".red"
Battle warrior1 and warrior2 in pmars writing to temp file
Open temp file for input
Read line from input file
score1 = value of text after "scores " in line
Read line from input file
score2 = value of text after "scores " in line
Close file
Print score1 " " score2 (tab) (no crlf)
If score1 > score2 Then
source = warrior1
destination = warrior2
Else
source = warrior2
destination = warrior1
Print source " --> " destination (crlf)
Open source for input
Open destination for output
Read first line from input and write to output
Length = 0
Loop
Read redcode line from input file
Saved line = redcode line
If Rnd < delete chance Then
If not end-of-file Then
read redcode line from input file
Saved line = redcode line
If Rnd < insert chance Then
Save redcode line
Call write sub (randomizes redcode line)
If Rnd < dup line chance Then restore redcode line
If Rnd < instruction chance Then
Replace instruction with a random instruction from instruction string
If Rnd < modifier chance Then
Replace modifier with a random modifier from modifier string
If Rnd < address chance Then
Replace A mode with a random mode from mode string
If Rnd < data chance Then
If Rnd < small number chance Then
Replace A data with INT(Rnd * MaxLen * 2 - MaxLen)
Else
Replace A data with INT(Rnd * Coresize)
If Rnd < incdec chance Then
If Rnd < .5 Then
A data = A data + 1
Else
A data = A data - 1
If Rnd < address chance Then
Replace B mode with a random mode from mode string
If Rnd < data chance Then
If Rnd < small number chance Then
Replace B data with INT(Rnd * MaxLen * 2 - MaxLen)
Else
Replace B data with INT(Rnd * Coresize)
If Rnd < incdec chance Then
If Rnd < .5 Then
B data = B data + 1
Else
B data = B data - 1
If 88mode Then
Call Check88 sub
If Bad88 Then redcodeline = Saved line
Call write sub
Keep looping until end of input file is reached
Close files
Keep looping until "q" is pressed
Exit program

Write sub:
If Length < MaxLen Then
Write redcode line to output file
Length = Length + 1
Call Random line sub (actually just drops through to it)
Return

Random line sub:
Loop
Randomly pick an instruction from the instruction string
Randomly pick a modifier from the modifier string
Randomly pick an address mode for the A field from the mode string
If Rnd < small number chance Then
A data = INT(Rnd * MaxLen * 2 - MaxLen)
Else
A data = INT(Rnd * Coresize)
Randomly pick an address mode for the B field from the mode string
If Rnd < small number chance Then
B data = INT(Rnd * MaxLen * 2 - MaxLen)
Else
B data = INT(Rnd * Coresize)
Call Check88 sub
Keep looping while Bad88 = true
Return

Check88 sub:
Bad88 = false
If 88mode Then
preInstr = first two chars of redcode line
Amode = character that's A-mode of redcode line
Bmode = character that's B-mode of redcode line
jumpinstr = false
If preInstr = "jm" Or
preInstr = "dj" Or
preInstr = "sp" Then
jumpinstr = true
If (jumpinstr And Amode = "#") Or
(jumpinstr = false And Bmode = "#") Or
((Amode = "@" Or Amode = "$" Or
Bmode = "@" Or Bmode = "$") And preinstr = "da") Or
(Bmode = "#" And (preInstr = "cm" Or preInstr = "sl")) Then
Bad88=true
Return

This isn't exactly how the code operates (moved things for structure) but operationally it should be close. BASIC error-handling is essentially a GOTO which usually interferes with structuring the code even if I wanted to, not that I want to... things like "keep looping", "loop until" etc are disguised GOTO's anyway so might as well cut to the chase when it makes more sense to just jump. String processing is left out of the psuedocode except where necessary, a fixed-field warrior format is used to make it easy to apply the mutations with fairly simple code.

Evolved results

Typical early output using the stock settings (grid of 256 warriors)...

;assert 1
spl.b # 3860,$ 3
spl.i $ 2342,* -20
mov.i > -6,} -1
djn.i } 953,@ 3886
djn.a { 1876,< -7
djn.ba @ 6,* -10

This warrior scores about 70 Wilkies.

In the same amount of time the '88 settings (a ring of 100 warriors) produced this...

;assert 1
mov $ 4973,$ 3904
mov < 7624,$ 12
spl $ 7274,$ 6929
add $ 5409,< 10
sub $ -13,$ 5639
spl $ 3884,$ -19
mov $ 6,$ 16
spl $ 4705,# 15
spl < 1010,# 2
mov < 14,$ 1990
sub $ -4,< -5
spl $ -2,$ -17
spl < -3,# 5267
spl < 12,# 14
mov $ 2598,$ 17
jmz $ 6,@ 6550
spl $ 2,@ 5
slt $ -2,@ -14

A bit messy, scores about 60 Wilkies. At least it replicates.

Both of these are very early results, after the equivalent of about an hour of evolving. Stronger results typically requires many hours to days of evolving, and may require setting for a larger number of warriors in the soup for more genetic diversity. The 88 run got up to 70+ Wilkies at one point but increased the number of rounds and they all turned to stone... wasn't expecting that, didn't save any while still paper...

Eventually the 88 run (continued from a soup saved before the stone incident) produced this...

;assert 1
mov $ 5661,$ 7405
mov < 11,$ 7723
mov $ -2,< 4845
spl $ -2,# 6005
djn < -3,$ -5
djn $ 7924,< 6653
spl $ 1411,$ 6411

A bit more optimized, scores 72 Wilkies, but still too weak to even make the "mixed" hill. Previous versions of LCE produced replicating warriors scoring over 80 Wilkies, this one is presently 6th place on the mixed hill...

;redcode
;name Confused Paper
;author TEV5/WTN
;evolved 58.red 5/25/09
;assert CORESIZE==8000
slt.x $ 1554,} 4041
spl.a $ -4,< -5
mov.b @ -5,} 16
seq.x $ -5,# 15
spl.f # 6370,{ -16
spl.i $ 5007,$ 17
mov.i * 1475,{ 1127
mov.i { 5488,> 9
mov.i < 13,{ -3
mov.i < 2356,{ -3
djn.i $ -2,{ 5681
div.x } 3451,{ 1762
sne.i * 4378,$ 3412
djn.i $ -16,@ 8
jmn.x # -4,} 4876
mov.i } 7736,* 5702
mov.ba $ 3353,* 2159
djn.ba > -14,$ 1

Note that the "TEV5" in the warrior comments has nothing to do with J.M.'s truly tiny TEV0 (TEV is an obvious name to call a tiny/trivial evolver), here is the v0.5 version of my "trivial" series...

soupsize=100:maxlen=20:range=3:spl=1:cs=8000:pmars$="pmars -s 8000 -p 8000 -c 80000 -l 100 -d 100 -b -r 50"
RANDOMIZE TIMER:n=soupsize:l=maxlen:m$=".i .i .i .i .a .a .b .b .x .f .ab.ba":em=12:d$="$ $ $ # @ * < > { }":ed=10
i$ = "mov mov mov mov mov mov spl spl spl spl spl djn djn dat add sub mul div mod sne seq slt jmn jmz jmp":ei=25
inl=.01:del=.01:dup=.3:ins=.01:inm=.03:adr=.03:chd=.05:sn=.5:ON ERROR GOTO noini:OPEN "tev5.ini" FOR INPUT AS #1
ON ERROR GOTO x:INPUT #1,n,l,range,spl,cs,pmars$,i$,ei,m$,em,d$,ed,inl,del,dup,ins,inm,adr,chd,sn:CLOSE:z=-(m$="88")
m$=LEFT$(" ",z*3)+m$:em=em*(1-z)+z:IF (n>5000)+(l>500)+(LCASE$(LEFT$(pmars$,2))<>"pm")=0 THEN GOTO start ELSE GOTO x
noini: RESUME start 'comment RESUME statements for FreeBasic, to run under QBasic.exe enable RESUME statements
start:ON ERROR GOTO seed:OPEN "1.red" FOR INPUT AS #1:CLOSE:GOTO mainloop 'if first warrior exists assumes all exist
seed:FOR k=1 TO n:OPEN LTRIM$(STR$(k))+".red" FOR OUTPUT AS #1:? #1,";assert 1":IF spl THEN ? #1,"spl $ 0,$ 0"
FOR j=1-(spl<>0) TO INT(RND(1)*l+1):GOSUB mrcl:? #1,a$:NEXT j:CLOSE:NEXT k :RESUME mainloop 'comment resume for FB
mainloop:ON ERROR GOTO x:z=(RND(1)<.5)*2+1:w1=INT(RND(1)*n)+1:w2=(w1+(1+INT(RND(1)*range))*z) MOD n:IF w2<1 THEN w2=n
? STR$(w1);" vs";STR$(w2),:SHELL pmars$+STR$(w1)+".red"+STR$(w2)+".red >sc.txt":OPEN "sc.txt" FOR INPUT AS #1:INPUT #1,a$
z=INSTR(a$, "scores "):s1=VAL(MID$(a$,z+7)):INPUT #1,a$:z=INSTR(a$, "scores "):s2=VAL(MID$(a$,z+7)):CLOSE
? STR$(s1);STR$(s2);" ",:IF s1>s2 THEN s=w1:d=w2:? STR$(s);" -->";STR$(d) ELSE s=w2:d=w1:? STR$(s);" -->";STR$(d)
OPEN LTRIM$(STR$(s))+".red" FOR INPUT AS #1:OPEN LTRIM$(STR$(d))+".red" FOR OUTPUT AS #2:INPUT #1,a$: PRINT #2,a$
k=0:WHILE NOT EOF(1):LINE INPUT #1,a$:IF RND(1)<del THEN IF NOT EOF(1) THEN LINE INPUT #1,a$ 'delete line if not end
IF RND(1)<inl THEN z$=a$:GOSUB prcl:IF RND(1)<dup THEN a$=z$ 'insert random or duplicate line Note... Linux FreeBasic
IF RND(1)<ins THEN a$=MID$(i$,INT(RND(1)*ei)*4+1,3)+MID$(a$,4) 'mutate instruction v0.20 has bug in SHELL
IF RND(1)<inm THEN a$=LEFT$(a$,3)+MID$(m$,INT(RND(1)*em)*3+1,3)+MID$(a$,7) 'mutate modifier fix libfb_sys_shell.c
IF RND(1)<adr THEN a$=LEFT$(a$,7)+MID$(d$,INT(RND(1)*ed)*2+1,1)+MID$(a$,9) 'mutate A mode, mutate A data...
IF RND(1)<chd THEN x=-(RND(1)<sn):a$=LEFT$(a$,8)+RIGHT$(" "+STR$(x*INT(RND(1)*l*2-l)+(1-x)*INT(RND(1)*cs)),5)+MID$(a$,14)
IF RND(1)<adr THEN a$=LEFT$(a$,14)+MID$(d$,INT(RND(1)*ed)*2+1,1)+MID$(a$,16) 'mutate B mode, mutate B data...
IF RND(1)<chd THEN x=-(RND(1)<sn):a$=LEFT$(a$,15)+RIGHT$(" "+STR$(x*INT(RND(1)*l*2-l)+(1-x)*INT(RND(1)*cs)),5)
GOSUB prcl:WEND:CLOSE:IF INKEY$="" GOTO mainloop ' | ******** Trivial Corewar Evolver v0.5 ******* 5/24/09 WTN ******** |
x:CLOSE:SYSTEM ' (for FreeBasic use fbc -e -lang qb) | Requires pMARS. Provided as-is and without warranty, use ram disk. |
prcl:IF k<l THEN PRINT #2,a$:k=k+1 'drop through to generate new line for possible insert (yes this program is nuts!) ----'
mrcl:a$=MID$(i$,INT(RND(1)*ei)*4+1,3)+MID$(m$,INT(RND(1)*em)*3+1,3)+" "+MID$(d$,INT(RND(1)*ed)*2+1,1):x=-(RND(1)<sn)
a$=a$+RIGHT$(" "+STR$(x*INT(RND(1)*l*2-l)+(1-x)*INT(RND(1)*cs)),5)+","+MID$(d$,INT(RND(1)*ed)*2+1,1)
x=-(RND(1)<sn):a$=a$+RIGHT$(" "+STR$(x*INT(RND(1)*l*2-l)+(1-x)*INT(RND(1)*cs)),5):RETURN

The INI file format is different than the current LCE8, order of the data is in the INPUT on the fifth code line - the dup-line rate parameter was placed after delete line rate and there was no inc/dec mutation in this version. Tests seemed to indicate that incrementing and decrementing existing values to fine-tune algorithms could lead to cleaner code, but not necessarily stronger warriors and if the inc/dec rate was set too high seemed to hurt performance. In the present LCE8 defaults, about 28% of the number mutations are inc/dec operations but keep in mind it is extremely difficult to determine optimum parameters for an unguided evolver - every run is different and it requires many time-consuming runs for each parameter set before any conclusions can be drawn about the parameters. Also the final outcome heavily depends on the makeup of random code generated during the initial phases of evolution, once one of the seeds evolves to become the dominant soup form it tends to inhibit the evolution of new forms. Larger soups (1000 warriors or more) tend to increase the odds of a good seed form being created or evolving in the early stages before any one form becomes dominant. Large soups also increase diversity by providing more room for islands of different "species" of warriors to develop and compete with each other, driving up the overall strength. Of course large soups also take longer to evolve and test so it's a tradeoff, so sometimes I instead use a smaller soup of 100-200 warriors and if I don't like what I see early on I start over.

For serious evolving, an evolver such as RedMixer permits better monitoring of the process and better tracking of what parameters produced what code at what generation, however small programs like LCE and other "tiny" evolvers make it easier to try new things (like 88-rules evolving) without the weight of dozens of kilobytes of code. Although they lack the features of larger evolvers occasionally can perform at close to the same level, especially when evolving nano-size warriors where it is possible for a sub-2K program to produce quite acceptable results. BTW the nano parameters listed in the LCE8 docs probably are not optimal - maybe try spl mov mov djn or spl mov mov mov djn with no weighting of the address modes. I have also noticed with varients of TEV0, my JB evolver and others that evolving using a fixed instruction.modifier list can be as effective and faster than evolving the modifier separately. There is still much research to do when it comes to evolving warriors.



Terry Newton (wtn90125@yahoo.com)