BATCODE

Developer(s): Eduardo Fuentes
Date: 1986 and 2023
Type: Program
Platform(s): TS 2068
Tags: Game

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 program
  • f$(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:

AddressPurpose
65502Zero-fill / clear a string array
65497Delete lines (bulk NOP fill) in p$
65507Initialize the arena f$ with NOP instructions
64236Execute one battle step / full battle loop
64444Advance 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 string
  • FN 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 256 via subtraction)
  • FN p(x) — reads a 16-bit little-endian word from memory at address x

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)MnemonicOperand count
0DAT1
1JMP1
2DJN2
3MOV2
4ADD2
5SUB2
6JMZ2
7JMN2
8CMP2
9LDP2
10STP2
11XCH1
12NOP0 (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, matching f$(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,8 at line 1010 sets the caps-lock system variable so that INPUT LINE accepts lowercase input naturally.
  • Line 3080 calls GO SUB 1630 directly (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

Appears On

Related Products

Related Articles

Related Content

Image Gallery

BATCODE

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.

Scroll to Top