[net.sources] Corewar program source

berry@zinfandel.UUCP (Berry Kercheval) (06/29/84)

Here is a program that implements 'corewar' as described in A.K.Dewdney's 
Scientific American column of May, 1984.  I would be interested in 
collecting any battle programs anyone might develop.  

For those unfamiliar with the game , this program simulates two programs in  
a simple computer's memory, battling for survival.  The simulator alternately  
executes instructions from each until a time limit expires, or an illegal  
instruction is encountered.  Time limit implies a draw; the program with the  
illegal instruction loses.  

Berry Kercheval		Zehntel Inc.	(ihnp4!zehntel!zinfandel!berry)
(415)932-6900

======= Cut Here =======  
#! /bin/csh -f
#extract this file with csh
echo extracting corewar.6
cat <<'KerchDrivOrnCo' > corewar.6
.TH COREWAR 6 
.UC 4
.SH NAME
corewar \- battling programs
.SH SYNOPSIS
.B corewar
[
.B \-l
] [
.B \-t
] [
.B \-d
]
prog1 prog2
.br
.SH DESCRIPTION
.I Corewar
loads 
.I file1
and
.I file2
into the simulated memory arena and executes instructions for 
each in turn until one encounters an illegal instruction, at which
point the other is declared the winner.
If neither has won in two minutes, the match is declared a draw.
.PP
.I Corewar is
based on A.K Dewdeney's May 1984 column in Scientific American.
.PP
The
.B \-l
option causes a listing of the generated code to be written on the
standard output while each file is loaded.
.PP
The option
.B \-t
causes an instruction execution trace to be written on the standard output.
.PP
The option
.B \-d
causes a post mortem memory dump to be writenm on the standard output.
.SH AUTHOR
Berry Kercheval (ihnp4!zehntel!berry)
'KerchDrivOrnCo'
echo extracting corewar.c
cat <<'KerchDrivOrnCo' > corewar.c
/*
 *	CoreWar
 *
 *	Programs battle for supremacy in a simulated arena!  Based on A.K
 *	Dewdeney's May 1984 column in Scientific American.
 *
 *	Usage:
 *		corewar [-l][-d][-t] prog1 prog2
 *			prog1 and prog2 must be ascii redcode files.
 *		    -l: print listing while loading the redcode programs.
 *		    -t: print verbose execution trace
 *		    -d: print post-mortem memory dump.
 *
 *	This program assumes your C compiler supports structure assignment.
 *
 *	Copyright 1984, Berry Kercheval.  All rights reserved.  Permission
 *	for distribution is granted provided no direct commercial
 *	advantage is gained, and that this copyright notice appears 
 *	on all copies.
 */

#include <stdio.h>
#include <signal.h>
#include "corewar.h"

memword	mem[MEMSIZE];		/* the simulated memory 'arena' */

char	*pr_inst();		/* returns formatted string representation of a
				 * redcode instruction suitable for %s */

int	dumpflag = 0;		/* TRUE --> print post-mortem dump */
int	traceflag = 0;		/* TRUE --> print execution trace */
int	listflag = 0;		/* TRUE --> print listing of loaded program */

