A while back I took a week off from work to spend time at home finishing some programming projects (hey, I had plenty of leave stored up). One of the projects that I finished was in implementation of Core Wars for the QL.
Below is the documentation that I wrote for the program. It details the whole program.
INTRODUCTION
Core Wars is a game where programs that the user’s write, battle with each other in the computer. This game is taken from the Computer Receations column of Scientific American. The game was conceived by A. K. Dewdney.
Two programs are loaded into “memory” and then each program is executed in turn, one line at a time. When the computer can not execute a command from one of the programs, it declares that program to be the loser.
Programs are written in an assembly-like language called REDCODE. The syntax of REDCODE will be explained later.
HOW TO RUN THE PROGRAM
Use LRUN to load and run the SuperBasic version.
When the program starts you will be asked to enter the file name of the first program. All REDCODE programs are stored in a separate file and then loaded by the program. Enter the name file containing one of the REDCODE programs you want to run. You must include the drive name (flp1_, mdv1_, etc). A number of sample programs are included on the distribution disk. They have the _CW extention.
You will be prompted for the file name of the second REDCODE program you want to run. You man run any combination of REDCODE program.
The screen will clear and two different windows will appear. As each program executes, the current line of the program will be printed in the window designated for it. It will also print the memory location at which that line is stored. An example is:
392 MOV -1 0
This shows that the command MOV -1 0 was executed a memory location 392. These windows will allow you to see each program as they execute.
Below these windows is an area that will display the results of each program as they move through memory. Memory consists of 1000 memory locations. A graphics display of memory had been broken down to two lines of 500 pixels. As each program affects memory, it will be reflected in this graphics display. You will not see the display at first, since empty memory locations are black. Memory locations affected by Program 1 will appear as green, where as those affected by Program 2 will appear as red.
This display will vividly show you how the progams are moving through memory and where they stand in relation to each other.
When a REDCODE program line can not be executed, the word “ERROR” will be printed in the window of the losing REDCODE program and the Core Wars program will stop and return you to QDOS.
REDCODE PROGRAMMING
REDCODE is an assembly-like language consisting of commands and operands. Each line of the program has one command and one or two operands, depending on the specific command.
Below are the REDCODE commands:
MOV A B - Move
Move contents of address A to address B
ADD A B - Add
Add contents of address A to the contents of
address B and store results in address B
SUB A B - Subtract
Subtract contents of address A from contents of
address B and store results in address B
JMP A - Jump
Transfer control (goto) to address A
JMZ A - Jump Zero
Jump to address A if contents of address B are
equal to zero
JMG A - Jump Greater Than Zero
Jump to address A if contents of address B are
greater than zero
JMN A - Jump Not Zero
Jump to address A if contents of address B are
not equal to zero
DJZ A B - Decrement Jump Zero
Subtract (decrement) 1 from the contents of
address B and jump to address A if the contents
of address B are equal to zero
DJN A B - Decrement Jump Not Zero
Decrement 1 from the contents of address B and
jump to address A if the contents of address
B are not equal to zero
CMP A B - Compare
Compare contents of address A and B then skip the
next instruction if not equal
DAT A - Data
Non-executing statement. Used to make a memory
location useable for storage of data
All REDCODE addresses are relative. PC is the Program Counter. The Program Counter controls the execution of the programs and is equivilent to the memory location of the current program line. MOV 0 1 means to put the contents of address PC+0 (the current line) and put it in address PC+1 (the next line). Negative numbers are used to mean lines before the PC.
REDCODE does make provisions for direct and indirect addressing. A # before a number is direct addressing and an @ is used for indirect. MOV #0 1 means to put the number 0 in the address PC+1. The indirect commands:
DAT 20
MOV 0 @-1
means to put the contents of address PC+0 and store it at the address pointed at by the number at PC-1 (the previous line). This will put the MOV 0 @-1 line at PC+20. Note that you must use a DAT statment and not just store the number 20 at that memory location. This is what the DAT statement is for.
Indirect addressing may be used for both A and B operands, direct may be used for A, and direct may only be used for B with the CMP command. All other commands may not use direct addressing of B.
The MOV and DAT commands interact differently together. If you MOV #0 20 and address PC+20 is a DAT statement, the DAT 0 is stored at address PC+20. If there is no DAT steatement, the only 0 is stored at PC+20. This second case is used to put “logic bombs” into other programs, so that when the 0 is reached, the Core Wars program has reached an unexecutable command line and that program loses.
REDCODE PROGRAM FILES
REDCODE programs are stored in a separate file and loaded at runtime. The file is an ASCII file created with an ASCII editor, like ED, or The Editor. Each command line is on a separate line. No blank lines are allowed in the file. The commands must start in the first column. Only 1 space may separate commands and operands. Comments are allowed by using a # in the first column. With a # comment, the entire line is commented out and is ignored by the Core Wars program.
See the example REDCODE programs included on the distribution disk to help clarify any questions you may have.
** Program Name: CoreWars
** Version: 2.0
** Author: Timothy Swenson
** Date Created: 17 Oct 1990
** Date Revised: 19 Oct 1990
** The is an implementation of Core Wars as found in the
** Computer Recreations column in Scientific American.
top_mem=1000
DIM memory$(top_mem,14)
@label
pc_prg1 = RND(1 TO 1000)
pc_prg2 = RND(1 TO 1000)
IF ABS(pc_prg1-pc_prg2)=100 THEN GO TO @label
OPEN #3,con_512x256a0x0_32
PAPER #3,0 : INK #3,4 : CLS #3
title
PRINT #3
PRINT #3,TO 30;"Version 2.0"
PRINT #3,TO 26;"By Timothy Swenson"\
load_prg(pc_prg1)
load_prg(pc_prg2)
CLS #3
title
OPEN #4,scr_200x150a51x51
OPEN #5,scr_200x150a262x51
BORDER #4,2,4 : BORDER #5,2,4
PAPER #4,2 : INK #4,1
PAPER #5,2 : INK #5,1
CLS #4 : CLS #5
CURSOR #3,70,40
PRINT #3," P R O G R A M # 1"
CURSOR #3,300,40
PRINT #3," P R O G R A M # 2"
REPeat main_loop
pc_main = pc_prg1
prog=1
cmd$=memory$(pc_main)
PRINT #4,pc_main;" ";cmd$
sep_line
evaluate
plot_mem pc_prg1
IF pc_prg1=pc_main THEN
pc_prg1=pc_prg1+1
ELSE
pc_prg1=pc_main
END IF
pc_main = pc_prg2
prog=2
cmd$=memory$(pc_main)
PRINT #5,pc_main;" ";cmd$
sep_line
evaluate
plot_mem pc_prg2
IF pc_prg2=pc_main THEN
pc_prg2=pc_prg2+1
ELSE
pc_prg2=pc_main
END IF
IF pc_prg1>top_mem THEN pc_prg1=pc_prg1-top_mem
IF pc_prg2>top_mem THEN pc_prg2=pc_prg2-top_mem
END REPeat main_loop
STOP
DEFine PROCedure title
LOCal x,y
x=150 : y=10
CSIZE #3,3,1
OVER #3,1
INK #3,2
FOR z = 1 to 5
CURSOR #3,x-z,y-z
PRINT #3,"CORE WARS"
NEXT z
INK #3,4
CURSOR #3,x-6,y-6
PRINT #3,"CORE WARS"
CSIZE #3,0,0
END DEFine title
DEFine PROCedure load_prg (location)
INPUT #3,"Enter file name for Program : ";file_name$
OPEN_IN #6,file_name$
REPeat loop1
INPUT #6,in$
IF EOF(#6) THEN EXIT loop1
IF in$(1)="#" THEN END REPeat loop1
memory$(location)=in$
location=location+1
END REPeat loop1
CLOSE #6
END DEFine load_prg
DEFine PROCedure plot_mem (num)
LOCal x,y,c
IF prog=1 THEN c=4
IF prog=2 THEN c=2
IF num>500 THEN
x=num-500
y=235
ELSE
x=num
y=225
END IF
BLOCK #3,1,2,x,y,c
END DEFine plot_mem
DEFine FuNction get_num(address)
LOCal temp$
temp$=memory$(address)
IF temp$="" THEN goto_error
IF temp$(1 TO 3) <> "DAT" THEN goto_error
RETurn temp$(5 TO)
END DEFine get_num
DEFine PROCedure sep_line
LOCal blank
IF LEN(cmd$)<5 THEN goto_error
arg2 = 0
opcode$=cmd$(1 TO 3)
cmd$=cmd$(5 TO)
opmodea=1 : opmodeb=1
IF cmd$(1)="#" THEN opmodea=0
IF cmd$(1)=CHR$(64) THEN opmodea=2
IF opmodea<>1 THEN cmd$=cmd$(2 TO)
IF (opcode$="JMP") OR (opcode$="DAT") THEN arg1=cmd$
: RETurn
blank = " " INSTR cmd$
arg1 = cmd$(1 TO blank-1)
cmd$=cmd$(blank+1 TO)
IF cmd$(1)="#" THEN opmodeb=0
IF cmd$(1)=CHR$(64) THEN opmodeb=2
IF opmodeb<>1 THEN cmd$=cmd$(2 TO)
arg2=cmd$
END DEFine sep_line
DEFine PROCedure evaluate
IF opmodea=0 THEN a=arg1
IF opmodea=1 THEN a=pc_main+arg1
IF opmodea=2 THEN a=pc_main+get_num(pc_main+arg1)
IF opmodeb=0 AND opcode$<>"CMP" THEN PRINT #0,"Direct
mode not allowed to second argguement ": STOP
IF opmodeb=0 THEN b=arg2
IF opmodeb=1 THEN b=pc_main+arg2
IF opmodeb=2 THEN b=pc_main+get_num(pc_main+arg2)
IF a>top_mem THEN a=a-top_mem
IF b>top_mem THEN b=b-top_mem
IF opcode$="MOV" THEN MOV: RETurn
IF opcode$="ADD" THEN ADD: RETurn
IF opcode$="SUB" THEN SUBT: RETurn
IF opcode$="JMP" THEN JMP: RETurn
IF opcode$="JMZ" THEN JMZ: RETurn
IF opcode$="JMG" THEN JMG: RETurn
IF opcode$="JMN" THEN JMN: RETurn
IF opcode$="DJZ" THEN DJZ: RETurn
IF opcode$="DJN" THEN DJN: RETurn
IF opcode$="CMP" THEN CMP: RETurn
IF opcode$="DAT" THEN RETurn
goto_error
END DEFine evaluate
DEFine PROCedure goto_error
IF prog=1 THEN PRINT #4," E R R O R"
IF prog=2 THEN PRINT #5,"E R R O R"
FOR xx = 1 to 4
BEEP 1000,10
NEXT xx
STOP
END DEFine goto_error
DEFine PROCedure MOV
IF opmodea=0 THEN
temp$=memory$(b)&" "
IF temp$(1 TO 3)="DAT" THEN
memory$(b)="DAT "&a
ELSE
memory$(b)=a
END IF
ELSE
memory$(b)=memory$(a)
END IF
plot_mem b
END DEFine MOV
DEFine PROCedure ADD
LOCal temp
IF opmodea=0 THEN
temp=get_num(b)+a
ELSE
temp=get_num(b)+get_num(a)
END IF
memory$(b)="DAT "&temp
END DEFine ADD
DEFine PROCedure SUBT
LOCal temp
IF opmodea=0 THEN
temp=get_num(b)-a
ELSE
temp=get_num(b)+get_num(a)
END IF
memory$(b)="DAT "&temp
END DEFine SUBT
DEFine PROCedure JMP
pc_main = a
END DEFine JMP
DEFine PROCedure JMZ
LOCal temp
temp = get_num(b)
IF temp=0 THEN JMP
END DEFine JMZ
DEFine PROCedure JMG
LOCal temp
temp = get_num(b)
IF temp>0 THEN JMP
END DEFine JMG
DEFine PROCedure JMN
LOCal temp
temp = get_num(b)
IF temp<>0 THEN JMP
END DEFine JMN
DEFine PROCedure DJZ
LOCal temp
temp = get_num(b)
temp=temp-1
memory$(b)="DAT "&temp
IF temp=0 THEN JMP
END DEFine DJZ
DEFine PROCedure DJN
LOCal temp
temp = get_num(b)
temp=temp-1
memory$(b)="DAT "&temp
IF temp<>0 THEN JMP
END DEFine DJN
DEFine PROCedure CMP
LOCal tempa,tempb
IF opmodea=0 THEN
tempa=a
ELSE
tempa=get_num(a)
END IF
IF opmodeb=0 THEN
tempb=b
ELSE
tempb=get_num(b)
END IF
IF tempa<>tempb THEN pc_main=pc_main+2
END DEFine CMP
DEFine PROCedure list_memory
FOR x=1 TO top_mem
PRINT memory$(x)
NEXT x
END DEFine list_memory
Example Core War Programs: (indented only for publication. Code must start in the first column)
IMP_CW:
MOV 0 1
DWARF_CW:
DAT -1
ADD #5 -1
MOV #0 @-2
JMP -2
GEMINI_CW:
DAT 0
DAT 99
MOV @-2 @-1
CMP -3 #9
JMP 4
ADD #1 -5
ADD #1 -5
JMP -5
MOV #99 93
JMP 93