The JB Corewar Evolver ====================== This is a small evolver that's mainly useful for evolving nano warriors for core-size 80. It was heavily inspired by John Metcalf's tiny evolver: http://impomatic.blogspot.com/2009/06/corewar-tiny-evolver-tev-v0.html Like TEV, JB is written in a GWBASIC-style language (Blassic) with line numbers, stores the soup in memory to simplify mutation and reduce disk access, and uses pmars' -k option to simplify scoring. Other evolvers also use an in-memory soup and -k but until seeing TEV I didn't realize just how much these techniques can simplify things. I borrowed TEV's RND-RND method of generating +/- numbers, the full-size JB has additional number options. Beyond that, JB is very different. TEV is a bench-driven evolver that can function with a limited soup size and permits controlling the nature of the output by the selection of benchmark warriors that are used with it. JB is an unguided evolver with little control over the output other than careful adjustment of the evolving parameters. As with any unguided evolver, JB requires a large soup, a long time, and good selection skills to produce effective results. However, once strong code does arise it tends to perform better than bench-driven evolvers against a greater variety of warriors. Two versions of JB are presented here, a minimal v0.3 version and a "fancy" 0.7 version. The 0.3 version eventually generated this warrior... ;redcode-nano ;name JB268 ;author Terry Newton ;strategy Evolved using JB v0.3 w/ 1000-warrior soup ;strategy NanoBM07 score 144.2, Koenigstuhl(20) 153.9 ;assert CORESIZE==80 mov.i {-19,$-28 spl.b #-42,>21 mov.i @-28,{-3 mov.i {-4,{-2 djn.f $-2,$-3 end ...which got 8th place on the SAL Nano Hill when submitted on 6/14/09. The 0.7 version adds multiple choices for number mutations, has an additional line-swap mutation, uses a "ribbon" soup topology rather than a "fuzzy ring" topology, and stores the parameters in the soup save file to avoid constantly editing the code. The 0.7 version is much larger but still rather small for an evolver. These programs are designed to be run using the Blassic interpreter, Windows and Linux versions are available at: http://blassic.org/ They can be run under GWBASIC or QBasic with slight mods - dos has far less available memory so the soup size (variable N) must be set fairly low, 1000 won't work, 200 is probably OK. Version 0.3 should run under GWBASIC or QBasic with no mods other than lowering the number of soup warriors, see version 0.7 notes for GW/QB mods. Running on a ram disk is less critical with this evolver than with file-based evolvers that constantly update hundreds or thousands of files (writes are restricted to 3 files - a b and s) but I still run it on a ram disk for speed, and DO NOT run JB on a solid-state disk. Like most evolvers JB uses 100% of the available CPU speed so avoid running on fast systems with inadequate cooling. Under Ubuntu Linux I can run multiple instances in different different directories with not that much impact on using the system for other light-duty things. Installing for Windows... Get blassicxxx.zip (xxx=version) and extract the blassic.exe file, can be placed in a path directory like C:\Windows\System32 or put in the directory where the program is being run from. Copy/paste the desired JB version from this text file into Notepad and save as "jb.bas" (use quotes so Notepad doesn't add .txt). From a dos prompt, change to the directory (cd) if not there and run using: blassic jb.bas To avoid having to open a dos prompt save this as "run_jb.bat"... @echo off blassic jb.bas Installing for Linux... Get the Debian or RPM package and install (I didn't actually install, just opened the .deb file as if it were an archive and extracted the blassic file to /usr/local/bin - no other files needed to run). If desired get the source and compile but for me it came out lots bigger than the packaged version, so using the binary that was in the blassic_0.8.1-1_i386.deb package (don't know what the difference is between that and the current 0.10 version but it works and is fast). Copy/paste the desired JB version into an editor and save as "jb.bas". To run open a command terminal where it is and enter: blassic jb.bas To avoid opening a terminal make a shell script, say "run_jb.sh"... #!/bin/bash cd `dirname $0` xterm -e blassic jb.bas Modify the filenames, scripts etc in these instructions as needed. A "server" version of pmars compatible with the OS is needed. For Windows the SDL version of pmars should work, get pmars-server.exe from http://www.cs.helsinki.fi/u/jpihlaja/cw/pmars-sdl/ and either rename it to pmars.exe or edit the JB program code to say pmars-server instead. The pmars binary can be put in a path directory or the JB work directory. For Linux the x86 binaries I use and the source used to make it are at: http://newton.freehostia.com/cw/pmars092N.tar.gz Or get the pmars source and compile your own binary. Rename the desired binary to "pmars" and put in /usr/local/bin or another path directory, or edit the JB code to specify location. If pmars is put in the evolver work directory specify ./pmars so it can find it. (ok... most the above info should be obvious to anyone familiar with standard Corewar and PC OS procedures but I have difficulty writing sparse docs... afraid I'll leave out something important or useful...) ============================================================================ JB Version 0.3... This is the minimalistic version. It could be even smaller but if the soup save/restore code were removed it'd be rather useless with no easy way to tell when to stop it and no way to restart once stopped. A couple of lines have been added for comments and safety but the parameters are the same as used to make JB268. The program code must be edited to alter the parameters or to restart after exiting. ------ begin jb.bas version 0.31 ----------------------------------------- 10 PRINT "JB v0.31 Evolving, press q to quit" '6/12/09 WTN (mod 6/21/09) 11 REM Requires pmars. This code is Public Domain, as-is, no warranty. 100 L=5:N=1000:R=2:C=80:P$="pmars -b -s 80 -p 80 -c 800 -l 5 -r 100 -k" 102 LS=0 'set LS=1 to reload soup from soup.txt 110 I$="spl.b mov.i mov.i mov.i djn.f ":NI=5:N$="$#@<>*{}":NN=8 115 IF N>5000 OR R>N OR L>500 THEN PRINT "Settings too big":SYSTEM 120 DIM A$(N,L),C$(N,L),D(N,L),E$(N,L),F(N,L):RANDOMIZE 'TIMER 122 IF LS=0 THEN GOTO 130 123 OPEN "soup.txt" FOR INPUT AS #1:FOR W=1 TO N:INPUT #1,L$ 124 FOR I=1 TO L:INPUT #1,A$(W,I),C$(W,I),D(W,I),E$(W,I),F(W,I) 125 NEXT I:NEXT W:CLOSE #1 130 W1=INT(RND*N+1):F$="a":W=W1:GOSUB 500 140 W2=(W1+(INT(RND*2)*2-1)*INT(RND*R+1))MOD N:IF W2<1 THEN W2=W2+N 150 F$="b":W=W2:GOSUB 500:SHELL P$+" a b >s":OPEN "s" FOR INPUT AS #1 160 INPUT #1,L$:S1=VAL(L$):INPUT #1,L$:S2=VAL(L$) 170 CLOSE #1:IF S1<=S2 THEN T=W1:W1=W2:W2=T 180 W=W2:FOR I=1 TO L:A$(W,I)=A$(W1,I):C$(W,I)=C$(W1,I) 190 D(W,I)=D(W1,I):E$(W,I)=E$(W1,I):F(W,I)=F(W1,I) 200 IF RND<.02 THEN GOSUB 600 210 IF RND<.03 THEN GOSUB 610 220 IF RND<.06 THEN GOSUB 620 230 IF RND<.03 THEN GOSUB 630 240 IF RND<.06 THEN GOSUB 640 250 NEXT I:IF INKEY$<>"q" THEN GOTO 130 252 OPEN "soup.txt" FOR OUTPUT AS #1:FOR W=1 TO N:PRINT #1,"----";W 253 FOR I=1 TO L:PRINT #1,A$(W,I);",";C$(W,I);",";D(W,I);","; 254 PRINT #1,E$(W,I);",";F(W,I):NEXT I:NEXT W:CLOSE #1 260 FOR W=1 TO N:F$=MID$(STR$(-W),2)+".red":GOSUB 500:NEXT W:SYSTEM 500 OPEN F$ FOR OUTPUT AS #1:PRINT #1,";assert 1" 510 FOR I=1 TO L:IF A$(W,I)<>"" THEN GOTO 530 520 GOSUB 600:GOSUB 610:GOSUB 620:GOSUB 630:GOSUB 640 530 PRINT #1,A$(W,I);" ";C$(W,I);D(W,I);","; 540 PRINT #1,E$(W,I);F(W,I):NEXT I:CLOSE #1:RETURN 600 A$(W,I)=MID$(I$,INT(RND*NI)*6+1,6):RETURN 610 C$(W,I)=MID$(N$,INT(RND*NN)+1,1):RETURN 620 D(W,I)=INT((RND-RND)*C):RETURN 630 E$(W,I)=MID$(N$,INT(RND*NN)+1,1):RETURN 640 F(W,I)=INT((RND-RND)*C):RETURN ------ end jb.bas -------------------------------------------------------- Settings... L=warrior length N=soup size (number of .red files dumped when exiting) R=selection range (maximum soup distance between battle partners) C=core size for numbers (set simulation coresize pmars command line) (note - C=80 makes flat distribution, use C=40 for weighted) P$=pmars command line used to run the battles LS=0 for new run, LS=1 to reload soup from soup.txt I$=list of instructions and modifiers (each field 6 chars so space-pad) NI=number of items in I$ N$=list of address mode symbols NN=number of symbols in N$ Mutation rates are set in lines 200-240 to set the odds-per-item of changing the instruction, A mode, A value, B mode and/or B value. Operation... Adjust the parameters as desired, for a new run set LS to 0, run the program. Let it run for awhile, or to check the soup press q and it will write all the soup warriors to .red files and also to the soup.txt file to be able to restart. Use a benchmark program (I use my dos TEST program - http://newton.freehostia.com/cw/test.zip) to evaluate the strength of the soup warriors, I usually archive the whole work directory along with test results so I can analyse later or restore if things go wrong. To continue evolving edit the code to specify LS=1 to reload the soup from the soup.txt file created when quitting. If you forget to edit to load soup, don't press q but press control-C to exit without saving anything. How it works... Lines 100-120 set and check the variables and dimension the arrays. Lines 122-125 reload the soup from soup.txt if specified. Main loop is at line 130, starting with picking a random soup number and writing the warrior to file "a" (by calling the subroutine at 500). Line 140 determines the topology, in this case picks another warrior that is + or - 1 to R warriors removed (wrapping at the ends). The 2nd warrior is written to file "b" then pmars is called to battle and write the scores to file "s" which is opened and the values of the first score digits (wins) is read. The W1 and W2 variables are swapped if the first warrior ties or loses, so that W1 is always the winner (or other opponent in the event of a tie) and W2 is the destination of the mutation. Next line 180 sets W to W2 for the mutation code, and starts a loop to loop through each line of the warrior. First the array items for each line are copied to the destination array items, then if RND is less than the specified mutation rate numbers it calls a subroutine to alter that item. Line 250 loops for the next line, then checks to see if q was pressed - if not then jumps to line 130 to keep evolving. Lines 252-254 write the soup arrays to soup.txt in a form that can be reloaded. Line 260 dumps all the soup warriors to .red files and exits the program. Filenames formed using the code F$=MID$(STR$(-W),2)+".red" for compatibility with BASIC's that add a leading space to positive numbers. Lines 500-540 are a subroutine to write warrior W to file F$. Opens the file for output and writes an assert line, then loops for each warrior line. If the instruction is empty then calls the mutate subroutines to create random code in the array. Writes each line in proper redcode format, closes the file and returns. Called for each battle warrior while evolving, and for each warrior when dumping. Lines 600-640 are the mutation subroutines, with the warrior and line specified by W and I. Line 600 selects a new instruction, line 610 selects a new A-field address mode, line 620 selects a new A-field value, line 630 selects a new B-field address mode, and line 640 selects a new B-field value. ============================================================================ JB Version 0.7... This is the expanded version with more features: - prompts to reload the soup rather than having to edit the code - stores the parameters in the soup.txt file so different setups can be loaded without having to edit the program to match, also permits altering the parameters by (carefully) editing the soup file - selectable number weighting, can specify ratio of small and big numbers, use RND-RND method, or further weight using RND^n-RND^n - new mutation option that when triggered swaps two lines - uses a "ribbon" grid topology rather than a "fuzzy ring", X-size is adjustable, offset-wraps in X direction but not Y direction so can (sort of) simulate a ring with range of r if X=N/r - derives pmars command line from warrior and core size and rounds (this mostly limits it to standard sizes of 80, 800 or 8000) - goofy "cylons" display while evolving (but useful for gaging speed) ------ begin jb.bas version 0.71 ----------------------------------------- 100 REM JB Corewar Evolver for Blassic 6/14/09 WTN (mod 6/21/09) 110 REM Requires the pmars program, edit below if needed. 120 REM This code is Public Domain, as-is, no warranty. 130 REM ===== Default variables for Nano ============= 140 L=5:N=200:X=50:C=80:R=100:PM$="pmars" 150 I$="spl.b mov.i mov.i djn.f ":N$="$#@<>*{}" 160 MA=.02:MC=.03:MD=.06:ME=.03:MF=.06:MG=.01:WA=1:WB=1 170 REM L=warrior length, N=number of soup warriors 180 REM X=width of grid (<= soup size), C=core size 190 REM R=battle rounds, PM$=pmars base command 200 REM I$=instruction.modifier list, N$=address mode list 210 REM (each instr.mod must be 6 chars, space pad) 220 REM MA,MC,MD,ME,MF,MG=mutation rates for instruction, 230 REM A mode, A value, B mode, B value, line swap 240 REM WA,WB=if <0 then small-number ratio for A/B fields 250 REM If WA or WB >=1 then uses INT((RND^n-RND^n)*C) 260 REM All these except PM$ recorded in soup.txt file 270 CY=1 'set CY to 1 for "cylons", 0 to don't do that 280 REM ===== Initial prompts and soup load ========== 290 RANDOMIZE 'TIMER 300 PRINT "JB v0.71 Evolver":PRINT "Reload soup? "; 310 T$=INKEY$:IF T$="y" THEN PRINT "Yes":GOTO 330 320 IF T$="n" THEN PRINT "No":GOTO 430 ELSE GOTO 310 330 OPEN "soup.txt" FOR INPUT AS #1:INPUT #1,T$:T$=MID$(T$,5,3) 340 IF T$<>"JB7" THEN CLOSE #1:PRINT "Incorrect soup file":SYSTEM 350 INPUT #1,L:INPUT #1,N:DIM A$(N,L),C$(N,L),D(N,L),E$(N,L),F(N,L) 360 INPUT #1,X:INPUT #1,C:INPUT #1,R:LINE INPUT #1,I$:LINE INPUT #1,N$ 370 INPUT #1,MA:INPUT #1,MC:INPUT #1,MD:INPUT #1,ME:INPUT #1,MF 380 INPUT #1,MG:INPUT #1,WA:INPUT #1,WB:FOR W=1 TO N:INPUT #1,T$ 390 IF LEFT$(T$,1)<>"-" THEN CLOSE #1:GOTO 440 'permit partial load 400 FOR I=1 TO L:INPUT #1,A$(W,I),C$(W,I),D(W,I),E$(W,I),F(W,I) 410 NEXT I:NEXT W:CLOSE #1:GOTO 440 420 REM ===== Initialize ============================= 430 DIM A$(N,L),C$(N,L),D(N,L),E$(N,L),F(N,L) 'skip if soup loaded 440 IF N>5000 OR X>N OR L>500 THEN PRINT "Settings too big":SYSTEM 450 T=INT((SIN(TIMER)+2)*100):FOR I=1 TO T:W=RND:NEXT I 'shuffle 460 P$=PM$+" -k -b -s "+STR$(C)+" -p "+STR$(C)+" -c "+STR$(C*10) 470 P$=P$+" -l "+STR$(L)+" -r "+STR$(R):NN=LEN(N$):NI=INT(LEN(I$)/6) 480 PRINT "Evolving... (press q to quit)" 490 IF CY THEN PRINT " |";CHR$(13);"|*"; 500 REM ===== Main Loop ============================== 510 W1=INT(RND*N+1):F$="a":W=W1:GOSUB 780 520 W2=W1+INT(RND*3-1)+INT(RND*3-1)*X:IF W2<1 OR W2>N OR W1=W2 THEN 520 530 F$="b":W=W2:GOSUB 780:SHELL P$+" a b >s":OPEN "s" FOR INPUT AS #1 540 INPUT #1,L$:S1=VAL(L$):INPUT #1,L$:S2=VAL(L$) 550 CLOSE #1:IF S1<=S2 THEN T=W1:W1=W2:W2=T:IF CY=0 THEN 590 560 IF DI=0 THEN CP=CP+1:PRINT CHR$(8);" *"; 570 IF DI=1 THEN CP=CP-1:PRINT CHR$(8);CHR$(8);"* ";CHR$(8); 580 IF CP>26 THEN DI=1 ELSE IF CP<1 THEN DI=0 590 W=W2:FOR I=1 TO L:A$(W,I)=A$(W1,I):C$(W,I)=C$(W1,I) 600 D(W,I)=D(W1,I):E$(W,I)=E$(W1,I):F(W,I)=F(W1,I) 610 IF RND"q" THEN 510 680 REM ===== Save soup and exit ===================== 690 OPEN "soup.txt" FOR OUTPUT AS #1:PRINT #1,"----JB7 Soup File----" 700 PRINT #1,L:PRINT #1,N:PRINT #1,X:PRINT #1,C:PRINT #1,R:PRINT #1,I$ 710 PRINT #1,N$:PRINT #1,MA:PRINT #1,MC:PRINT #1,MD:PRINT #1,ME 720 PRINT #1,MF:PRINT #1,MG:PRINT #1,WA:PRINT #1,WB:FOR W=1 TO N 730 PRINT #1,"----";W:FOR I=1 TO L:PRINT #1,A$(W,I);",";C$(W,I); 740 PRINT #1,",";D(W,I);",";E$(W,I);",";F(W,I):NEXT I:NEXT W:CLOSE #1 750 FOR W=1 TO N:F$=MID$(STR$(-W),2)+".red":GOSUB 780:NEXT W 760 PRINT:SYSTEM 770 REM ===== Subroutine: Write warrior to file ====== 780 OPEN F$ FOR OUTPUT AS #1:PRINT #1,";assert 1" 790 FOR I=1 TO L:IF A$(W,I)<>"" THEN 810 800 GOSUB 840:GOSUB 850:GOSUB 860:GOSUB 890:GOSUB 900 810 PRINT #1,A$(W,I);" ";C$(W,I);D(W,I);","; 820 PRINT #1,E$(W,I);F(W,I):NEXT I:CLOSE #1:RETURN 830 REM ===== Subroutines for mutations ============== 840 A$(W,I)=MID$(I$,INT(RND*NI)*6+1,6):RETURN 850 C$(W,I)=MID$(N$,INT(RND*NN)+1,1):RETURN 860 IF WA>=1 THEN D(W,I)=INT((RND^WA-RND^WA)*C):RETURN 870 IF RND>WA THEN D(W,I)=INT(RND*C):RETURN 880 D(W,I)=INT(RND*L*2-L):RETURN 890 E$(W,I)=MID$(N$,INT(RND*NN)+1,1):RETURN 900 IF WB>=1 THEN F(W,I)=INT((RND^WB-RND^WB)*C):RETURN 910 IF RND>WB THEN F(W,I)=INT(RND*C):RETURN 920 F(W,I)=INT(RND*L*2-L):RETURN 930 IF I=1 THEN RETURN 940 T$=A$(W,I):A$(W,I)=A$(W,I-1):A$(W,I-1)=T$ 950 T$=C$(W,I):C$(W,I)=C$(W,I-1):C$(W,I-1)=T$ 960 T=D(W,I):D(W,I)=D(W,I-1):D(W,I-1)=T 970 T$=E$(W,I):E$(W,I)=E$(W,I-1):E$(W,I-1)=T$ 980 T=F(W,I):F(W,I)=F(W,I-1):F(W,I-1)=T:RETURN 990 REM ===== End of program ========================= ------ end jb.bas -------------------------------------------------------- Setup... L=warrior size N=soup size (number of .red files dumped when exiting) X=size in X direction, typically N/some integer specifying the Y size (offset wrap along X direction, no wrap along Y direction) C=coresize (typically 80, 800 or 8000 and don't expect much from 8000) R=battle rounds I$=instruction.modifier list (space at end if last not .ab or .ba) N$=address mode list MA=mutation rate for instructions MC=mutation rate for A-field address mode MD=mutation rate for A-field value ME=mutation rate for B-field address mode MF=mutation rate for B-field value MG=chance per line of swapping line with previous line WA=small number weighting for A-field (see notes below) WB=small number weighting for B-field These settings are saved to the soup.txt file in the listed order starting on line 2 (soup.txt format is not compatible with v0.3). PM$ sets the pmars base command but is not included in the soup file for obvious reasons. The soup load is cancelled if a separator does not begin with "-" so soup.txt can be used to specify the parameters for a new run by removing the soup warriors and replacing with END or any other text. Usage... Run the program. Press n if a new run or y to reload the soup from the soup.txt file. Press q to quit and check progress. If rerunning and soup load inadvertently not specified, don't press q but press control-C to exit without saving and try again. How it works... Pretty much same as v0.3 except it prompts to reload the soup, loads and saves the evolving parameters, uses a different warrior selection method, modifies the number selects to use different weighting methods, and adds a subroutine to swap the current line with the previous line (if not on line 1 which has no previous line). Most of the bloat is in extra comments, "nice" things like prompting, restoring parms and figuring out how many items in the instruction/mode lists, and in the "cylons" status display (which only works in Blassic). To run under GWBASIC or QBasic set CY=0 to disable the status display, QBasic also requires that all IF ... THEN # branches be changed to IF ... GOTO # (or IF ... THEN GOTO #). Number weighting... If WA/WB is from 0 to less than 1 then specifies the ratio of small +/- offsets (if say 0.3 then on average 30% small offsets from -L to L and 70% big numbers from 0 to C-1). If WA/WB = 1 then uses flat RND-RND method from v0.3. If WA/WB > 1 then weights numbers in the small direction. Unlike v0.3 with v0.7 the RND-RND method cannot be weighted by changing C, as it derives the pmars command line from C. Instead if gradient-weighting is desired try setting WA/WB to say 2 to 2.5 which is better anyway as it produces a higher proportion of offsets in the +/-3 range while still producing a fair number of numbers in the higher ranges (the v0.3 weighted RND-RND method with C=40 produces almost no high numbers and anything but C=40 or C=80 produces a lopsided distribution). Personally I prefer the dual-select method with WA/WB somewhere in the 0.5 range but for nano pure flat (WA/WB = 1 for +/- or 0 for all +) does pretty good too. The following program might be of use for investigating these matters... 100 DIM n(80) 110 FOR i=1 TO 100000 120 z=INT((RND^2.5-RND^2.5)*80) 130 IF z<0 THEN z=z+80 140 n(z+1)=n(z+1)+1 150 NEXT i 160 FOR i=1 TO 80 170 PRINT i-1,n(i) 180 NEXT i Written to be used in a terminal with scroll ability. The 2.5's in line 120 represent the weighting value, or remove (or make 1) and change the 80 to 40 etc explore v0.3 weighting options. ============================================================================ Well, there ya go, these evolvers aren't particularly advanced and probably only strong for nano-size warriors, but they illustrate (along with their inspiration - TEV - which is even simpler) that evolving warriors does not require complex of code. The simple GWBASIC style eliminates the need to compile, and if paths or OS-specific shells aren't used the same code runs as-is on either Windows or Linux. Line numbers don't bother me, if desired Blassic can use labels instead (handy for larger programs). Update 6/21/09... Blassic doesn't support TIMER, which is treated as any other variable, so RANDOMIZE TIMER is the same as seeding to 0. However plain RANDOMIZE does randomize so slightly modified the programs by commenting the TIMER part. The "shuffle" part in JB 0.7 is redundant now but leaving in place, doesn't hurt and might become supported and might be useful if converting to GWBASIC or other BASICs. [These varients haven't been tested yet but should be OK, at the moment my CPU is busy doing other things...] Another randomize option I sometimes use is code like... PRINT "Press a key to randomize":WHILE INKEY$="":Z=RND:WEND PRINT "Evolving..." --------------------------------------- Terry Newton (wtn90125@yahoo.com)