main (argc, argv)
int argc;
char **argv;
{
	char 	*file1 = NULL;
	char	*file2 = NULL;
	address	pc_1, pc_2;	/* addresses of program starting points. 
				 * filled in by load() */
	address load();		/* loads a file */
	int	winner;		/* who won? */
	int	draw();		/* what happens if we time out */

	while (*++argv && **argv == '-')
		switch (*++*argv) {
		case 'l':	/* print listing at load-time */
			listflag = 1;
			break;
		case 'd':	/* print memory dump at end */
			dumpflag = 1;
			break;
		case 't':	/* print execution trace */
			traceflag = 1;
			break;
		default:	/* huh??? */
			printf("Unknown flag '%s'\n",*argv);
			break;
		}
	/* now the last two arguments should be names of two redcode files */
	if (*argv) {
		if ( access (*argv, 4) != 0) { /* check read access */
			perror(*argv);
			exit (-1);
		}
		file1 = *argv;
	}
	else {
		printf ("Usage: corewar prog1 prog2\n");
		exit(-1);
	}
	argv++;
	if (*argv) {
		if ( access (*argv, 4) != 0)  { /* check read access */
			perror(*argv);
			exit (-1);
		}
		file2 = *argv;
	}
	else {
		printf ("Usage: corewar prog1 prog2\n");
		exit(-1);
	}
	srand(getpid());
			
	clear_mem();			/* Clear the arena! Clear the arena! */
	pc_1 = load(  -1, file1); 	/* load file1 anywhere */
	pc_2 = load(pc_1, file2); 	/* load file2 1000 away from pc_1 */
	signal ( SIGALRM, draw);	/* set up for timeout */
	alarm (TIMEOUT);		/* give up after TIMEOUT seconds */
	if(traceflag)			/* print trace header here because
					 * we know the filenames here */
	{
		printf ("       %10s                                 %10s\n",
			file1, file2);
		printf("-----------------------------------------------------------\n");
	}
	winner = execute(pc_1, pc_2);	/* run the programs */
	alarm(0);			/* turn off the alarm */
	switch (winner)			/* allocate blame */
	{
		case 0:	printf ("DRAW!\n"); break;

		case 1:
		case 2: printf ("%s(%d) wins.\n", winner==1?file1:file2,winner);
			break;

		default: printf ("Error in execute -- illegal return value\n");
	}
	if (dumpflag)			/* post-mortem dump if requested */
		dump();
	exit(0);			/* Th-th-that's all folks */
}

/*
 *	clear_mem:	clear the simulated memory
 */

clear_mem()
{
	address i;

	for ( i=0 ; i < MEMSIZE; i++ )
	{
		mem[i].op = 0;
		mem[i].a_mode = 0;
		mem[i].b_mode = 0;
		mem[i].a = 0;
		mem[i].b = 0;
	}
}

/*
 *	load:
 *		Load a redcode file.  If where is not -1, place the loaded file
 *		at a random place im mem, NOT WITHIN   'DISTANCE' of 'where'
 *		If where IS -1, put the code anywhere.
 */

