/*
	H19 RALLY Game,		5/80	Steve Ward

	Works ONLY on Heathkit/Zenith H19/Z19 terminal
	 			(or H89 computer)

	Command format:

	A>rally [-rn] [-b] [mapname] <cr>

	where:	"n"  is an optional seed for the random number generator
		     (results in exactly the same minor track deviations each
		     time it is given with a particular track);
		     If "n" is omitted, then track deviations are totally
		     random for each session (but same for each run in any
		     single session.)

		"-b" is a debugging option doing Steve-knows-what.

		"mapname" specifies the map file to use for the track
		    (defaults to "rally.map").
*/


#include "bdscio.h"		/* Get std console parameters	*/

#define	CARY	16		/* Y position of car.		*/
#define	IBAD	0
#define	LSPEED	7		/* Line 25 label posns		*/
#define	LMILES	20
#define	MAXSPD	9
#define	SPDSCL	128
#define	TENTHS	10		/* Number of lines per mile.	*/
#define	TICMIN	1920		/* Number of tics per minute.	*/

char	Free[10000], *Freep;

char	CRTFrz, CRTChr;
int	Miles;
char	Pavement, Freeze, BFlag;
char	CurX, CurY, SignY;
int	CarX, CarDX;
char	RevFlg, AltFlg;
int	Speed, Tenths;
char	Image[CARY*80+80];
char	*ImPtr, *ImEnd;
int	Seed;
int	Ranno;
char	InBuf[BUFSIZ], SavChr;
int	SpTime[MAXSPD+1];

struct Road {
	struct Node *Next;
	char	active;
	char	Token;
	int	Windy;
	int	Curvy;
	int	Age;
	int	ToGo;
	char	Holes;
	char	X;
	char	dx;
	char	width; } Road1, Road2;

struct Sign {
	struct Node *Next;
	char key;
	char text[0]; };

struct Fork {
	struct Node *Next;
	char key;
	char *Branch; };

struct Dist {
	struct Node *Next;
	char key;
	char wid, curve, wind;
	int miles; };

union Node {
	struct Dist;
	struct Fork;
	struct Sign; } *Tag[128];


/*
	Write a character to the terminal, handling X-ON/X-OFF
	protocol and not going into a busy loop if the terminal
	isn't ready to send a character, but rather just returning
	in that case to let the caller do more crunching and try
	again later.
*/

putchar(c)
 {	char stat, ch;
	for(;;)
	 { if ((CIMASK & (stat = inp(CSTAT))) == (CAHI ? CIMASK : 0))
	    switch(ch = (0177 & inp(CDATA))) {
		case 'S'-64:	CRTFrz=1; break;
		case 'Q'-64:	CRTFrz=0; break;
		case 'C'-64:	puts("\033z"); exit();
		default:	CRTChr=ch; }
	   if (CRTFrz) continue;
	   if ((stat & COMASK) == (CAHI ? COMASK : 0))
		 { if (c) outp(CDATA,c); return; }
	 }
}


char *new(size)
 {	char *rr;
	rr = Freep; Freep += size; return rr; }

struct Dist *NRoad(widx, curv, windx, dist)
 {	struct Dist *rr;
	rr = new(sizeof *rr);
	rr->key = 'D';		rr->Next = 0;
	rr->miles = dist;	rr->wid = widx;
	rr->curve = curv;	rr->wind = windx;
	return rr; }

struct Sign *NSign(txt)
 char *txt;
 {	int leng; char *cc, *dd;
	struct Sign *ss;
	leng = sizeof *ss; leng++;
	for (cc=txt; *cc++; leng++);
	ss = new(leng);		ss->key = 'S';		ss->Next = 0;
	dd = &(ss->text); for (cc=txt; *dd++ = *cc++;);
	return ss; }

struct Fork *NFork(kk)
 {	struct Fork *ff;
	ff = new(sizeof *ff); ff->key = kk;
	ff->Next = 0;	ff->Branch = 0;
	return ff; }

PrNode(nn)
 struct Node *nn;
 {	printf("Node %x: %c -> %x \r\n", nn, nn->key, nn->Next); }

char rdc()
 {	char ch;
	if (ch = SavChr) { SavChr=0; return ch; };
	return (getc(InBuf)); }

char pkc()
 {	return (SavChr = rdc()); }

int rdn()
 {	int ans, ch;
	ans = 0;
	while (isdigit(pkc()))	ans = ans*10 + (rdc() - '0');
	return ans; }

struct Dist *LRoad()
 {	int w, c, iwid, dd;
	char ch;
	dd = rdn();
	w = -1; c = -1; iwid = 20;
	while (pkc() != '\n') switch(rdc())
	 {	case '~':	c++; continue;
		case 'W':	iwid = rdn(); continue;
		case '!':	w++; continue;
		default:	continue; }
	return NRoad(iwid, c, w, dd);
 }

