/*
 * dworms.c  
 *
 * Copyright (c) 1994,1995 Ilpo Järvinen <ilpo.jarvinen@helsinki.fi>
 *
 * worm game for up to 3 players with special "moves".
 */


#include <bios.h>
#include <conio.h>
#include <stdlib.h>
#include <dos.h>
#include <graphics.h>

/*
 * Ok, here it is, my first "finished" C project, d-worms.
 *
 * The code is left *exactly* as it was when I finished it.
 * Only tabs and "formatting" is changed a bit because otherwise
 * it would be very hard to follow it.
 *
 * Obviously there weren't a single comment in the code ;) so
 * all of them were added afterwards. I try to explain a bit
 * what I have been doing, this is DOS/Turbo C stuff mostly,
 * so if you don't have any experience on those, there is probably
 * something you cannot understand.
 *
 * As far as I know Turbo C can now be freely downloaded from
 * internet...
 *
 * At some point the code is _really_ ugly. At the time I wrote
 * this I did have very little experience on C, some on x86 ASM
 * though, I had studied English 1 year or so, so my English could
 * have been better when I tried to teach myself how to code in C
 * from in-English-tutorials. :)
 * I had no idea about ands/ors (&/&&/|/||), so there aren't any
 * if operations combined, but everything is separated from other
 * ifs. It seems that for loops were missing too... ;)
 */


/* Key pressed/not pressed holder, a table would be enough but
 * I didn't know _anything_ at the time I made this, but I might
 * have had something "timer" stuff in my mind too, and in that
 * case struct would have been required */
struct keys{
	char status;
}k[0x81];

/* Saved (original) interrupt handlers, I don't know why I have hooked
 * 0x1b and 0x23 too, does it have something to do with correct
 * termination? */
void interrupt (*oldint23)(...);
void interrupt (*oldint1b)(...);
void interrupt (*oldint9)(...);

/* Macro which acknowledges a single key when it is read from KBD,
 * otherwise all consecutive reads would return same key and eventually
 * the buffer wouldn't accept any new keys */
#define ACKKEY  al = ah = inportb( 0x61 ); \
		al |= 0x80;                \
		outportb( 0x61, al );      \
		outportb( 0x61, ah );      \
		outportb( 0x20, 0x20 )

/* The mysterious termination interrupt handlers? */
void interrupt int1b(...) {
	outport(0x20, 0x20);
	setvect(0x9, oldint9);
	setvect(0x1b, oldint1b);
	setvect(0x23, oldint23);
};
void interrupt int23(...) {
	outport(0x20, 0x20);
	setvect(0x9, oldint9);
	setvect(0x1b, oldint1b);
	setvect(0x23, oldint23);
};

/* Keyboard interrupt, called when key is pressed/released.
 * It changes the state of the key in struct keys */
void interrupt int9(...){
	unsigned char c,al,ah;
	c = inportb( 0x60 );
	if(c >= 0x80){k[c-0x80].status=0;}
	if(c < 0x80){k[c].status=1;}
	ACKKEY;
	outport(0x20, 0x20);
};

/* int plys; //would be enough? ;) */
struct mn {
	int plys;
}p;

/* state of the single worm */
struct madot {
	int x,y,c,l,oc,d,spe,ene,oene,col;
}m[3];

/* When enter key (no-numpad) is pressed, game is paused */
void enter_pause(){
	setcolor(15);
	outtextxy(500,1,"*** Paused ***");
	while(k[14].status != 0)
		;
	while(k[14].status != 1)
		;
	setcolor(0);
	outtextxy(500,1,"*** Paused ***");
}

/* int -> char * converter, as always, better ways to do this... ;) */
char *getlives(int n){
	if (m[n].l == 1) return("1");
	if (m[n].l == 2) return("2");
	if (m[n].l == 3) return("3");
	if (m[n].l == 4) return("4");
	if (m[n].l == 5) return("5");
	if (m[n].l == 6) return("6");
	if (m[n].l == 7) return("7");
	if (m[n].l == 8) return("8");
	if (m[n].l == 9) return("9");
	if (m[n].l == 10) return("10");
	if (m[n].l == 11) return("11");
	if (m[n].l == 12) return("12");
	if (m[n].l == 13) return("13");
	if (m[n].l == 14) return("14");
	if (m[n].l == 15) return("15");
	if (m[n].l == 16) return("16");
	if (m[n].l == 17) return("17");
	if (m[n].l == 18) return("18");
	if (m[n].l == 19) return("19");
	if (m[n].l == 20) return("20");
	return(0);
}