address
load(where, filename)
address where;
char *filename;
{
	address	r;		/* where to load current instruction */
	FILE	*f;		/* stream pointer for input file */
	char	buf[256];	/* line buffer */
	char 	*ptr;		/* pointer into the line; used in parsing */
	char	*ip;		/* pointer into the line; used in parsing */
	char	*index();
	char	error;		/* error flag printed at beginning of each 
				 * listing line; currently either ' ' or 'E' */
	address	start;		/* address of first executable instruction */
	int	op;		/* op-code value */
	int	i;		/* counter for for loops */

	/* select starting address */

	start = 0;
	r = (rand() >> 12) % MEMSIZE;	/* 4.1 rand() sucks */
	if (where != -1)
	{
		/* second program must be at least DISTANCE addresses from
		 * first; I just make their load-points DISTANCE apart 
		 */
		while ( abs (r - where) < DISTANCE )
			r = (rand() >> 12) % MEMSIZE;
	}
	printf ("loading %s at %d\n", filename, r);

	/* now do load */

	f = fopen (filename, "r");
	if (f == NULL ) {
		perror (filename); exit(-1);
	}
	/*
	 * There now follows a moderately crufty ad-hoc redcode assembler.
	 * It's not modular or very structured, but it seems to work, and
	 * redcode was so simple I didn't want to use YACC or LEX or SSL
	 */

	while ( fgets ( buf, 256, f) != NULL)	/* for each line in the file */
	{
		error = ' ';			/* no error yet */
		/* zap trailing newline to make listing generation easier */
		if ( (ptr = index(buf, '\n')) != NULL ) *ptr = '\0';
		/* zap comment */
		if ( (ptr = index(buf, '/')) != NULL ) *ptr = '\0';
		 
		/* decode instruction */

		ip = buf;		/* start at the beginning of the line */
		op = -1;		/* Invalid op-code */
		/* skip leading whitespace */
		while ( *ip && (*ip == ' ' || *ip == 011)) ip++;	
		if ( ip == ptr ) {	/* it's a 'blank' line */
			if ( ptr != NULL ) *ptr = '/';	/* put comment back */
			if (listflag)
				printf ("%c                      '%s'\n",
					error, buf);
			break;
		}
		for ( i = 0; i <= CMP; i++)	/* CMP is highest op-code */
		{
			if ( strncmp(ip, op_str[i], 3) == 0){
				op = i;
				break;
			}
		}
		if ( op == -1 ) 		/* didn't find it! */
		{
			printf ("SYNTAX ERROR '%s' -- Bad opcode\n", buf);
			error = 'E';
		}
		mem[r].op = op;
		ip += 3;		/* skip over opcode mnemonic */
		/* skip whitespace */
		while ( *ip && (*ip == ' ' || *ip == 011)) ip++;	
		/* figure out addressing mode for operand A */
		if ( op != DAT ) { 	/* DAT has only B operand */
			if ( *ip == '#'){
				mem[r].a_mode = IMMEDIATE; 
				ip++;
			}
			else if ( *ip == '@') {
				mem[r].a_mode = INDIRECT; 
				ip++;
			}
			else 
			{
				mem[r].a_mode = DIRECT;
			}
			mem[r].a = atoi ( ip );
			if ( mem[r].a < 0 ) mem[r].a += MEMSIZE;
			while ( *ip && (*ip != ' ' && *ip != 011)) ip++;
		}
		/* skip whitespace */
		while ( *ip && ( *ip == ' ' || *ip == 011)) ip++;	

		if ( op != JMP ) {		/* JMP has only A operand */
			if ( *ip == '#'){
				mem[r].b_mode = IMMEDIATE; 
				ip++;
			}
			else if ( *ip == '@') {
				mem[r].b_mode = INDIRECT; 
				ip++;
			}
			else 
			{
				mem[r].b_mode = DIRECT;
			}
			mem[r].b = atoi ( ip );
			if ( mem[r].b < 0 ) mem[r].b += MEMSIZE;
		}

		/* check for start of executable code */
		if ( op != DAT && start == 0) start = r;
		/* DAT has zero modes... */
		if ( op == DAT ) mem[r].b_mode = mem[r].a_mode = 0;

		/* Do listing stuff... */

		if ( ptr != NULL ) *ptr = '/';		/* put comment back */
		if (listflag)
			printf ("%c%4d %s  '%s'\n", error, r, pr_inst(mem[r]),
				buf);
		r++;			/* Advance to next memory location */
		if ( r > MEMSIZE ) r = r % MEMSIZE;
	}

	if (listflag)
		fflush (stdout );
	fclose (f);
	/* return starting address */
	return start;
}

/*
 * Execute the two loaded Redcode programs starting at addresses p1 and p2
 * until one executes an illegal instruction
 */

execute (p1, p2)
address p1;
address p2;
{
	printf ("executing: p1 = %d, p2 = %d\n", p1, p2);
	for(;;)
	{
		if ( -1 == (p1 = do_instruction(p1))) return 1;	
		if (p1 >= MEMSIZE) p1 = p1 % MEMSIZE;

		/* separate the two instruction traces */
		if (traceflag)
			printf ("     ||     ");

		if ( -1 == (p2 = do_instruction(p2))) return 1;	
		if (p2 >= MEMSIZE) p2 = p2 % MEMSIZE;

		/* do_instruction prints a trace, which needs to be
		 * 'newlined' here */
		if(traceflag)
			printf ("\n");
	}
}

/*
 * interpret one instruction at addr; return address of next instruction to
 * be executed, or -1 if it was an illegal instruction
 */