struct Dist *Load(name)
 char *name;
 {	char ch, buf[100], *cc;
	struct Sign First, Ignore;
	struct Node *it, *last;
	puts("\033z");
	SavChr = 0;
	it = &First; First.key = '?';
	if (fopen(name, InBuf) == -1)
	 { printf("Can't read %s\r\n", name); exit(); }

	while ((ch = rdc()) != 014) putchar(ch);
	while ((ch = rdc()) != ('Z'-64))
	 { last = it;
	   switch(ch)
	    {	case '=':	Tag[rdc(InBuf)] = Freep; break;
		case '|':	while (rdc() != '\n'); break;
		case '"':	cc = buf;
				while (((ch = rdc()) != '"') &&
					(ch != '\n')) *cc++ = ch;
				*cc = 0;
				it->Next = NSign(buf); it = it->Next;
				break;
		case '#':	it->Next = LRoad(); it = it->Next;
				break;

		case ':':	it->Next = rdc();
		case '.':	it = &Ignore; break;

		case '>':	it->Next = NFork('R'); it = it->Next;
				it->Branch = rdc();
				break;
		case '<':	it->Next = NFork('L'); it = it->Next;
				it->Branch = rdc();
				break;

		case ' ':	case '\n':	case '\r':	case '\t':
		case '\f':	break;

		default:	puts("Illegal syntax: "); putchar(ch);
				while ((ch = rdc(InBuf)) != '\n') putchar(ch);
				puts("\n\r"); break; }}
	NSign("Unbound label");
	srand1("\033x1\033x5\033Y8   (type a character to start)");
	if (!Seed) Seed = rand();	/* Do this only if "-r" option given
					   without any argument.	*/
	bdos(1);
	return First.Next; }

Exec(rr)
struct Road *rr;
 {	int x, dir, tt, right, left;
	union Node *nn;
	nn = rr->Next;	rr->Next = nn->Next;
	x = rr->X; dir = -1;

	switch (nn->key) {
	 case 'S':	right = x+(rr->width); left = 78-right;
			x = x>left? 0:right+2;
			printf("\033Y%c%c\033G %s \033F",
				SignY++, x+32, &(nn->text));
			return;
	 case 'D':	rr->Age = 0;
			if (nn->wind != 255) rr->Windy = ~(-1 << nn->wind);
			if (nn->curve != 255) rr->Curvy = nn->curve;
			rr->ToGo = nn->miles;
			if (nn->wid != 255) rr->width = nn->wid; return;
	 case 'L':	dir = 1;
	 case 'R':	if (!Freeze) fork(nn->Branch, dir); return;
			 }
 }

MoveTo(x, y) { puts("\033Y"); putchar(y+32); putchar(x+32); }
SpeedL() { MoveTo(LSPEED, 24); putchar(Speed + '0'); }
label(val, posn)
 {	printf("\033Y8%c%d ", posn+32, val); }


getchar()
 {	char ch;
	while (!(ch = CRTChr)) putchar(0);
	CRTChr = 0; return ch; }

car(x)
 {	puts("\033Y"); putchar(CARY+31); putchar(x+32);
	puts(" "); }

roll()
 {	puts("\033H\033L");
	CurX=0; CurY=0;
	if ((ImPtr += 80) == ImEnd) ImPtr = Image;
	Pavement = ImPtr[CarX];
	setmem(ImPtr, 80, IBAD); }

road(x, width, rdno)
 {	char i, *cc;
	puts("\033Y ");
	putchar(32+x);
	if (!RevFlg) { puts("\033p"); RevFlg=1; }
	if (!AltFlg) { puts("\033F"); AltFlg=1; }
	cc = &(ImPtr[x]);
	for (i=width; i--;) { putchar('i'); *cc++ |= rdno; }}

/* Update a Road; returns 1 if finish line.	*/

UpRd(rr)
 struct Road *rr;
 {	int ddx, left, right, curve, act, rough; unsigned i;
	if (!(act = rr->active)) return 0;
	(rr->ToGo)--;
	while ((rr->ToGo) <= 0)
	 { if (i = (rr->Next))
	    {	if (i == '.') return 1;
		if (i == '*') { rr->active = 0; return 0; }
		if (i < 128)
		 { rr->Next = Tag[i];
		   if (BFlag) { puts("\033H\033G");
				putchar(i);
				puts("\033F"); }}
		Exec(rr); }
	   else { rr->active = 0; return 0; }}
	if (Freeze) rough=0;
	else rough=1;
	if (++(rr->Age) > 24)
		if (!(Pavement & (rr->Token)))
			{ rr->active = 0; Freeze = 0; return 0; }
	ddx = rr->dx;
	left = rr->X; right = left+(rr->width);
	if (left < 1) ddx = rough;
	else if (right > 79) ddx = -rough;
	else if ((!Freeze) && (!((rr->Windy) & Ranno)))
	 { curve = rr->Curvy;
	   if (Ranno & 64) ddx += 1;
	   if (Ranno & 1024) ddx -= 1;
	   if ((ddx > curve) || (ddx < (-curve))) ddx = rr->dx; }
	rr->dx = ddx;
	rr->X += ddx;
	road(rr->X, rr->width, rr->Token);
	return 0; }