/* Draws static stuff to screen, suchs as borders and player "names" */
void int_ply_scrn(){
	setcolor(15);
	outtextxy(1,1,"Ply 1:");
	if (p.plys > 1)
		outtextxy(200,1,"Ply 2:");
	if (p.plys > 2)
		outtextxy(400,1,"Ply 3:");
	setcolor(9);
	outtextxy(50,1,getlives(0));
	setcolor(12);
	if (p.plys > 1)
		outtextxy(250,1,getlives(1));
	setcolor(10);
	if (p.plys > 2)
		outtextxy(450,1,getlives(2));
	setcolor(15);
	line(1,20,getmaxx(),20);
	line(1,20,1,getmaxy());
	line(getmaxx(),20,getmaxx(),getmaxy());
	line(1,getmaxy(),getmaxx(),getmaxy());
}

/* The initial state of worms */
void int_ply_stat(){
	m[0].x = 620;
	m[0].y = 100;
	m[0].c = 7;
	m[0].col = 9;
	m[1].x = 20;
	m[1].y = 100;
	m[1].c = 3;
	m[1].col = 12;
	m[2].x = 320;
	m[2].y = 400;
	m[2].c = 1;
	m[2].col = 10;
}

/* Moves nth worm and updates it energy */
void chk_ply_stat(int n){
	if (m[n].c == 1) m[n].y--;
	if (m[n].c == 3) m[n].x++;
	if (m[n].c == 5) m[n].y++;
	if (m[n].c == 7) m[n].x--;
	if (m[n].c == 2) {m[n].y--;m[n].x++;}
	if (m[n].c == 4) {m[n].x++;m[n].y++;}
	if (m[n].c == 6) {m[n].y++;m[n].x--;}
	if (m[n].c == 8) {m[n].x--;m[n].y--;}
	if (m[n].ene < 250) m[n].ene++;
	if (m[n].spe == 1) if (m[n].ene < 10) m[n].spe = 0;
	if (m[n].spe == 2) if (m[n].ene < 3) m[n].spe = 0;
	if (m[n].spe == 1) m[n].ene = m[n].ene - 10;
	if (m[n].spe == 2) m[n].ene = m[n].ene - 3;
}


/* Checks nth worm for collisions, the z _probably_ tells something about the
 * state of the special "jump", because worm is hidden at that point and
 * can't collide with _another worm_ but border still limits your movements
 * though. */
int check_snake(int n, int z){
	int s = 0;
	if (m[n].spe != 1)
		do{
			if (m[s].spe != 1) if (s != n) if (m[n].x == m[s].x) if (m[n].y == m[s].y)
				m[n].d = 1;
			s++;
		}while(s < p.plys);

	if (m[n].spe != 1) if (!z) if (getpixel(m[n].x,m[n].y) != 0)
		m[n].d = 1;
	if (getpixel(m[n].x,m[n].y) == 15)
		m[n].d = 1;
	if (m[n].c == 2){
		if (m[n].spe != 1) if (getpixel(m[n].x - 1,m[n].y) != 0)
			if (getpixel(m[n].x,m[n].y + 1) != 0)
				m[n].d = 1;
	}
	if (m[n].c == 4){
		if (m[n].spe != 1) if (getpixel(m[n].x - 1,m[n].y) != 0)
			if (getpixel(m[n].x,m[n].y - 1) != 0)
				m[n].d = 1;
	}
	if (m[n].c == 6){
		if (m[n].spe != 1) if (getpixel(m[n].x + 1,m[n].y) != 0)
			if (getpixel(m[n].x,m[n].y - 1) != 0)
				m[n].d = 1;
	}
	if (m[n].c == 8){
		if (m[n].spe != 1) if (getpixel(m[n].x + 1,m[n].y) != 0)
			if (getpixel(m[n].x,m[n].y + 1) != 0)
				m[n].d = 1;
	}
	m[n].oc = m[n].c;

	return(m[n].d);
}