do_instruction(addr)
address addr;
{
	memword inst;

	if(traceflag)
		printf ("@ %d", addr); fflush (stdout);
	/* fetch instruction */
	inst = mem[addr];
	if (traceflag)
		printf ("  %s %c%4d, %c%4d    ", op_str[inst.op], 
			mode_char[inst.a_mode], inst.a,
			mode_char[inst.b_mode], inst.b); 
	fflush (stdout);
	switch ( inst.op )
	{
		case MOV:
			if (do_mov(addr, inst)) return -1; else return addr + 1;
		case ADD:
			if (do_add(addr, inst)) return -1; else return addr + 1;
		case SUB:
			if (do_sub(addr, inst)) return -1; else return addr + 1;
		case JMP:
			return (addr + inst.a)%MEMSIZE;	
		case JMZ:
			if ( mem[inst.b].op == DAT && mem[inst.b].b == 0 )
				return (addr + inst.a)%MEMSIZE;	
			else
				return (addr + 1)%MEMSIZE;
		case JMG:
			if ( mem[inst.b].op == DAT && mem[inst.b].b > 0 )
				return (addr + inst.a)%MEMSIZE;	
			else
				return (addr + 1) % MEMSIZE;
		case DJZ:
			mem[inst.b].b -= 1;
			if ( mem[inst.b].b == 0 )
				return (addr + inst.a) % MEMSIZE;	
			else
				return (addr + 1) % MEMSIZE;
		case CMP:
			return addr + do_cmp(addr, inst);

		case DAT:
		default:
			printf ("Illegal instruction %s @ %d\n", 
				pr_inst(inst), addr); 
			return -1;

	}
}

do_mov(addr, inst)
address addr;
memword inst;
{
	address source, destination;
	memword data;

	data.op = 0;
	data.a_mode = data.b_mode = 0;
	data.a = data.b = 0;

	switch (inst.a_mode)
	{
		case IMMEDIATE:
			data.b = inst.a;
			break;
		case DIRECT:
			data = mem[(addr + inst.a) % MEMSIZE];
			break;
		case INDIRECT:
			source = mem[(addr + inst.a) % MEMSIZE].b;
			data =   mem[(source + addr + inst.a) % MEMSIZE];
			break;
		default: /* ERROR */
			printf ("do_mov: illegal addressing mode\n");
			return 1;
	}
	switch (inst.b_mode)
	{
		case IMMEDIATE: /* error */
			printf ("do_mov: illegal immediate destination\n");
			return 1;
		case DIRECT:
			mem[(addr + inst.b) % MEMSIZE] = data;
			break;
		case INDIRECT:
			destination = mem[(addr + inst.b) % MEMSIZE].b;
			mem[(destination + addr + inst.b) % MEMSIZE] = data;
			break;
		default: /* ERROR */
			printf ("do_mov: illegal addressing mode\n");
			return 1;
	}	
	return 0;
}

/*
 * CMP: compare a and b, return 1 if same, 2 if different 
 */

do_cmp(addr, inst)
address addr;
memword inst;
{
	address source, destination;
	memword data;

	data.op = 0;
	data.a_mode = data.b_mode = 0;
	data.a = data.b = 0;

	switch (inst.a_mode)
	{
		case IMMEDIATE:
			data.b = inst.a;
			break;
		case DIRECT:
			data = mem[(addr + inst.a) % MEMSIZE];
			break;
		case INDIRECT:
			source = mem[(addr + inst.a) % MEMSIZE].b;
			data =   mem[(source + addr + inst.a) % MEMSIZE];
			break;
		default: /* ERROR */
			printf ("do_add: illegal addressing mode\n");
			return 1;
	}
	switch (inst.b_mode)
	{
		case IMMEDIATE: /* error */
			if (data.a == inst.b) return 1;
			else return 2;
		case DIRECT:
			if ( data.a == mem[(addr + inst.b) % MEMSIZE].b)
				return 1;
			else return 2;
		case INDIRECT:
			destination = mem[(addr + inst.b) % MEMSIZE].b;
			if(data.b == mem[(destination+addr + inst.b)%MEMSIZE].b)
				return 1;
			else return 2;
		default: /* ERROR */
			printf ("do_add: illegal addressing mode\n");
			return 1;
	}	
}

