#####!/bin/bash # Redcode evolver bash script # Requires pmars (compiled with no display) # Docs follow the script code version="1.3f" # pmars configuration.... rounds=50 # rounds per battle size=800 # size of CORE processes=800 # max processes cycles=8000 # cycles before tie maxsize=20 # max size of warriors pmars="pmars" parms="-b -s $size -p $processes -c $cycles -r $rounds -d $maxsize -l $maxsize" enablefixed=1 # 0=random 1=double fixed fixedoption="-f" deleteiferror=1 # if zero halts, otherwise removes offending code # display/files setup... wardir="soup" # directory to put warriors killfile="DeleteMe" # name of stop file tempfile="evolver.tmp" # file to put output of pmars warriors=1617 # max number of warriors fndigits=4 # number of digits in base filename topology=2 # 0=random 1=ring 2=grid xsize=77 # for grid, number of warriors per line (also for display) enableplot=1 # 0=status display 1=ansi-colored soup display ylines=21 # how many warrior lines to display statline=24 # line to show status line on xhome=2 # column to start in from 1 yhome=2 # line to start in from 1 drawframe=1 # 0=no border 1=draw frame around soup display showprevsoup=1 # 0=just start 1=show previous soup first numcolors=15 # number of colors, max 15 startcolor=31 # ANSI color to start with (normally 31) colormethod=0 # 0=color by origin 1=color by size coloroffset=0 # make >0 to shift color assignment # evolver configuration... redcodeline=";redcode" authorline=";author rebs$version" assertline=";assert 1" ninstructions=26 # number of instructions - 4 char fields instructions="spl spl spl spl spl spl mov mov mov mov mov dat dat dat jmp jmp" instructions="$instructions add sub slt seq sne djn jmn mul mod div" nmodifiers=12 # number of modifiers - 3 char fields modifiers="i i i i f f x x a b ab ba" nadrmodes=8 # number of address modes - 2 char fields adrmodes="# @ < > { } $ *" # chances are in proportion to 0-32767 sizechangechance=200 # chance of insertion or deletion duplinechance=25000 # if insertion, chance of duplicate evolved line instchance=200 # chance of instruction change modchance=300 # chance of modifier change mode1chance=400 # chance of adrmode1 change data1chance=500 # chance of data1 change mode2chance=400 # chance of adrmode2 change data2chance=500 # chance of data2 change datatweekchance=18000 # for data, chance of inc/dec rather than random localchance=20000 # for data random, chance of local-sized [-]number endchance=400 # chance of end number change endtweekchance=25000 # chance of end inc/dec rather than random end0chance=20000 # if random end pick chance of end 0 # more display variables... dispchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" # chars for warrior display topleftcorner="." # chars for frame toprightcorner="." bottomleftcorner="\`" bottomrightcorner="'" horizontalchar="-" verticalchar="|" # --- end normal setup variables ----------------------------------------- # this determines the format of the output code, must be "just right"... ilen=3 # length of instruction mlen=2 # length of modifier amlen=1 # length of address mode dlen=4 # length of data ipos=0 # position of instruction mpos=4 # position of modifier am1pos=7 # position of 1st address mode d1pos=9 # position of 1st data field am2pos=16 # position of 2nd address mode d2pos=18 # position of 2nd data field redcodelen=22 # total length of redcode line # derive other variables... instdiv=$[32768/$ninstructions+1] moddiv=$[32768/$nmodifiers+1] modediv=$[32768/$nadrmodes+1] filediv=$[3276800/$warriors+1] # scaling by 100 to improve accuracy datadiv=$[3276800/$size+1] # of file and data selection localdiv=$[32768/$maxsize+1] maxwarnum=$[3276700/$filediv] maxdatanumber=$[$size-1] maxnegdata=$[0-$maxsize+1] ndispchars=${#dispchars} # flow continues after functions function getinstruction { # input: $instdiv $instructions # output: random $instruction strpos=$[$RANDOM / $instdiv * 4] instruction="${instructions:strpos:ilen}" } function getmodifier { # input: $moddiv $modifiers # output: random $modifier strpos=$[$RANDOM / $moddiv * 3] modifier="${modifiers:strpos:mlen}" } function getadrmode { # input: $modediv $adrmodes # output: random $adrmode strpos=$[$RANDOM / $modediv * 2] adrmode="${adrmodes:strpos:amlen}" } function getdata { # input: $localchance $maxsize $localdiv $datadiv # output: random $data if [ $RANDOM -lt $localchance ]; then n_tmp=$[$maxsize / 2] s_tmp=" $[$RANDOM/$localdiv - $n_tmp]" else s_tmp=" $[($RANDOM * 100)/$datadiv]" fi strpos=$[${#s_tmp} - 4] data="${s_tmp:strpos}" } function fixdata { # input: $data possibly corrupted # output: numeric $data data=${data// /} # remove spaces and data=${data///} # cause syntax error data=$[$data] # force numeric } function modifydata { # input: $data $datatweekchance $size $maxnegdata plus getdata's reqs # output: modified $data if [ $RANDOM -lt $datatweekchance ]; then fixdata # avoid errors from corrupted code if [ $RANDOM -lt 16384 ]; then data=$[$data - 1] else data=$[$data + 1] fi if [ $data -lt $maxnegdata ]; then data=$[$data + $size] fi if [ $data -ge $size ]; then data=$[$data - $size] fi data=" $data" strpos=$[${#data} - 4] data="${data:strpos}" else getdata fi } function makerandomline { # input: # output: $line containing random redcode getinstruction getmodifier getadrmode && adrmode1="$adrmode" getdata && data1="$data" getadrmode && adrmode2="$adrmode" getdata && data2="$data" line="$instruction.$modifier $adrmode1 $data1 , $adrmode2 $data2" } function getendnumber { # input: $end0chance $linecount # output: $endadr from 0 to $linecount-1 if [ $RANDOM -lt $end0chance ]; then endadr=0 else # generate rnd between 1 and $linecount-1 temp1=$[$linecount - 1] if [ $temp1 -le 0 ]; then endadr=0 # length 1 warrior always has end 0 else temp1=$[32768/$temp1 + 1] endadr=$[$RANDOM/$temp1 + 1] fi fi } function printevolvedend { # input: $line containing end line, $endchance $end0chance # $endtweekchance $linecount # output: prints evolved end line to stdout line="$line " data=${line:4:3} # grab end number fixdata # avoid error if end line never read endadr=$data if [ $RANDOM -lt $endchance ]; then if [ $RANDOM -lt $endtweekchance ]; then if [ $RANDOM -lt 16384 ]; then if [ $endadr -gt 0 ]; then endadr=$[$endadr - 1] fi else endadr=$[$endadr + 1] # final check catches overflow fi else getendnumber fi fi if [ $endadr -ge $linecount ]; then getendnumber fi echo "end $endadr" } function printheader { # input: name and parent as parms, $redcodeline $authorline # $assertline $generation $origin # output: prints header to stdout echo "$redcodeline" echo ";name $1" echo "$authorline" echo ";parent $2" echo ";origin $origin" echo ";generation $generation" echo "$assertline" } function readheader { # input: stdin # output: $generation $origin $warriorname # a bit more complex than needed to allow evolving 1.0 warriors # generation defaults to zero # origin defaults to name read -r line read -r line warriorname=${line:6} generation=0 origin=$warriorname read -r line read -r line if [ "${line:2:6}" == "arent " ]; then read -r line origin=${line:8} read -r line generation=${line:12} read -r line fi } function printrandomwarrior { # input: name as parm, $localdiv $author # output: prints random warrior to stdout generation=0 origin=$1 printheader $1 "random" warsize=$[$RANDOM/$localdiv + 1] linecount=0 while (($linecount < $warsize)); do makerandomline echo "$line" linecount=$[$linecount+1] done getendnumber echo "end $endadr" } function evolveline { # input: $line containing one line of redcode, chance vars, layout vars # output: possibly modified $line # things must be in the right places for this to work # spl.ab $ 1234 , * 5678 instruction="${line:ipos:ilen}" modifier="${line:mpos:mlen}" adrmode1="${line:am1pos:amlen}" data1="${line:d1pos:dlen}" adrmode2="${line:am2pos:amlen}" data2="${line:d2pos:dlen}" if [ $RANDOM -lt $instchance ]; then getinstruction fi if [ $RANDOM -lt $modchance ]; then getmodifier fi if [ $RANDOM -lt $mode1chance ]; then getadrmode && adrmode1="$adrmode" fi if [ $RANDOM -lt $data1chance ]; then data=$data1 && modifydata && data1=$data fi if [ $RANDOM -lt $mode2chance ]; then getadrmode && adrmode2="$adrmode" fi if [ $RANDOM -lt $data2chance ]; then data=$data2 && modifydata && data2=$data fi line="$instruction.$modifier $adrmode1 $data1 , $adrmode2 $data2" } function evolvewarrior { # input: stdin, name and parent as parm, $maxsize $sizechangechance # output: warrior printed to stdout endofwarrior=0 linecount=0 readheader generation=$[$generation + 1] printheader $1 $2 # replace with new comments while [ $endofwarrior = 0 ]; do read -r line if [ ${#line} -ne $redcodelen ]; then endofwarrior=1 else evolveline if [ $RANDOM -lt $sizechangechance ]; then if [ $RANDOM -lt 16384 ]; then # delete line if [ $linecount -eq 0 ]; then echo "$line" # don't delete 1st to avoid empty linecount=$[$linecount + 1] fi else # add line if [ $linecount -lt $maxsize ]; then echo "$line" # write previous evolved line linecount=$[$linecount + 1] if [ $linecount -lt $maxsize ]; then if [ $RANDOM -lt $duplinechance ]; then evolveline # duplicate evolved line else makerandomline # random line fi echo "$line" linecount=$[$linecount + 1] fi fi fi else # no size change, write evolved line if [ $linecount -lt $maxsize ]; then echo "$line" linecount=$[$linecount + 1] fi fi fi done printevolvedend } function grabscores { # input: stdin # output: $score1 $score2 read -r score1 score1="${score1/scores / }" scorepos=$[${#score1}-5] score1=${score1:scorepos} read -r score2 score2="${score2/scores / }" scorepos=$[${#score2}-5] score2=${score2:scorepos} } function battlewarriors { # input: warrior1 warrior2 as parms, $file1 $file2 $pmars etc # output: $score1 $score2, $pmerror set to 1 if error, 0 if ok # creates random warrior(s) if file(s) don't exist if [ ! -e "$file1" ]; then if [ $enableplot -eq 0 ]; then echo "Creating warrior $1";fi printrandomwarrior $1 > "$file1" fi if [ ! -e "$file2" ]; then if [ $enableplot -eq 0 ]; then echo "Creating warrior $2";fi printrandomwarrior $2 > "$file2" fi # battle the warriors... echo -n "$1 vs $2 " pmerror=0 if [ $enablefixed -eq 0 ]; then # normal random battle, max rounds=3333 $pmars $parms $file1 $file2 1> $tempfile if [ ! $? -eq 0 ]; then pmerror=1 fi grabscores < $tempfile else # double fixed battles, max rounds=1666 $pmars $fixedoption $parms $file1 $file2 1> $tempfile if [ ! $? -eq 0 ]; then pmerror=1 else grabscores < $tempfile total1=$score1 total2=$score2 $pmars $fixedoption $parms $file2 $file1 1> $tempfile grabscores < $tempfile total1=$[$total1+$score2] total2=$[$total2+$score1] score1=" $total1" score2=" $total2" scorepos=$[${#score1}-5] score1=${score1:scorepos} scorepos=$[${#score2}-5] score2=${score2:scorepos} fi fi } function getwarriorname { # input: $warnum # output: $warriorname warriorname="000000$warnum" strpos=$[${#warriorname}-$fndigits] warriorname="${warriorname:strpos}.red" } function picktwofiles { # input: $wardir $filediv $topology $maxwarnum # output: $war1 $war2 $file1 $file2 # war1/2 return filename only, file1/2 return full path warnum=$[($RANDOM * 100)/$filediv] getwarriorname war1=$warriorname if [ $topology -eq 1 ]; then # circular topology if [ $RANDOM -lt 16384 ]; then warnum=$[$warnum-1] if [ $warnum -lt 0 ]; then warnum=$maxwarnum fi else warnum=$[$warnum+1] if [ $warnum -gt $maxwarnum ]; then warnum=0 fi fi getwarriorname war2=$warriorname else if [ $topology -eq 2 ]; then # grid-like topology # wrap on sides, no wrap on top/bottom gotit=0 while [ $gotit = 0 ]; do n=$[$RANDOM / 4096] # 0-7 opponent=$[$warnum - 1] if [ $n -eq 1 ]; then opponent=$[$warnum - $xsize - 1] fi if [ $n -eq 2 ]; then opponent=$[$warnum - $xsize] fi if [ $n -eq 3 ]; then opponent=$[$warnum - $xsize + 1] fi if [ $n -eq 4 ]; then opponent=$[$warnum + 1] fi if [ $n -eq 5 ]; then opponent=$[$warnum + $xsize + 1] fi if [ $n -eq 6 ]; then opponent=$[$warnum + $xsize] fi if [ $n -eq 7 ]; then opponent=$[$warnum + $xsize - 1] fi if [ $opponent -ge 0 ]; then if [ $opponent -le $maxwarnum ]; then warnum=$opponent getwarriorname war2=$warriorname gotit=1 fi fi done else # random topology war2=$war1 while [ $war1 == $war2 ]; do warnum=$[($RANDOM * 100)/$filediv] getwarriorname war2=$warriorname done fi fi file1="$wardir/$war1" file2="$wardir/$war2" } function drawframe { # input: $maxwarnum and display variables # output: draws frame xoffset=$[($maxwarnum/($ylines*$xsize) )*($xsize+1)] nframechars=$[$xoffset + $xsize] fxhome=$[$xhome-1] ; fyhome=$[$yhome-1] fxend=$[$xhome+$nframechars] ; fyend=$[$yhome+$ylines] echo -ne "\033[$fyhome;$fxhome""H$topleftcorner" echo -ne "\033[$fyend;$fxhome""H$bottomleftcorner" x=$xhome while (( $x < $fxend )); do echo -ne "\033[$fyhome;$x""H$horizontalchar" echo -ne "\033[$fyend;$x""H$horizontalchar" x=$[$x + 1] done y=$yhome while (( $y < $fyend )); do echo -ne "\033[$y;$fxhome""H$verticalchar" echo -ne "\033[$y;$fxend""H$verticalchar" y=$[$y + 1] done echo -ne "\033[$fyhome;$fxend""H$toprightcorner" echo -ne "\033[$fyend;$fxend""H$bottomrightcorner" } function plotwarrior { # input: $warnum $linecount $origin and display vars # output: plots warrior xoffset=$[($warnum/($ylines*$xsize) )*($xsize+1)] xpos=$[($xoffset + $warnum - ( ($warnum/$xsize)*$xsize) ) + $xhome] ypos=$[$warnum/$xsize] ypos=$[($ypos-( ($ypos/$ylines)*$ylines) ) + $yhome] echo -en "\033[$ypos;$xpos""H" # set char position if [ $linecount -ge $ndispchars ]; then char="*" else char="${dispchars:$linecount:1}"; fi if [ $colormethod -eq 1 ]; then color=$linecount # color by length else color="10#${origin:0:$fndigits}" # color by origin fi color=$[$color+$coloroffset] color=$[$startcolor+$color-($color/$numcolors)*$numcolors] if [ $color -gt 37 ]; then color=$[$color - 8] color="\033[1;$color""m" else color="\033[0;$color""m" fi echo -en "$color"$char } function getwarfilestats { # input: stdin # output: $origin $linecount $generation readheader linecount=0 endofwarrior=0 while [ $endofwarrior = 0 ]; do read -r line if [ ${#line} -ne $redcodelen ]; then endofwarrior=1 else linecount=$[$linecount + 1] fi done } function plotsoup { # input: $maxwarnum $wardir and display variables # output: draws soup from existing files for ((warnum=0;$warnum<=$maxwarnum;warnum++)); do getwarriorname warfile="$wardir/$warriorname" if [ -e "$warfile" ]; then getwarfilestats < "$warfile" # grab length and origin plotwarrior fi done } # end of functions, flow continues... # # -------- initialize -------- # if [ -e $killfile ]; then echo "$killfile exists, remove before running." exit 1 fi mkdir "$wardir" &> /dev/null # make sure soup directory exists echo "Delete this file to stop evolution script" > $killfile nobegin=0;redir=0 if [ "$1" == "-redir" ]; then # rebs -redir > file to record "ansi movie" deleteiferror=0 # always exit if pmars error redir=1 # suppresses printing of extra text fi if [ "$1" == "-append" ]; then # rebs -append >> file to continue recording deleteiferror=0 # always exit if pmars error nobegin=1 # suppresses initial frame/soup display redir=1 # suppresses printing of extra text fi if [ $redir -eq 0 ]; then echo "Redcode Evolver Bash Script version $version" echo "Delete the $killfile file to stop evolution" echo "First two random numbers: $RANDOM $RANDOM" sleep 2 fi if [ $nobegin -eq 0 ]; then if [ $enableplot -ne 0 ]; then echo -ne "\033[0m" ; clear if [ $drawframe -ne 0 ]; then drawframe ; fi if [ $showprevsoup -ne 0 ]; then plotsoup ; fi fi fi # # -------- main loop -------- # stopevolving=0 while [ $stopevolving = 0 ]; do if [ ! -e $killfile ]; then if [ $enableplot -ne 0 ]; then echo -e "\033[$statline;1H\033[0m" fi stopevolving=1 else if [ $enableplot -ne 0 ]; then echo -en "\033[$statline;1H\033[0m" # position stat line, no color fi picktwofiles battlewarriors $war1 $war2 if [ $pmerror -eq 0 ]; then echo -n "$score1 $score2 " if [ $score1 -lt $score2 ]; then echo -n "$war2->$war1" evolvewarrior $war1 $war2 < "$file2" > "$file1" warnum=10#${war1:0:$fndigits} else echo -n "$war1->$war2" evolvewarrior $war2 $war1 < "$file1" > "$file2" warnum=10#${war2:0:$fndigits} fi linecount="$linecount " echo -n " O:${origin:0:$fndigits} L:${linecount:0:3}G:$generation " if [ $enableplot -ne 0 ]; then plotwarrior else echo fi else echo "**********************" echo "** Error in redcode **" echo "**********************" if [ ! $deleteiferror -eq 0 ]; then echo "Deleting $war1 and $war2" rm "$file1" rm "$file2" else stopevolving=1 fi fi fi done if [ -e $tempfile ]; then rm $tempfile ; fi exit 0 # # Usage notes... # # This script evolves redcode warriors for the game of Corewar. The interface # is simple: delete the DeleteMe file in the current directory to make it stop. # Don't stop by any other means or corrupted redcode may be left in the soup. # Edit the script to change filenames, number of files and other variables. # # When run it creates a directory (named "soup" by default) for the warriors # then runs the following formula: # # pick two files # if either file doesn't exist, create random warrior(s) # battle the two warriors using pmars # copy the winner over the loser while making random changes # # It's a little more complicated than that but that's the general idea. # How the files are picked determines topology, topology=0 for random # pick, topology=1 to increment/decrement the warrior number (50/50 chance) # with wraparound to form a circle of warriors, topology=2 for a grid # of warriors with wraparound on the sides, no wrap from top/bottom. # In the event of a tie battle, the first selected warrior wins. # Instruction, modifier and address mode weighting is done using # duplicate entries in the array strings, numbers are either large # positive numbers or if $RANDOM is less than localchance, then a small # offset usually within the range of the warrior code. The end line # has either a number from 1 to warrior size (at time of creation), # or if $RANDOM is less than end0chance, end 0 to start the warrior # from the beginning. The end number is evolved according to endchance, # endtweekchance determines the likelyhood of an inc/dec rather than rnd. # During the copy process code lines are changed according to chance vars, # instchance controls how often the instruction changes (for each line), # modchance controls how often the modifier changes, etc. For data fields, # datatweekchance controls how often the change is inc/dec rather than rnd. # The sizechangechance var controls frequency of insertion or deletion # (50/50 odds to avoid size bias), if inserting then duplinechance determines # the likelyhood of an evolved duplicate line rather than an all-random line. # All chance variables are relative to 32767 max, the change is triggered # when $RANDOM is less than the number. n>32767 always triggers, n<1 disables. # For ex. if endchance=328 changes the end number changes roughly 1 out of 10 # evolves, if sizechangechance=328 then *each line* has a 1 in 10 chance of # being deleted or a new line being inserted. The odds are applied to every # occurence of the object, if data1chance=328 for 1 in 10 odds per object, # a 10-line warrior will have a 50/50 chance of at least one data1 change. # # The standard pmars program, compiled without graphics, is used to evaluate # the warriors to determine which warrior to evolve. I'm using version 0.8.6. # If pmars isn't in a path directory, specify full name in pmars var at the # beginning of the script. Make sure wardir and killfile don't conflict with # existing files/directories. Adjust rounds and number of warriors as needed. # If enablefixed=0 max rounds is 3333, with enablefixed=1 max rounds is 1666. # # REBS has an ANSI soup display enabled by default, make enableplot=0 to # disable. When continuing a run it scans and plots the existing soup unless # disabled by making showprevsoup=0. Width is automatic, derived from xsize # and how many warriors are specified. Normally for grids, set ylines to # warriors divided by xsize (or make warriors xsize times ylines), if ylines # isn't big enough to contain all the lines then columns are displayed. # Make statline equal to ylines plus 3 to place underneath the soup. # # Command line redirection options... # -redir as 1st parm to suppress printing of extra text, for use when # redirecting the screen output to record an ANSI "movie" file. # Usage: rebs -redir > file & # -append as the 1st parm to additionally suppress the clearscreen and # initial frame/soup display, for use when continuing an ANSI recording. # Usage: rebs -append >> file & # rm -f DeleteMe to stop rebs, cat file to play back the ANSI file. # Caution: redirecting makes huge files, get right before attempting. # # Changes... # 4/9/05 1.3f added -redir and -append command line options # 4/9/05 1.3e added coloroffset variable, different parms # 4/7/05 1.3d scaled $RANDOM to cover entire file/data range, parms # 4/7/05 1.3c fixed display bugs, different parms, rearranged code/vars # 4/3/05 1.3b color by origin or length # 4/2/05 1.3a added/rearranged code to display existing soup at startup # added color variables to colorize soup in different ways # 3/28/05 1.3 removed seed stuff, read -t fails on Cygwin 1.1/bash 2.04 # New experimental soup display - shows length and colors by species, for # small xsize displays soup as multiple columns, ylines determines number # of screen lines, width determined by number of files in soup. Status # line shows current battle on the left of cursor blob, results of the # *last* battle shown after cursor. # 3/26/05 1.2b altered RNG seed method, different parms # 3/24/05 1.2a fixed size bug again, randomizes end if out of range # 3/23/05 1.2 double fixed battles, grid topology, data/end tweeks # 3/19/05 1.1 added line topology and generation/parent/origin comments # 3/12/05 1.0f ok let's try that again (still has size bugs though) # 3/12/05 1.0e added check to prevent warrior from growing too big # 3/11/05 1.0d eliminated grep usage, simplified end number processing # 3/11/05 1.0c fixed picktwofiles, eliminated head/tail string processing # 3/10/05 1.0b more instructions, neater status display, code tweeks # 3/9/05 1.0a changed pmars redirect to 1> for Cygwin compatibility # # Wishes... better evolution/instruction parms, goal benchmarking, # interactive user interface to inspect/battle code with. # # created 3/7/05 - last code mod 4/9/05 - last evol parm mod 4/9/05 # (c) 2005 Terry Newton under the terms of the GNU Public License (GPL) # http://www.infionline.net/~wtnewton/corewar/evol/ #