/* Draws nth worm energybar */
void print_energy(int n){
	int s;
	s = m[n].oene;
	do{
		if (s < m[n].ene) {
			setcolor(12);
			if (s > 25)
				setcolor(14);
			if (s > 100)
				setcolor(10);
			line((n * 200 + 2 + s / 5),13,(n * 200 + 2 + s / 5),18);
			s++;
		}
		if (s > m[n].ene) {
			setcolor(0);
			line((n * 200 + 2 + s / 5),13,(n * 200 + 2 + s / 5),18);
			s--;
		}
	}while(m[n].ene != s);

	setcolor(15);
	m[n].oene = m[n].ene;
}

/* Places the head of nth worm to appropriate location, doesn't move the
 * worm. */
void print_one_snake(int n){
	if (p.plys > n)
		putpixel(m[n].x,m[n].y,m[n].col);
}

/* Outputting of all worms, special move boost is not handled here */
void print_snake(){
	if (m[0].spe != 1)
		putpixel(m[0].x,m[0].y,9);
	if (p.plys > 1) if (m[1].spe != 1)
		putpixel(m[1].x,m[1].y,12);
	if (p.plys > 2) if (m[2].spe != 1)
		putpixel(m[2].x,m[2].y,10);
}


/* Has somebody died? */
int livesoff(){
	if (m[0].l == 0) return(1);
	if (m[1].l == 0) return(1);
	if (m[2].l == 0) return(1);

	return(0);
}

/* Added this function to tell something about the program */
void exitmsg(int e){
	cprintf( "Thank you for playing d-worms by Ilpo J„rvinen\n" );
	exit(e);
}