do_sub(addr, inst)
address addr;
memword inst;
{
	address source, destination;
	memword data;

	data.op = 0;
	data.a_mode = data.b_mode = 0;
	data.a = data.b = 0;

	switch (inst.a_mode)
	{
		case IMMEDIATE:
			data.b = inst.a;
			break;
		case DIRECT:
			data = mem[(addr + inst.a) % MEMSIZE];
			break;
		case INDIRECT:
			source = mem[(addr + inst.a) % MEMSIZE].b;
			data =   mem[(source + addr + inst.a) % MEMSIZE];
			break;
		default: /* ERROR */
			printf ("do_sub: illegal addressing mode\n");
			return 1;
	}
	switch (inst.b_mode)
	{
		case IMMEDIATE: /* error */
			printf ("do_sub: illegal immediate destination\n");
			return 1;
		case DIRECT:
			mem[(addr + inst.b) % MEMSIZE].b -= data.b;
			break;
		case INDIRECT:
			destination = mem[(addr + inst.b) % MEMSIZE].b;
			mem[(destination + addr + inst.b) % MEMSIZE].b -= data.b;
			break;
		default: /* ERROR */
			printf ("do_sub: illegal addressing mode\n");
			return 1;
	}	
	return 0;
}

do_add(addr, inst)
address addr;
memword inst;
{
	address source, destination;
	memword data;

	data.op = 0;
	data.a_mode = data.b_mode = 0;
	data.a = data.b = 0;

	switch (inst.a_mode)
	{
		case IMMEDIATE:
			data.b = inst.a;
			break;
		case DIRECT:
			data = mem[(addr + inst.a) % MEMSIZE];
			break;
		case INDIRECT:
			source = mem[(addr + inst.a) % MEMSIZE].b;
			data =   mem[(source + addr + inst.a) % MEMSIZE];
			break;
		default: /* ERROR */
			printf ("do_add: illegal addressing mode\n");
			return 1;
	}
	switch (inst.b_mode)
	{
		case IMMEDIATE: /* error */
			printf ("do_add: illegal immediate destination\n");
			return 1;
		case DIRECT:
			mem[(addr + inst.b) % MEMSIZE].b += data.b;
			break;
		case INDIRECT:
			destination = mem[(addr + inst.b) % MEMSIZE].b;
			mem[(destination + addr + inst.b) % MEMSIZE].b += data.b;
			break;
		default: /* ERROR */
			printf ("do_add: illegal addressing mode\n");
			return 1;
	}	
	return 0;
}

dump()
{
	int r;
	int flag = 0;

	printf ("\n\n---------- MEMORY DUMP -------------\n");
	for ( r = 0; r < MEMSIZE; r++) {

		if ( iszero(mem[r]) && flag == 0) {
			printf ("%5d 0:0:0:0000:0000\n", r);
			flag = 1;
		}
		else if (iszero(mem[r]) && flag == 1) {
			printf (" *\n");
			flag = 2;
		}
		else if (iszero(mem[r]) && flag == 2) {
			/* skip it */
		}
		else
		{
			printf ("%5d %s\n", r, pr_inst(mem[r]));
			flag = 0;
		}
	}
}

