This program is a BASIC editor and simulator for “Core War,” a programming game in which two programs written in Redcode assembly battle each other inside a shared memory arena. The editor supports 13 Redcode mnemonics (DAT, JMP, DJN, MOV, ADD, SUB, JMZ, JMN, CMP, LDP, STP, XCH, NOP) with three addressing modes: immediate (#), indirect (@), and direct. Programs are stored as packed 5-byte records in string arrays, with the first byte encoding the opcode and both addressing mode nibbles, and the next four bytes storing the two operands as 16-bit little-endian two’s-complement integers. The battle engine itself is implemented as machine code loaded separately from tape and invoked via RANDOMIZE USR calls at addresses such as 64236, 64444, and 65507. Tape save/verify and a memory-transfer facility allow two competing programs to be loaded, swapped, or saved independently.
Originally written by Sergio Martinez Lara, published in the Spanish language magazine Microhobby, issues 70 and 71, BATCODE is a Redcode interpreter. It implements the Core War programming game by D. G. Jones and A. K. Dewdney.
Translated to English by Eduardo Fuentes in 2023. Eduardo updated the instruction set and made it Redcode 94 compliant. He corrected some bugs and added a user’s guide, which is included in the download.
Program Analysis
Program Structure
The program is organized into a command dispatcher at lines 1000–1170 that reads a single-letter command and branches accordingly, a syntactic analyzer/parser for Redcode assembly (lines 1490–1870), display and listing routines (lines 2040–2350), and dedicated command handlers for save, load, new, help, transfer, delete, move, and battle. Lines 8000–8030 save the BASIC program itself; lines 9000–9040 are the auto-run bootstrap that loads the companion machine code block from tape before jumping to line 10 (which does not exist, effectively falling through to line 50 — a deliberate technique).
Data Representation
Each Redcode instruction is stored as a 5-byte record packed into a string array. The first byte encodes the opcode in the high nibble and the two addressing mode fields in bits 3–2 and 1–0 respectively. The next two bytes are the first operand as a 16-bit little-endian value, and the final two bytes are the second operand in the same format. Two’s-complement negative values are converted by adding 65536 before packing (line 1930). Decoding reverses the process at lines 2370–2450 using integer division and modulo arithmetic.
String Arrays as Memory
Three large string arrays serve as the program’s working memory:
p$(2500)— the “player 1” warrior program (500 instructions × 5 bytes)m$(2500)— the “player 2” warrior programf$(22000)— the full 4400-cell arena used during battle
Initializing a string array element to itself (e.g., LET p$(1)=p$(1)) is a well-known idiom that forces the interpreter to zero-fill the entire array without an explicit loop.
Machine Code Integration
The battle engine and several utility routines reside in a separately loaded machine code block. They are invoked via RANDOMIZE USR at fixed addresses:
| Address | Purpose |
|---|---|
65502 | Zero-fill / clear a string array |
65497 | Delete lines (bulk NOP fill) in p$ |
65507 | Initialize the arena f$ with NOP instructions |
64236 | Execute one battle step / full battle loop |
64444 | Advance one execution step (step-by-step mode) |
Parameters are passed to machine code by POKEing values directly into the code segment. For example, line 3040 uses POKE 65498/65499 to pass a count, and lines 3140 sets the starting position of the second warrior via POKE 64259/64260. The step-by-step flag is passed at line 3170 via POKE 64064.
Defined Functions
Five DEF FN functions are declared at lines 50–90:
FN j$(x)— formats a number as a zero-padded 3-digit stringFN s$(x)— formats a number as a zero-padded 4-digit string (used for arena addresses)FN m(x)— extracts the high byte of a 16-bit value (INT(x/256))FN r(x)— extracts the low byte (x MOD 256via subtraction)FN p(x)— reads a 16-bit little-endian word from memory at addressx
Note that FN s$ shadows the plain variable s$ used as a blank-line string at line 200, which is a potential source of confusion but not a functional bug because the function and the variable occupy separate namespaces.
Mnemonic Table
The 13 Redcode opcodes are stored as a flat 39-character string T$, read from DATA at line 210, with each 3-character slice accessed as t$(j*3+1 TO j*3+3). The opcodes and their indices are:
| Index (j) | Mnemonic | Operand count |
|---|---|---|
| 0 | DAT | 1 |
| 1 | JMP | 1 |
| 2 | DJN | 2 |
| 3 | MOV | 2 |
| 4 | ADD | 2 |
| 5 | SUB | 2 |
| 6 | JMZ | 2 |
| 7 | JMN | 2 |
| 8 | CMP | 2 |
| 9 | LDP | 2 |
| 10 | STP | 2 |
| 11 | XCH | 1 |
| 12 | NOP | 0 (blank line) |
Parser Design
The parser is a simple recursive-descent style analyzer. GO SUB 1830 skips whitespace; GO SUB 1640 reads a raw decimal number (with optional sign); GO SUB 1730 reads an operand with an optional # (immediate, md=1) or @ (indirect, md=2) prefix; GO SUB 1490 reads two operands; and GO SUB 1580 reads one operand. Errors are signaled via the variable error and displayed with a caret pointer at line 1890.
Battle Execution Flow
When the E command is issued, the arena f$ is first filled with NOP instructions by the machine code at 65507. Player 1’s program is then copied into the arena starting at offset 1 (lines 3182–3184 use 250-byte chunks to work around string-slice size limits). Player 2 is placed at a random offset pos2 between 1000 and 3400. The battle loop at line 3210 calls machine code at 64236 repeatedly, checking PEEK 65122 for termination and PEEK 65121 for the loser identity (encoded as A=1, B=2 via CHR$(64+...)).
Notable Techniques and Anomalies
- The title banner string
c$is constructed at lines 130–140 by decoding pairs of decimal digits from a DATA string into characters, building a custom display string at runtime. - Line 2100 uses an address limit of 500 for
p$listings, consistent with 500 instructions × 5 bytes = 2500, but line 2110 allows up to 4400 entries for the arena dump, matchingf$(22000). - The listing routine uses
CHR$(8)(cursor left) at lines 2240–2250 to backspace over a space already printed before printing the addressing mode character, achieving in-place overwriting without attribute color changes. - The
POKE 23658,8at line 1010 sets the caps-lock system variable so thatINPUT LINEaccepts lowercase input naturally. - Line 3080 calls
GO SUB 1630directly (bypassing the modifier check of 1730) to read a plain number for the move-line count, which is intentional since a destination offset has no addressing mode.
Content
Source Code
10 REM
20 REM (c) 1985
30 REM Sergio Martinez Lara
40 REM
50 DEF FN j$(x)="000"( TO 3-LEN STR$ x)+STR$ x
60 DEF FN s$(x)="0000"( TO 4-LEN STR$ x)+STR$ x
70 DEF FN m(x)=INT (x/256)
80 DEF FN r(x)=x-256*INT (x/256)
90 DEF FN p(x)=PEEK x+256*PEEK (x+1)
100 DATA "32495756543283698271737932776582847378699032"
110 DATA "DAT","JMP","DJN","MOV","ADD","SUB","JMZ","JMN","CMP","LDP","STP","XCH","NOP"
120 RESTORE
130 READ w$: LET c$=" ": LET c$(2)=CHR$ 127
140 FOR i=1 TO LEN w$ STEP 2: LET c$=c$+CHR$ VAL w$(i TO i+1): NEXT i
150 PAPER 1: INK 7: BORDER 1: CLS
160 PRINT AT 0,7; PAPER 2;"* BATCODE EDITOR *"
170 PRINT AT 2,5; INVERSE 1; BRIGHT 1;c$
180 DIM T$(39): DIM f$(22000): DIM p$(2500): DIM m$(2500)
190 LET linea=-1
200 LET s$=" "
210 FOR I=1 TO 39 STEP 3: READ t$(i TO i+2): NEXT i
220 LET p$(1)=p$(1): RANDOMIZE USR 65502
230 LET m$(1)=m$(1): RANDOMIZE USR 65502
240 PAUSE 50: CLS
250 GO SUB 1000: INPUT "End this game? (Y/N) "; LINE l$: IF l$="Y" THEN STOP
260 GO TO 250
1000 REM syntactic analyzer
1010 POKE 23658,8: INPUT "Command: "; LINE l$
1020 IF l$="" THEN RETURN
1030 LET i=1: GO SUB 1830: LET l$=l$(i TO ): LET i=1: IF error THEN GO TO 1000
1040 LET long=LEN l$: LET l$=l$+" "
1050 IF l$(1)="L" THEN LET Z$="L": GO TO 2040
1060 IF l$(1)="D" THEN LET Z$="D": GO TO 2040
1070 IF l$(1)="P" THEN LET z$="L": GO TO 2010
1080 IF l$(1)="I" THEN LET z$="D": GO TO 2010
1090 IF l$(1)="S" THEN GO TO 2470
1100 IF l$(1)="C" THEN GO TO 2580
1110 IF l$(1)="N" THEN GO TO 2650
1120 IF l$(1)="H" THEN GO TO 2720
1130 IF l$(1)="T" THEN GO TO 2850
1140 IF l$(1)="E" THEN GO TO 3130
1150 IF l$(1)="R" THEN GO TO 3000
1160 IF l$(1)="M" THEN GO TO 3060
1170 LET i=1: GO SUB 1640
1180 IF error THEN GO SUB 1890: GO TO 1010
1190 LET dir=valor
1200 IF dir<0 OR dir>2500 THEN GO SUB 1890: GO TO 1010
1210 LET r$=l$(i TO )
1220 IF long<=4 THEN GO TO 1980
1230 IF l$(i)<>" " THEN GO SUB 1880: GO TO 1010
1240 GO SUB 1830
1250 IF long<i+2 THEN LET i=long: GO SUB 1890: GO TO 1010
1260 LET w$=l$(i TO i+2): LET i=i+3
1270 IF long<i THEN GO TO 1290
1280 IF l$(i)<>" " THEN GO SUB 1890: GO TO 1010
1290 FOR j=0 TO 12
1300 IF w$=t$(j*3+1 TO j*3+3) THEN GO TO 1330
1310 NEXT j
1320 GO SUB 1890: GO TO 1010
1330 LET cod=j
1335 IF cod=12 THEN GO TO 1980
1340 IF (cod=0 OR cod=1 OR cod=11) THEN GO SUB 1580: GO TO 1360
1350 GO SUB 1490
1360 IF error THEN GO SUB 1890: GO TO 1010
1370 LET x=op1
1380 GO SUB 1920
1390 LET v$=w$
1400 IF cod=0 OR cod=1 THEN LET w$=CHR$ 0+CHR$ 0: LET md2=0: GO TO 1430
1410 LET x=op2
1420 GO SUB 1920
1430 LET p$(dir*5-4 TO dir*5)=CHR$ (16*cod+4*md1+md2)+v$+w$
1440 PRINT AT 20,0;s$;s$
1450 IF linea<19 THEN : LET linea=linea+1: GO TO 1470
1460 PRINT AT 21,0: POKE 23692,255: PRINT
1470 PRINT AT linea,0;FN j$(dir);r$
1480 GO TO 1000
1490 REM ANALYSIS OF 2 OPERANDS
1500 GO SUB 1730
1510 IF error THEN RETURN
1520 IF I>long THEN LET error=1: RETURN
1530 LET md1=md: LET op1=valor
1540 GO SUB 1730
1550 IF error THEN RETURN
1560 LET md2=md: LET op2=valor
1570 RETURN
1580 REM ANALYSIS OF 1 OPERAND
1590 GO SUB 1730
1600 IF error THEN RETURN
1610 LET md1=md: LET op1=valor: LET md2=0: LET op2=0
1620 RETURN
1630 REM get a number
1640 GO SUB 1830: IF error THEN GO SUB 1890: GO TO 1000
1650 LET w$=""
1660 FOR i=i TO i+4
1670 IF l$(i)=" " THEN GO TO 1710
1680 IF (l$(i)<"0" OR l$(i)>"9") AND l$(i)<>"+" AND l$(i)<>"-" THEN LET error=1: RETURN
1690 LET w$=w$+l$(i)
1700 NEXT i
1710 LET valor=VAL w$
1720 RETURN
1730 REM number with modifier
1740 LET md=0
1750 IF i>long THEN LET error=1: RETURN
1760 IF l$(i)<>" " THEN LET error=1: RETURN
1770 LET i=i+1
1780 IF i>long THEN LET error=1: RETURN
1790 IF l$(i)="#" THEN LET i=i+1: LET md=1: GO TO 1810
1800 IF l$(i)="@" THEN LET i=i+1: LET md=2: GO TO 1810
1810 GO SUB 1630
1820 RETURN
1830 REM SKIP SPACES
1840 LET error=0
1850 IF i>LEN l$ THEN LET error=1: RETURN
1860 IF l$(i)=" " THEN LET i=i+1: GO TO 1850
1870 RETURN
1880 REM ERROR MESSAGE
1890 PRINT AT 20,0;s$;s$;AT 20,0;l$;AT 21,i-1;"^ ERROR"
1900 BEEP 0.5,20
1910 RETURN
1920 REM 2 complement converter
1930 IF x<0 THEN LET x=x+65536
1940 LET w$=" "
1950 LET w$(2)=CHR$ (INT (x/256))
1960 LET w$(1)=CHR$ (x-256*CODE w$(2))
1970 RETURN
1980 REM borrado de una linea
1990 LET cod=12: LET op1=0: LET op2=op1: LET md1=op1: LET md2=op1
2000 GO TO 1370
2010 REM impresion
2020 LET x=3
2030 GO TO 2060
2040 REM listado
2050 LET x=2
2060 CLS
2070 LET dir=1
2080 IF LEN l$<3 THEN GO TO 2120
2090 LET i=2: GO SUB 1630
2100 IF error=0 AND valor>1 AND valor<=500 THEN LET dir=valor*5-4
2110 IF z$="D" AND (error=0 AND valor>1 AND valor<=4400) THEN LET dir=valor*5-4
2120 FOR i=0 TO 19
2130 IF Z$="D" THEN LET w$=F$(dir TO dir+4): GO TO 2150
2140 LET w$=p$(dir TO dir+4)
2150 GO SUB 2360
2160 IF z$<>"D" THEN PRINT #x;FN j$(INT (dir/5)+1);: GO TO 2180
2170 PRINT #x;FN s$(INT (dir/5)+1);
2180 PRINT #x;" ";t$(cod*3+1 TO cod*3+3);" ";
2190 IF cod=12 THEN GO TO 2270
2200 IF md1=1 THEN PRINT #x;"#";: GO TO 2220
2210 IF md1=2 THEN PRINT #x;"@";: GO TO 2220
2215 PRINT #x;" ";
2220 PRINT #x;op1;TAB 14;
2230 IF cod<=1 OR cod=11 THEN GO TO 2270
2240 IF md2=1 THEN PRINT #x;CHR$ (8);"#";
2250 IF md2=2 THEN PRINT #x;CHR$ (8);"@";
2260 PRINT #x;op2;
2270 LET dir=dir+5: IF dir>22000 THEN LET dir=1
2280 IF Z$="L" AND dir>2500 THEN LET dir=1
2290 PRINT #x: NEXT i
2300 PRINT AT 21,0;"Press 'Y' to continue listing "
2310 PAUSE 0: LET w$=INKEY$
2320 IF w$="y" OR w$="Y" THEN CLS : GO TO 2120
2330 LET linea=19
2340 PRINT AT 20,0;s$;s$
2350 GO TO 1000
2360 REM decodification
2370 LET md2=CODE w$(1)
2380 LET op1=CODE w$(2)+256*CODE w$(3)
2390 IF op1>32767 THEN LET op1=op1-65536
2400 LET op2=CODE w$(4)+256*CODE w$(5)
2410 IF op2>32767 THEN LET op2=op2-65536
2420 LET cod=INT (md2/16)
2430 LET md2=md2-cod*16
2440 LET md1=INT (md2/4)
2450 LET md2=md2-md1*4
2460 RETURN
2470 REM save program
2480 CLS
2490 PRINT AT 0,7; PAPER 2;"* SAVE PROGRAMS *"
2500 PRINT AT 2,5; INVERSE 1; BRIGHT 1;c$
2510 INPUT "Programs name to save: "; LINE w$
2520 IF w$="" THEN GO TO 2840
2530 SAVE w$ DATA p$()
2540 PRINT #1;"Rewind the tape to verify "
2550 PRINT AT 10,0;
2560 VERIFY w$ DATA p$()
2570 GO TO 2840
2580 REM load program
2590 CLS
2600 PRINT AT 0,7; PAPER 2;"* LOAD PROGRAMS *"
2610 PRINT AT 2,5; INVERSE 1; BRIGHT 1;c$
2620 INPUT "Programs name to load: "; LINE w$
2630 LOAD "" DATA p$()
2640 GO TO 2840
2650 REM NEW PROGRAM
2660 CLS
2670 PRINT AT 0,12; FLASH 1; PAPER 2;"* ""NEW"" *"
2680 PRINT AT 2,5; INVERSE 1; BRIGHT 1;c$
2690 INPUT "Sure? (Y/N)";w$
2700 IF w$="Y" OR w$="y" THEN LET p$(1)=p$(1): RANDOMIZE USR 65502: GO TO 2840
2710 GO TO 2840
2720 REM Help screen
2730 LET w$="H-Help R-Rubout ln.C-Load prog.S-Save prog.M-Move linesN-New prog.L-List P-Print listD-Dump mem. I-Print DumpT-Transfer E-Execute "
2740 PRINT AT 3,17; PAPER 6;" "
2750 PRINT AT 4,17; PAPER 6;" ";AT 4,31;" "
2760 FOR i=0 TO 11
2770 PRINT AT i+5,17; PAPER 6;" "; PAPER 1;" ";w$(i*12+1 TO (i+1)*12); PAPER 6;AT i+5,31;" "
2780 NEXT i
2790 PRINT AT 17,17; PAPER 6;" ";AT 17,31;" "
2800 PRINT AT 18,17; PAPER 6;" "
2810 PRINT AT 20,7; PAPER 2;"* HELP SCREEN *"
2820 PRINT #1;AT 0,5; INVERSE 1; BRIGHT 1;c$
2830 PAUSE 0
2840 INK 7: PAPER 1: BORDER 1: CLS : LET linea=-1: GO TO 1000
2850 REM TRANSFER PROGRAM
2860 CLS
2870 PRINT AT 0,6; PAPER 2;"* TRANSFER PROGRAM *"
2880 PRINT AT 2,5; INVERSE 1; BRIGHT 1;c$
2890 PRINT AT 9,7;"S .- Save progs."
2900 PRINT AT 11,7;"C .- Load progs."
2910 PRINT AT 13,7;"I .- Swap progs."
2920 PRINT #0;" Choose an opction (S/C/I) ";: PAUSE 0: LET o$=INKEY$: PRINT #0;O$: IF CODE o$>96 THEN LET o$=CHR$ (CODE o$-32)
2930 IF o$="S" THEN LET m$=p$
2940 IF o$="C" THEN LET p$=m$
2950 IF o$<>"I" THEN GO TO 2840
2960 LET f$( TO 2500)=m$
2970 LET m$=p$
2980 LET p$=f$( TO 2500)
2990 GO TO 2840
3000 REM Delete lines
3010 LET i=2: GO SUB 1490
3020 IF error THEN GO SUB 1890: GO TO 1000
3030 IF (op1+op2>501) OR op1<1 OR op2<1 THEN GO SUB 1890: GO TO 1000
3040 LET op1=op1*5-4: POKE 65498,FN r(op2): POKE 65499,FN m(op2)
3050 LET p$(op1)=p$(op1): RANDOMIZE USR 65497: GO TO 1000
3060 REM Move lines
3070 LET i=2: GO SUB 1490: IF error THEN GO SUB 1880: GO TO 1000
3080 GO SUB 1630: IF error THEN GO SUB 1880: GO TO 1000
3090 IF op1+valor>500 OR op2+valor>500 OR valor<0 OR op1<1 OR op2<1 THEN GO SUB 1880: GO TO 1000
3100 LET op1=op1*5-4: LET op2=op2*5-4: LET valor=valor*5-1
3110 LET m$=p$: LET p$(op2 TO op2+valor)=m$(op1 TO op1+valor)
3120 LET m$(1)=m$(1): RANDOMIZE USR 65502: GO TO 1000
3130 REM Battle !!!
3140 CLS : LET pos2=INT (RND*2400+1000): POKE 64259,FN r(pos2): POKE 64260,FN m(pos2)
3150 PRINT AT 0,5; PAPER 2;"* BATTLE OF PROGRAMS *"
3160 PRINT AT 2,5; INVERSE 1; BRIGHT 1;c$
3170 INPUT "Execute step by step? (Y/N) "; LINE o$: POKE 64064,o$="y" OR o$="Y"
3180 LET f$(1)=f$(1): RANDOMIZE USR 65507
3182 FOR j=1 TO 2499 STEP 250: LET f$(j TO j+249)=p$(j TO j+249): NEXT j
3184 FOR j=1 TO 2499 STEP 250: LET f$(pos2*5+j TO pos2*5+j+249)=m$(j TO j+249): NEXT j
3200 INK 0: PAPER 6: BORDER 6
3210 LET f$(1)=f$(1): RANDOMIZE USR 64236
3220 IF o$="y" OR o$="Y" THEN PRINT AT 20,6;" Press any key ": PAUSE 0: PRINT AT 20,6; PAPER 2;" "
3230 IF INKEY$=" " THEN BEEP .1,.1: PRINT AT 20,6; FLASH 1;" Execution interrupted ": GO TO 3260
3240 IF PEEK 65122=0 THEN PRINT AT 20,6; FLASH 1;" Defeated program is ";CHR$ (64+PEEK 65121);" ": GO TO 2830
3250 LET f$(1)=f$(1): RANDOMIZE USR 64444: GO TO 3220
3260 PAUSE 0: IF INKEY$="C" OR INKEY$="c" THEN PRINT AT 20,6; PAPER 2;" ": GO TO 3240
3270 GO TO 2840
8000 REM SAVE THE EDITOR
8010 CLEAR : CLS : LET A$="BATTLE.BAS": PRINT "SAVING.. """;A$;"""...": SAVE A$ LINE 9000
8030 STOP
9000 REM LOAD MACHINE CODE
9010 CLEAR 63999: PRINT AT 10,5; FLASH 1;"LOADING MACHINE CODE..."
9020 LOAD ""CODE
9030 BORDER 7: PAPER 7: INK 0: CLS : PRINT FLASH 1;AT 10,8;" STOP THE TAPE ": FOR I=1 TO 64: BEEP .02,I: NEXT I: CLS
9040 GO TO 10
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