/* The MAIN */
void main(){
	/* Go to the graphics mode */
	int gdriver = DETECT, gmode, errorcode;
	errorcode = registerfarbgidriver(EGAVGA_driver_far);
	if (errorcode < 0)
	{
		cprintf("Graphics error: %s\n", grapherrormsg(errorcode));
		cprintf("Press any key to halt:");
		getch();
		exit(1);
	}

	initgraph(&gdriver, &gmode, "");
	errorcode = graphresult();
	if (errorcode != grOk)
	{
		cprintf("Graphics error: %s\n", grapherrormsg(errorcode));
		cprintf("Press any key to halt:");
		getch();
		exit(1);
	}

	/* This isn't pure C... :( */
	int c,ex = 0,speed = 10,die,lives = 20;
	char ch;
	int modifiers;

	/* You can probably guess what is done here... ;) */
	outtextxy(1,1,"How many players (1,2,3=default) ?");
	ch = getch();
	p.plys = 0;
	/* There are better ways to do this... ;) */
	if (ch == '1') p.plys = 1;
	if (ch == '2') p.plys = 2;
	if (ch == '3') p.plys = 3;
	if (!p.plys) p.plys = 3;

	outtextxy(1,20,"Speed (0-9,5=default) ?");
	ch = getch();
	speed = -1;
	if (ch == '0') speed = 0;
	if (ch == '1') speed = 2;
	if (ch == '2') speed = 4;
	if (ch == '3') speed = 6;
	if (ch == '4') speed = 8;
	if (ch == '5') speed = 10;
	if (ch == '6') speed = 14;
	if (ch == '7') speed = 16;
	if (ch == '8') speed = 18;
	if (ch == '9') speed = 20;
	if (speed == -1) speed = 10;

	/* Initial energybar, int s is not pure C... :( */
	int s = 0;
	do{
		m[s].l = lives;
		m[s].spe = 0;
		m[s].ene = 0;
		print_energy(s);
		s++;
	}while(s < 3);

	/* Keyboard sanity */
	s = 0;
	do{
		k[s].status = 0;
		s++;
	}while(s < 0x81);

	/* Hook interrupt vectors */
	oldint9 = getvect(0x9);
	setvect(0x9, int9);
	oldint1b = getvect(0x1b);
	setvect(0x1b, int1b);
	oldint23 = getvect(0x23);
	setvect(0x23, int23);

	/* ...and the actual game... :) */
	do{
		/* Cleanup, first time and also when someone dies */
		cleardevice();
		int_ply_scrn();
		int_ply_stat();
		s = 0;
		do{
			m[s].spe = 0;
			m[s].ene = 0;
			print_energy(s);
			s++;
		}while(s < 3);

		/* The game "loop" */
		do{
			/* Make everyone alive and no for specials
			 * If special key is pressed then spe will be
			 * set in key section... */
			die = 0;
			m[0].d = 0;
			m[1].d = 0;
			m[2].d = 0;
			m[0].spe = 0;
			m[1].spe = 0;
			m[2].spe = 0;

			s = 0;
			ex++;
			do{
				/* handle keys */
				s++;
				if(k[s-1].status==1){
					switch(s-1){
					case 1:	/* Esc, exit */
						setvect(0x9, oldint9);
						setvect(0x1b, oldint1b);
						setvect(0x23, oldint23);
						closegraph();
						exitmsg(0);
						break;
					/* Directional & special keys for
					 * all players */
					case 71:
						if (m[0].oc != 4) m[0].c = 8;
						break;

					case 73:
						if (m[0].oc != 6) m[0].c = 2;
						break;

					case 81:
						if (m[0].oc != 8) m[0].c = 4;
						break;

					case 79:
						if (m[0].oc != 2) m[0].c = 6;
						break;

					case 72:
						if (m[0].oc != 5) m[0].c = 1;
						break;

					case 75:
						if (m[0].oc != 3) m[0].c = 7;
						break;

					case 77:
						if (m[0].oc != 7) m[0].c = 3;
						break;

					case 80:
						if (m[0].oc != 1) m[0].c = 5;
						break;

					case 76:
						m[0].spe = 1;
						break;

					case 28:
						m[0].spe = 2;
						break;

					case 17:
						if (m[1].oc != 5) m[1].c = 1;
						break;

					case 32:
						if (m[1].oc != 7) m[1].c = 3;
						break;

					case 45:
						if (m[1].oc != 1) m[1].c = 5;
						break;

					case 30:
						if (m[1].oc != 3) m[1].c = 7;
						break;

					case 18:
						if (m[1].oc != 6) m[1].c = 2;
						break;

					case 46:
						if (m[1].oc != 8) m[1].c = 4;
						break;

					case 44:
						if (m[1].oc != 2) m[1].c = 6;
						break;

					case 16:
						if (m[1].oc != 4) m[1].c = 8;
						break;

					case 58:
						m[1].spe = 1;
						break;

					case 42:
						m[1].spe = 2;
						break;

					case 24:
						if (m[2].oc != 5) m[2].c = 1;
						break;

					case 39:
						if (m[2].oc != 7) m[2].c = 3;
						break;

					case 52:
						if (m[2].oc != 1) m[2].c = 5;
						break;

					case 37:
						if (m[2].oc != 3) m[2].c = 7;
						break;

					case 25:
						if (m[2].oc != 6) m[2].c = 2;
						break;

					case 53:
						if (m[2].oc != 8) m[2].c = 4;
						break;

					case 51:
						if (m[2].oc != 2) m[2].c = 6;
						break;

					case 23:
						if (m[2].oc != 4) m[2].c = 8;
						break;

					case 38:
						m[2].spe = 1;
						break;

					case 50:
						m[2].spe = 2;
						break;

					case 14:
						if(ex > 50)
							enter_pause();
						ex = 0;
						break;

					default:
						break;
					}
				}
			}while(s < 0x81);

			/* Wait some time, after that move and check the worms */
			delay(20-speed);
			chk_ply_stat(0);
			if (p.plys > 1) chk_ply_stat(1);
			if (p.plys > 2) chk_ply_stat(2);
			print_energy(0);
			if (p.plys > 1) print_energy(1);
			if (p.plys > 2) print_energy(2);
			die = die + check_snake(0,0);
			if (p.plys > 1) die = die + check_snake(1,0);
			if (p.plys > 2) die = die + check_snake(2,0);
			print_snake();
			die = die + check_snake(0,1);
			if (p.plys > 1) die = die + check_snake(1,1);
			if (p.plys > 2) die = die + check_snake(2,1);

			/* Special "boost" */
			s = 0;
			int n = 0;
			do{
				do{
					if (m[s].spe == 2){
						if (p.plys > s) chk_ply_stat(s);
						if (p.plys > s) die = die + check_snake(s,0);
						print_one_snake(s);
						if (p.plys > s) die = die + check_snake(s,1);
					}
				s++;
				}while(s < 3);
			n++;
			}while(n < 2);
		}while(!die);

		/* Update lives */
		if (m[0].d == 1) m[0].l--;
		if (m[1].d == 1) m[1].l--;
		if (m[2].d == 1) m[2].l--;

		/* Wait until space to acknowledge the death */
		while (k[57].status != 1)
			;
	}while(!livesoff());

	/* Terminate cleanly */
	closegraph();
	setvect(0x9, oldint9);
	setvect(0x1b, oldint1b);
	setvect(0x23, oldint23);
	exitmsg(0);
};