/* returns 2 iff end, 0 iff crash, 1 else.	*/

int Update()
 {	int Eor;
	SignY=32;
	roll();
	Eor = UpRd(&Road1) | UpRd(&Road2);
	if (Eor) return 2;
	Delay(SpTime[Speed]);
	if ((CarX += CarDX) < 0) { CarX=0; CarDX=0; }
	else if (CarX > 79) { CarX=79; CarDX=0; }
	car(CarX);
	if (Pavement == IBAD) return 0;
	return 1; }

Delay(n)
 {	char ch;
	n |= 1;
	while (n--)
	 {	putchar(0);
		if (CRTChr)
		 { ch = getchar();
		   switch(ch) {
			case '4':	CarDX--; break;
			case '6':	CarDX++; break;
			case '5':
			case '2':	if (Speed>1) Speed--; Speed--;
			case '8':	if (++Speed > MAXSPD) Speed=MAXSPD;
					SpeedL();
					 }}}}

fork(where, dir)
 struct Node *where;
 {	struct Road *r1, *r2, newx;
	r1 = &Road1; r2 = &Road2;
	if (!(r1->active)) { r1 = &Road2; r2 = &Road1; }
	r1->dx = dir; r2->dx = -dir;
	r2->X = r1->X;
	r2->active = 1; r2->Age = 0;
	r2->Windy = r1->Windy;
	r2->Curvy = r1->Curvy;
	r2->width = r1->width;
	r2->Next = where; r2->ToGo = -1;
	Freeze = 1; }

main(argc, argv)
 char **argv;
 {	int i, j, Mins, Hours, Tics;
	char *arg, *MapNam;
	struct Node *First;
	Seed = 12345;
	j = SPDSCL*MAXSPD;
	for (i=1; i<= MAXSPD; i++) SpTime[i] = j/i - SPDSCL;
	SpTime[MAXSPD] = 0;
	BFlag = 0; MapNam = "RALLY.MAP";
	for (i=1; i<argc; i++)
	 { if (*(arg = argv[i]) == '-') switch(*++arg) {
		case 'R':	Seed = atoi(++arg);
				continue;
		case 'B':	BFlag++; continue; }
	   else MapNam = arg; }
	CRTFrz = CRTChr = 0;
	Freep = Free;
	for (i=0; i<128; i++) Tag[i] = 0;
	First = Load(MapNam);
top:	srand(Seed); Ranno = rand();
	Freeze=0;
	puts("\033H\033J\033G\033x5\033x1\033Y8 \033K\033p");
	puts(" Speed 00     Miles 0      ");
	puts("\033Y8X Rally 1.2   Steve Ward ");
	setmem(Image,CARY*80,255);
	ImPtr = Image; ImEnd = &(Image[CARY*80]);
	Road1.Token=1; Road2.Token=2;
	Road1.active = 1; Road1.Age = 0; Road1.ToGo = 0;
	Road1.X = 20; Road1.dx = 0;
	Road2.active = 0;
	CarDX = 0;
	RevFlg = 0; AltFlg = 0;
	Road1.ToGo = 0; Road1.Next= First;
	CarX = Road1.X + (Road1.width >> 1);
	Speed=3;
	Update();
	for (i=0; i<CARY; i++) { Update(); SpeedL(); }
	Miles=0; Tenths=0;
	label(Miles, LMILES); Mins=0; Hours=0; Tics=0;
loop:	Ranno = rand();
	Tics += SpTime[Speed]+SPDSCL;
	while (Tics >= TICMIN) { Tics -= TICMIN; Mins++; };
	while (Mins >= 60) { Mins -= 60; Hours++; };
	if (!(i = Update()))
	 { puts("\033H CRASHED AFTER ");
done:	   printf("\033G %d Hours, %d Minutes\007!!! !!! !!!  ", Hours, Mins);
	   delay(10000);
	   goto top; }
	else if (i == 2)
	 { puts("\033H YOU MADE IT IN ");
	   goto done; }
	if (++Tenths >= 10)
	 { label(++Miles, LMILES); Tenths=0; }
	goto loop;
 }