iszero(x)
memword x;
{
	if (x.op == 0 && x.a_mode == 0 && 
	    x.b_mode == 0 && x.a == 0 && x.b == 0 )
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

draw()
{
	printf ("game is a DRAW -- timed out after %d seconds\n", TIMEOUT);
	if (dumpflag)
		dump();
	exit(0);
}

char *
pr_inst(x)
memword x;
{
	char buf[200];

	sprintf(buf,"%1d:%1d:%1d:%04d:%04d",x.op,x.a_mode,x.b_mode,x.a,x.b);
	return buf;
}
'KerchDrivOrnCo'
echo extracting corewar.h
cat <<'KerchDrivOrnCo' > corewar.h
/*
 *	General defines for CoreWar, based on A.K. Dewdney's May 1984
 *	column in Scientific American.
 *
 *	Copyright 1984, Berry Kercheval.  All rights reserved.  Permission
 *	for distribution is granted provided no direct commercial
 *	advantage is gained, and that this copyright notice appears 
 *	on all copies.
 */

#define TIMEOUT 120		/* only play for two minutes */
struct	memword
	{
		short op;
		short a_mode;
		short b_mode;
		int   a;
		int   b;
	};

typedef struct memword memword;
typedef	long	address;

#define 	MEMSIZE		8000
#define 	DISTANCE	1000	/* programs at laest this far apart */

/*
 *	Redcode Instruction Format:
 *
 *	N    N     N   NNNN NNNN	<-- decimal digits
 *	OP ModeA ModeB AAAA BBBB
 *
 *	Max. instruction: 82279997999 = 0x1 3245 222F
 *	Too bad it won't fit in a Long
 *
 */

/*
 * The redcode Op-code values
 */

#define		MOV	1
#define		ADD	2
#define		SUB	3
#define		JMP	4
#define		JMZ	5
#define		JMG	6
#define		DJZ	7
#define		CMP	8
#define		DAT	0

char *op_str[] =
{
	"dat",	/* 0 */
	"mov",	/* 1 */
	"add",	/* 2 */
	"sub",	/* 3 */
	"jmp",	/* 4 */
	"jmz",	/* 5 */
	"jmg",	/* 6 */
	"djz",	/* 7 */
	"cmp",	/* 8 */
	0
};
	
/*
 * Operand modes
 */

#define IMMEDIATE	0
#define DIRECT		1
#define INDIRECT	2

char mode_char[] = { '#', ' ', '@', 0};
'KerchDrivOrnCo'
echo extracting duh
cat <<'KerchDrivOrnCo' > duh
jmp 0		/do nothing, ever
'KerchDrivOrnCo'
echo extracting dwarf
cat <<'KerchDrivOrnCo' > dwarf
dat		-1
add	#5	-1
mov	#0	@-2
jmp	-2
'KerchDrivOrnCo'
echo extracting gemini
cat <<'KerchDrivOrnCo' > gemini
dat		0		/pointer to source address
dat		99		/pointer to destination address
mov	@-2	@-1		/copy source to destination
cmp	-3	#9		/if all 10 lines have been copied...
jmp	4			/...then leave the loop
add	#1	-5		/otherwise, increment the source address
add	#1	-5		/...and the destination address
jmp 	-5			/...and return to the loop
mov	#99	93		/restore the starting destination address
jmp	93			/jump to the new copy
'KerchDrivOrnCo'
echo extracting imp
cat <<'KerchDrivOrnCo' > imp
mov	0	1		/copy myself one instruction ahead
'KerchDrivOrnCo'
echo extracting makefile
cat <<'KerchDrivOrnCo' > makefile
CFLAGS = -O

corewar: corewar.o
	cc -o corewar corewar.o

corewar.o: corewar.c
'KerchDrivOrnCo'
echo extracting movtest
cat <<'KerchDrivOrnCo' > movtest
dat 0
dat 1
dat 2 
dat 3
dat -1		/indirect pointer back one
dat 1		/indirect pointer ahead 1
dat 0
mov -6 -7	/mov 1 to 0
mov #77 -6	/put 77 on top of 2
mov @-5 @-4	/mov the 3 to the nearest 0
dat 0		/illegal; terminates test
'KerchDrivOrnCo'

play@mcvax.UUCP (funhouse) (07/05/84)

As we got it, the source file corewar.c contains a bug causing
the first program to win whenever it is not a draw. The fix is
trivial: replace in the routine  execute()  the first return 1
by  return 2;

play@mcvax.UUCP (funhouse) (07/06/84)

Yesterday evening I posted one small bug fix to corewar.c.
In the meantime I fixed so many bugs that the diff listing
is longer than the original program source. If anybody is
interested in a working version of corewar he should mail me,
and I'll send our current version (and some battle programs).