/* $Filename:		WormWars/Source/WormWars.c $
 * $VER:		3.1 $
 * $Date:		16 Apr 1996 $
 * $Description:	Source code for Worm Wars 3.1 for Amiga $
 *
 * Compiled with: "sc wormwars.c link memsize=t code=far"
 * © Copyright 1993-1996 James R. Jacobs of Amigan Software.
 * Freely distributable.
 *
 * Release version.
 *        _
 *       //	-=AMIGA=-
 *      //
 * _   //
 * \\ //
 *  \X/

CODE SETUP ---------------------------------------------------------------

#includes */

#include <devices/gameport.h>
#include <devices/inputevent.h>
#include <dos/dos.h>
#include <exec/exec.h>
#include <exec/io.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <graphics/displayinfo.h>
#include <graphics/gfx.h>
#include <libraries/gadtools.h>

#include <ctype.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include ":PD/OctaMED/Programmers/libproto.h"
#include "WormWars.h"

// prototypes

void anykey(void);
void blitmode(BYTE state);
void bombblast(UBYTE type, BYTE player, BYTE centrex, BYTE centrey);
void celebrate(void);
void cleanexit(void);
void clearkybd(void);
void death(void);
void draw(BYTE x, BYTE y, UBYTE image);
void fastloop(void);
void fieldedit(void);
void highlight(BYTE player, BYTE line, BOOL status);
void muzak(BOOL flag);
void newboard(void);
BOOL openboard(BYTE level);
void orbexplosion(BYTE whichorb);
void orbloop(BYTE whichorb);
void orbsplit(BYTE whichorb);
int  partner(WORD teleport);
void putletter(BYTE player);
BOOL saveboard(BYTE level);
void say(STRPTR sentence);
void sendreadrequest(void);
void setpointer(UBYTE pointer);
BYTE sign(BYTE value);
void slowloop(void);
void squareblast(UBYTE type, BYTE player, BYTE x, BYTE y);
void stat(BYTE player, BYTE line);
void turnworm(BYTE player, BYTE deltax, BYTE deltay);
void updateboard(void);
BYTE valid(BYTE x, BYTE y);
void wormbullet(BYTE player);
void wormloop(BYTE player);
WORD xpixeltosquare(WORD x);
WORD ypixeltosquare(WORD y);
BYTE xwrap(BYTE x);
BYTE ywrap(BYTE y);

// globals

BYTE	a;
UBYTE	board[FIELDX + 1][FIELDY + 1];
UBYTE	field[FIELDX + 1][FIELDY + 1];
BOOL	letters[4][LETTERS];
BOOL	filenamed;
BYTE	gadgets = FALSE;
BYTE	ice;
BYTE	joy;
BYTE	level = 1;
BOOL	music = FALSE;
UWORD	r;
BYTE	sound = FALSE;
BYTE	startx = FIELDX / 2;
BYTE	starty = FIELDY / 2;
BOOL	tune = FALSE;
BOOL	leftdown = FALSE;
BOOL	rightdown = FALSE;
FILE*	JFilePtr = NULL;
BYTE	lo = 0, hi = 3;
BOOL	timer = FALSE, othertimer = FALSE;
BYTE	trainer;
BYTE	traintype;

	struct	GadToolsBase*	GadToolsBase		= NULL;
	struct	GfxBase*	GfxBase			= NULL;
	struct	IntuitionBase*	IntuitionBase		= NULL;
	struct	MEDPlayerBase*	MEDPlayerBase		= NULL;
	struct	Library*	TimerBase		= NULL;

	BYTE			JController;
	struct	timeval*	JCurrentValPtr		= NULL;
	struct	Gadget*		JGadgetPtr[4]		= {NULL, NULL, NULL, NULL};
	struct	InputEvent	JGameEvent;
	struct	Message*	JJoyMsgPtr		= NULL;
	BYTE			JJoyOpen		= -1;
	struct	MsgPort*  	JJoyPortPtr		= NULL;
	struct	IOStdReq*	JJoyRqPtr		= NULL;
	struct	Gadget*		JGListPtr		= NULL;
	struct	IntuiMessage*	JIMsgPtr		= NULL;
	struct	Gadget*		JContextPtr		= NULL;
	struct	Menu*		JMenuPtr		= NULL;
	struct	IntuiMessage*	JMsgPtr			= NULL;
	struct	timeval*	JPausedValPtr		= NULL;
	struct	Screen*		JScreenPtr		= NULL;
	ULONG			JSignal;
	ULONG			JSignalMask;
	struct	MMD0*		JSongPtr		= NULL;
	struct	timeval*	JStartValPtr		= NULL;
	BYTE			JTimerOpen		= -1;
	struct	MsgPort*	JTimerPortPtr		= NULL;
	struct	timerequest*	JTimerRqPtr		= NULL;
	APTR			JVisualInfoPtr		= NULL;
	struct	Gadget*		JWhichGadgetPtr		= NULL;
	struct	Window*		JWindowPtr		= NULL;
	WORD			JResult;
extern	struct	SysBase**	SysBase;

int main(void)
{	BOOL done[4];
	ULONG elapsed;
	BYTE i;
	UWORD minutes, seconds;
	BYTE timedisplay[6];
	ULONG class;
	UWORD code, qual;
	ULONG tempsecs, tempmicro;
	BYTE time, line;
	BYTE player, whichobj, whichorb, whichprot;
	UBYTE segment, chosen;

	// exit redirection

	JResult = atexit(cleanexit);
	if (JResult)
		exit(EXIT_FAILURE);

	/* OPEN ----------------------------------------------------------

	libraries */

	IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 37L);
	if (!IntuitionBase)
		exit(EXIT_FAILURE);
	GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 0L);
	if (!GfxBase)
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open graphics.library!\0", 24);
		exit(EXIT_FAILURE);
	}
	GadToolsBase = (struct GadToolsBase *) OpenLibrary("gadtools.library", 37L);
	if (!GadToolsBase)
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open gadtools.library V37+!\0", 24);
		exit(EXIT_FAILURE);
	}
	MEDPlayerBase = (struct MEDPlayerBase *) OpenLibrary("medplayer.library", 0L);
	if (!MEDPlayerBase)
		sound = NOLIBRARY;
	else if (GetPlayer(0))
		sound = NOCHANNELS;
	else
		sound = TRUE;

	// Open timer.device

	if (!(JStartValPtr = (struct timeval *) AllocMem(sizeof(struct timeval), MEMF_PUBLIC | MEMF_CLEAR)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't allocate timer value structure!\0", 24);
		exit(EXIT_FAILURE);
	}
	else if (!(JCurrentValPtr = (struct timeval *) AllocMem(sizeof(struct timeval), MEMF_PUBLIC | MEMF_CLEAR)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't allocate timer value structure!\0", 24);
		exit(EXIT_FAILURE);
	}
	else if (!(JPausedValPtr = (struct timeval *) AllocMem(sizeof(struct timeval), MEMF_PUBLIC | MEMF_CLEAR)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't allocate timer value structure!\0", 24);
		exit(EXIT_FAILURE);
	}
	else if (!(JTimerPortPtr = (struct MsgPort *) CreatePort(NULL, 0)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't allocate timer message port!\0", 24);
		exit(EXIT_FAILURE);
	}
	else if (!(JTimerRqPtr = (struct timerequest *) CreateExtIO(JTimerPortPtr, sizeof(struct timerequest))))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create timer I/O request!\0", 24);
		exit(EXIT_FAILURE);
	}
	else if (JTimerOpen = OpenDevice(TIMERNAME, UNIT_VBLANK, JTimerRqPtr, 0))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open timer.device!\0", 24);
		exit(EXIT_FAILURE);
	}
	TimerBase = (struct Library *) JTimerRqPtr->tr_node.io_Device;

	// Open gameport.device

	if (!(JJoyPortPtr = (struct MsgPort *) CreatePort(NULL, 0)))
		joy = NOPORT;
	else if (!(JJoyRqPtr = (struct IOStdReq *) CreateExtIO(JJoyPortPtr, sizeof(struct IOStdReq))))
		joy = NOREQUEST;
	else if (JJoyOpen = OpenDevice("gameport.device", 1, JJoyRqPtr, 0))
		joy = NODEVICE;
	else
	{	Forbid();
		JJoyRqPtr->io_Command		= GPD_ASKCTYPE;
		JJoyRqPtr->io_Data		= (APTR) &JController;
		JJoyRqPtr->io_Length		= 1;
		DoIO(JJoyRqPtr);
		if (JController != GPCT_NOCONTROLLER)
			joy = NOAVAIL;
		else
		{	JController		= GPCT_ABSJOYSTICK;
			JJoyRqPtr->io_Command	= GPD_SETCTYPE;
			JJoyRqPtr->io_Length	= 1;
			JJoyRqPtr->io_Data	= (APTR) &JController;
			DoIO(JJoyRqPtr);
			JJoyRqPtr->io_Command	= GPD_SETTRIGGER;
			JJoyRqPtr->io_Length	= sizeof(struct GamePortTrigger);
			JJoyRqPtr->io_Data	= (APTR) &JTrigger;
			DoIO(JJoyRqPtr);
			joy = TRUE;
		}
		Permit();
	}
	if (joy == TRUE)
		sendreadrequest();

	/* PREPARE DISPLAY -----------------------------------------------

	screen */

	if (!(JScreenPtr = (struct Screen *) OpenScreenTags(NULL,
	SA_Width, 640, SA_Height, 256,
	SA_Depth, 4,
	SA_DisplayID, HIRES_KEY,
	SA_Title, "Worm Wars 3.1",
	SA_Font, &Topaz8,
	SA_Pens, JPens,
	TAG_DONE)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open screen!\0", 24);
		exit(EXIT_FAILURE);
	}
	LoadRGB4(&JScreenPtr->ViewPort, JColours, 20);

	if (!(JVisualInfoPtr = (APTR) GetVisualInfo(JScreenPtr, TAG_DONE)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't get GadTools visual info!\0", 24);
		exit(EXIT_FAILURE);
	}
	for (player = 0; player <= 3; player++)
		JNewGadget[player].ng_VisualInfo = JVisualInfoPtr;

	// menus

	JMenuPtr = (struct Menu *) CreateMenus(JNewMenu, TAG_DONE);
	if (JMenuPtr == NULL)
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create menus!\0", 24);
		exit(EXIT_FAILURE);
	}
	if (!(LayoutMenus(JMenuPtr, JVisualInfoPtr, TAG_DONE)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't lay out menus!\0", 24);
		exit(EXIT_FAILURE);
	}

	// window

	if (!(JWindowPtr = (struct Window *) OpenWindowTags(NULL,
	WA_Top, 11,
	WA_Width, 640, WA_Height, 244,
	WA_IDCMP, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW,
	WA_Gadgets, NULL,
	WA_CustomScreen, JScreenPtr,
	WA_Borderless, TRUE,
	WA_Activate, TRUE,
	WA_RptQueue, 0,
	TAG_DONE)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't open window!\0", 24);
		exit(EXIT_FAILURE);
	}

	JSignalMask = 1L << JWindowPtr->UserPort->mp_SigBit;
	worm[0].statx	= 0;
	worm[0].staty	= 0;
	worm[0].colour	= GREEN;
	worm[0].control	= NONE;
	worm[1].statx	= 1;
	worm[1].staty	= 0;
	worm[1].colour	= RED;
	worm[1].control	= HUMAN;
	worm[2].statx	= 0;
	worm[2].staty	= 1;
	worm[2].colour	= BLUE;
	worm[2].control	= NONE;
	worm[3].statx	= 1;
	worm[3].staty	= 1;
	worm[3].colour	= YELLOW;
	worm[3].control = AMIGA;
	timedisplay[2] = ':';
	timedisplay[5] = '\0';

	if (!(JContextPtr = (struct Gadget *) CreateContext(&JGListPtr)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create GadTools context!\0", 24);
		exit(EXIT_FAILURE);
	}
	for (player = 0; player <= 3; player++)
		JGadgetPtr[player] = JContextPtr = (struct Gadget *) CreateGadget(CYCLE_KIND, JContextPtr, &JNewGadget[player], GTCY_Labels, JGadgetOptions, GT_Underscore, '_', GTCY_Active, worm[player].control, TAG_DONE);
	if (!JContextPtr)
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create gadgets!\0", 24);
		exit(EXIT_FAILURE);
	}
	gadgets = TRUE;
	if (sound == TRUE && (JSongPtr = (struct MMD0 *) LoadModule("WormWars.MED")))
	{	PlayModule(JSongPtr);
		tune = TRUE;
		muzak(TRUE);
	}

	while (1)
	{	Forbid();
		JWindowPtr->Flags = WFLG_BORDERLESS | WFLG_ACTIVATE;
		Permit();
		SetMenuStrip(JWindowPtr, JMenuPtr);
		SetAPen(JWindowPtr->RPort, BLACK);
		RectFill(JWindowPtr->RPort, STARTXPIXEL, STARTYPIXEL, ENDXPIXEL, ENDYPIXEL);

		AddGList(JWindowPtr, JGListPtr, 0, -1, NULL);
		RefreshGList(JGListPtr, JWindowPtr, NULL, -1);
		GT_RefreshWindow(JWindowPtr, NULL);
		for (player = 0; player <= 3; player++)
			GT_SetGadgetAttrs(JGadgetPtr[player], JWindowPtr, NULL, GA_Disabled, FALSE, TAG_DONE);
		gadgets = VERYTRUE;

		DrawImage(JWindowPtr->RPort, &JGrafix, 188, 112);

		for (player = 0; player <= 3; player++)
		{	worm[player].score = 0;
			worm[player].oldscore = 0;
			worm[player].speed = NORMAL;
			worm[player].lives = 0;
		}
		if (joy == NOPORT)
		{	say("Can't allocate joystick message port!");
			anykey();
			joy = FALSE;
		}
		else if (joy == NOREQUEST)
		{	say("Can't create joystick I/O request!");
			anykey();
			joy = FALSE;
		}
		else if (joy == NODEVICE)
		{	say("Can't open gameport.device!");
			anykey();
			joy = FALSE;
		}
		else if (joy == NOAVAIL)
		{	say("Gameport already in use!");
			anykey();
			joy = FALSE;
		}
		if (sound == NOLIBRARY)
		{	say("Can't open MEDPlayer.library!");
			anykey();
			sound = FALSE;
		}
		else if (sound == NOCHANNELS)
		{	say("Can't allocate audio hardware!");
			anykey();
			sound = FALSE;
		}

		// worm animations

		done[0] = FALSE;
		worm[0].x = 10;
		worm[0].y = 11;
		done[1] = FALSE;
		worm[1].x = 19;
		worm[1].y = 11;
		done[2] = FALSE;
		worm[2].x = 25;
		worm[2].y = 15;
		done[3] = FALSE;
		worm[3].x = 32;
		worm[3].y = 15;
		whichobj = -1;
		segment = 0;
		time = -1;
		a = GAMEOVER;
		clearkybd();

		do
		{	JTimerRqPtr->tr_node.io_Command	= TR_ADDREQUEST;
			JTimerRqPtr->tr_time.tv_secs	= 0;
			JTimerRqPtr->tr_time.tv_micro	= ANIMDELAY;
			SendIO(JTimerRqPtr);
			timer = TRUE;

			// w     w  oooo  rrrrr  mm   mm
			// w     w o    o r    r m m m m
			// w  w  w o    o rrrrr  m  m  m
			// w w w w o    o r r    m     m
			// ww   ww  oooo  r  rrr m     m

			if (done[0] == FALSE || done[1] == FALSE || done[2] == FALSE || done[3] == FALSE)
			{	for (player = 0; player <= 3; player++)
					if (done[player] == FALSE && anim[player][segment].x != SENTINEL)
					{	draw(worm[player].x, worm[player].y, GREENTAIL + player);
						worm[player].x += anim[player][segment].x;
						worm[player].y += anim[player][segment].y;
						draw(worm[player].x, worm[player].y, eachworm[player][0][anim[player][segment].x + 1 + (anim[player][segment].y + 1) * 3]);
					}
					else
						done[player] = TRUE;
				segment++;
			}

			// Animate the object descriptions.

			if (++time == 0 || time > VIEWFRAMES)
			{	time = 0;
				chosen = (rand() % VIEWS);
				if (++whichobj > DESCRIPTIONS || whichobj == 0)
					whichobj = 0;
				say(objectdesc[whichobj]);
				draw(15, 4, whichobj);
				draw(34, 4, whichobj);
			}

			for (line = 0; line <= ROWS; line++)
				for (i = 0; i <= COLUMNS; i++)
					if (view[chosen][time][line * 8 + i])
						draw(21 + i, line + 2, whichobj);
					else
					{	blitmode(BLACK);
						draw(21 + i, line + 2, ANYTHING);
						blitmode(NORMAL);
					}

			while (JIMsgPtr = (struct IntuiMessage *) GT_GetIMsg(JWindowPtr->UserPort))
			{	JWhichGadgetPtr = (struct Gadget *) JIMsgPtr->IAddress;
				class = JIMsgPtr->Class;
				code = JIMsgPtr->Code;
				qual = JIMsgPtr->Qualifier;
				GT_ReplyIMsg(JIMsgPtr);
				switch (class)
				{
				case IDCMP_RAWKEY:
					switch (code)
					{
					case 0x37:
						if (tune == TRUE)
						{	if (music == TRUE)
								muzak(FALSE);
							else
							{	ContModule(JSongPtr);
								muzak(TRUE);
						}	}
						break;
					case SPACEBAR:
						a = FIELDEDIT;
						break;
					case 0x01:
					case 0x1D:
					case 0x50:
						if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
						{	if (--worm[0].control < 0)
								worm[0].control = 2;
						}
						else
						{	if (++worm[0].control > 2)
								worm[0].control = 0;
						}
						GT_SetGadgetAttrs(JGadgetPtr[0], JWindowPtr, NULL, GTCY_Active, worm[0].control, TAG_DONE);
						break;
					case 0x02:
					case 0x1E:
					case 0x51:
						if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
						{	if (--worm[1].control < 0)
								worm[1].control = 2;
						}
						else
						{	if (++worm[1].control > 2)
								worm[1].control = 0;
						}
						GT_SetGadgetAttrs(JGadgetPtr[1], JWindowPtr, NULL, GTCY_Active, worm[1].control, TAG_DONE);
						break;
					case 0x03:
					case 0x1F:
					case 0x52:
						if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
						{	worm[2].control--;
							if (worm[2].control < NONE)
								worm[2].control = AMIGA;
							else if (joy == FALSE && worm[2].control == HUMAN)
								worm[2].control = NONE;
						}
						else
						{	worm[2].control++;
							if (worm[2].control > AMIGA)
								worm[2].control = NONE;
							else if (joy == FALSE && worm[2].control == HUMAN)
								worm[2].control = AMIGA;
						}
						GT_SetGadgetAttrs(JGadgetPtr[2], JWindowPtr, NULL, GTCY_Active, worm[2].control, TAG_DONE);
						break;
					case 0x04:
					case 0x2D:
					case 0x53:
						if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
						{	if (--worm[3].control < 0)
								worm[3].control = 2;
						}
						else
						{	if (++worm[3].control > 2)
								worm[3].control = 0;
						}
						GT_SetGadgetAttrs(JGadgetPtr[3], JWindowPtr, NULL, GTCY_Active, worm[3].control, TAG_DONE);
						break;
					case 0x43:
						a = PLAYGAME;
						break;
					case 0x44:
						a = PLAYGAME;
						break;
					case 0x45:
						exit(EXIT_SUCCESS);
						break;
					default:
						break;
					}
					break;
				case IDCMP_MENUPICK:
					if (code != MENUNULL)
					{	switch (MENUNUM(code))
						{
						case 0:
							// project, quit
							exit(EXIT_SUCCESS);
							break;
						case 1:
							// module, field editor
							a = FIELDEDIT;
							break;
						default:
							break;
					}	}
					break;
				case IDCMP_MOUSEBUTTONS:
					a = PLAYGAME;
					break;
				case IDCMP_REFRESHWINDOW:
					GT_BeginRefresh(JWindowPtr);
					GT_EndRefresh(JWindowPtr, TRUE);
					break;
				case IDCMP_GADGETUP:
					for (player = 0; player <= 3; player++)
						if (JWhichGadgetPtr == JGadgetPtr[player])
							if (joy == FALSE && player == 2 && code == HUMAN)
							{	if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
									worm[2].control = NONE;
								else
									worm[2].control = AMIGA;
								GT_SetGadgetAttrs(JGadgetPtr[2], JWindowPtr, NULL, GTCY_Active, worm[2].control, TAG_DONE);
							}
							else
								worm[player].control = code;
					break;
				default:
					break;
			}	}
			if (joy == TRUE && (GetMsg(JJoyPortPtr)))
			{	if (JGameEvent.ie_Code == IECODE_LBUTTON)
					a = PLAYGAME;
				sendreadrequest();
			}
			if (a == PLAYGAME && worm[0].control == NONE && worm[1].control == NONE && worm[2].control == NONE && worm[3].control == NONE)
			{	say("No worms active!");
				anykey();
				a = GAMEOVER;
			}
			WaitIO(JTimerRqPtr);
			timer = FALSE;
		} while (a == GAMEOVER);

		for (player = 0; player <= 3; player++)
			GT_SetGadgetAttrs(JGadgetPtr[player], JWindowPtr, NULL, GA_Disabled, TRUE, TAG_DONE);
		if (RemoveGList(JWindowPtr, JGListPtr, -1) < 0)
		{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't remove gadgets!\0", 24);
			exit(EXIT_FAILURE);
		}
		gadgets = TRUE;
		for (player = 0; player <= 3; player++)
		{	blitmode(BLACK);
			for (i = 0; i <= LETTERS - 1; i++)
			{	letters[player][i] = FALSE;
				draw(i - 18 + (FIELDX + 28) * worm[player].statx, (FIELDY / 2) - 2 + worm[player].staty * 2, ANYTHING);
			}
			blitmode(NORMAL);
		}

		// draw field

		if (a == FIELDEDIT)
			fieldedit();

		ClearMenuStrip(JMenuPtr);
		FreeMenus(JMenuPtr);
		JNewMenu[1].nm_Flags = NM_ITEMDISABLED;
		JNewMenu[2].nm_Flags = NM_ITEMDISABLED;
		JNewMenu[3].nm_Flags = NM_ITEMDISABLED;
		JNewMenu[7].nm_Flags = NM_ITEMDISABLED;
		JNewMenu[8].nm_Flags = NULL;
		JMenuPtr = (struct Menu *) CreateMenus(JNewMenu, TAG_DONE);
		if (JMenuPtr == NULL)
		{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create menus!\0", 24);
			exit(EXIT_FAILURE);
		}
		if (!(LayoutMenus(JMenuPtr, JVisualInfoPtr, TAG_DONE)))
		{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't lay out menus!\0", 24);
			exit(EXIT_FAILURE);
		}
		SetMenuStrip(JWindowPtr, JMenuPtr);

		if (a == PLAYGAME)
		{	if (worm[0].control != NONE)
				lo = 0;
			else if (worm[1].control != NONE)
				lo = 1;
			else if (worm[2].control != NONE)
				lo = 2;
			else
				lo = 3;
			if (worm[3].control != NONE)
				hi = 3;
			else if (worm[2].control != NONE)
				hi = 2;
			else if (worm[1].control != NONE)
				hi = 1;
			else
				hi = 0;

			for (player = lo; player <= hi; player++)
				if (worm[player].control != NONE)
				{	worm[player].x		= startx;
					worm[player].y		= starty;
					switch (player)
					{	case 0:
							worm[0].deltax = -1;
							worm[0].deltay = 0;
							break;
						case 1:
							worm[1].deltax = 1;
							worm[1].deltay = 0;
							break;
						case 2:
							worm[2].deltax = 0;
							worm[2].deltay = -1;
							break;
						case 3:
							worm[3].deltax = 0;
							worm[3].deltay = 1;
							break;
						default:
							break;
					}
					worm[player].oldx		= worm[player].x;
					worm[player].oldy		= worm[player].y;
					worm[player].alive		= TRUE;
					worm[player].lives		= 50;
					worm[player].moved		= FALSE;
					worm[player].armour		= 0;
					worm[player].tongue		= 0;
					worm[player].eater		= 0;
					worm[player].nitro		= FALSE;
					worm[player].mode			= NULL;
					worm[player].power		= 0;
					worm[player].diagonal		= FALSE;
					worm[player].bias		= 0;
					worm[player].multi		= 1;
					worm[player].ice		= 0;
					worm[player].victor		= -1;
					worm[player].ammo		= 0;
					worm[player].causewait		= (UWORD) ~0;	// most future time possible
					worm[player].last		= GREENTAIL + player;
					for (whichprot = 0; whichprot <= PROTECTORS; whichprot++)
						protector[player][whichprot].alive = FALSE;
				}
			for (player = 0; player <= 3; player++)
			{	JImage.ImageData = JImageData[BONUS];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,   4 + worm[player].staty * 136);
				JImage.ImageData = JImageData[LIFE];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,  12 + worm[player].staty * 136);
				JImage.ImageData = JImageData[BIAS];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,  20 + worm[player].staty * 136);
				JImage.ImageData = JImageData[AMMO];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,  36 + worm[player].staty * 136);
				JImage.ImageData = JImageData[POWER];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,  44 + worm[player].staty * 136);
				JImage.ImageData = JImageData[ARMOUR];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,  60 + worm[player].staty * 136);
				JImage.ImageData = JImageData[TONGUE];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,  68 + worm[player].staty * 136);
				JImage.ImageData = JImageData[EATER];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,  76 + worm[player].staty * 136);
				JImage.ImageData = JImageData[NITRO];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx,  92 + worm[player].staty * 136);
				JImage.ImageData = JImageData[DIAGONAL];
				DrawImage(JWindowPtr->RPort, &JImage, 504 * worm[player].statx, 100 + worm[player].staty * 136);
				for (i = 0; i <= 12; i++)
				{	highlight(player, i, FALSE);
					stat(player, i);
			}	}

			// D3: kill opponents

			for (i = 0; i <= KILLERS; i++)
			{	killer[i].alive = FALSE;
				killerfire[i].alive = FALSE;
			}
			for (whichorb = 0; whichorb <= ORBS; whichorb++)
			{	orb[whichorb].alive = FALSE;
				for (i = 0; i <= 7; i++)
					frag[whichorb][i].alive = FALSE;
			}
			level = 1;
			blitmode(BLACK);
			for (player = 0; player <= 3; player++)
				draw(-2 + ((FIELDX + 4) * worm[player].statx), (FIELDY / 2 - 2) + (worm[player].staty * 2), ANYTHING);
			blitmode(NORMAL);

			if (openboard(1))
				updateboard();
			else
				newboard();
			r = 0;
			elapsed = 0;
			trainer = 0;
			teleport[2].alive = FALSE;
			teleport[3].alive = FALSE;
			ice = -1;
			putletter(rand() % 4);	// this is another reason
						// why we must clear the
						// entire letters[] array
			for (player = 0; player <= 3; player++)
				missile[player].alive = FALSE;
			say("");
			for (player = 0; player <= 3; player++)
				for (i = 0; i <= 12; i++)
				{	highlight(player, i, FALSE);
					stat(player, i);
				}
			clearkybd();
			Forbid();
			JWindowPtr->Flags = WFLG_BORDERLESS | WFLG_ACTIVATE | WFLG_RMBTRAP;
			Permit();
			GetSysTime(JStartValPtr);
		}

		// MAIN GAME LOOP ----------------------------------

		while (a == PLAYGAME)
		{	GetSysTime(JCurrentValPtr);
			tempsecs  = JCurrentValPtr->tv_secs;
			tempmicro = JCurrentValPtr->tv_micro;
			SubTime(JCurrentValPtr, JStartValPtr);
			minutes = (JCurrentValPtr->tv_secs) / 60;
			seconds = (JCurrentValPtr->tv_secs) % 60;
			JCurrentValPtr->tv_secs  = tempsecs;
			JCurrentValPtr->tv_micro = tempmicro;
			timedisplay[0] = 48 + minutes / 10;
			timedisplay[1] = 48 + minutes % 10;
			timedisplay[3] = 48 + seconds / 10;
			timedisplay[4] = 48 + seconds % 10;
			say(timedisplay);

			for (i = 0; i <= VERYSLOW - 1; i++)
			{	if (a == PLAYGAME)
				{	JTimerRqPtr->tr_node.io_Command	= TR_ADDREQUEST;
					JTimerRqPtr->tr_time.tv_secs	= 0;
					JTimerRqPtr->tr_time.tv_micro	= DELAY;
					SendIO(JTimerRqPtr);
					timer = TRUE;
					fastloop();
					for (player = lo; player <= hi; player++)
						if (worm[player].lives > 0 && i % worm[player].speed == 0 && (ice == -1 || ice == player))
							wormloop(player);
					if (ice == -1)
						for (whichorb = 0; whichorb <= ORBS; whichorb++)
							if (orb[whichorb].alive == TRUE && i % orb[whichorb].speed == 0)
								if (orb[whichorb].explode == TRUE)
									orbexplosion(whichorb);
								else
									orbloop(whichorb);
					death();
					WaitIO(JTimerRqPtr);
					timer = FALSE;
			}	}
			if (a == PLAYGAME)
				slowloop();
		}
		say("");
}	}

// SUPPORT FUNCTIONS -----------------------------------------------------

void anykey(void)
{	BOOL done = FALSE;

	clearkybd();
	while (done == FALSE)
	{	if (joy == TRUE && (GetMsg(JJoyPortPtr)))
		{	if (JGameEvent.ie_Code == IECODE_LBUTTON)
				done = TRUE;
			sendreadrequest();
		}
		while (JIMsgPtr = (struct IntuiMessage *) GT_GetIMsg(JWindowPtr->UserPort))
		{	GT_ReplyIMsg(JIMsgPtr);
			done = TRUE;
}	}	}

void blitmode(BYTE state)
{	if (state == BLACK)
	{	JImage.PlanePick = 0x0;
		JImage.PlaneOnOff = 0x0;
	}
	else if (state == WHITE)
	{	JImage.PlanePick = 0x0;
		JImage.PlaneOnOff = 0x1;
	}
	else
		JImage.PlanePick = 0xf;
}

void bombblast(UBYTE type, BYTE player, BYTE centrex, BYTE centrey)
{
	/* Purpose:	To execute bomb explosions.

	Arguments:	type			HEAD or ORB
			player			worm or orb number
			centre, centrey		centre coordinates	*/

	BYTE	counter, strength;
	BYTE	leftx, leftxmax, rightx, rightxmax,
		uppy, uppymax, downy, downymax;
	BYTE	x, y;

	if (type != HEAD || worm[player].bias == 0)
		strength = BOMBADD + (rand() % BOMBRAND) + 1;
	else
		strength = BOMBADD + BOMBRAND;

	leftxmax = centrex - strength;
	if (leftxmax < 0)
		leftxmax = 0;
	rightxmax = centrex + strength;
	if (rightxmax > FIELDX)
		rightxmax = FIELDX;
	uppymax = centrey - strength;
	if (uppymax < 0)
		uppymax = 0;
	downymax = centrey + strength;
	if (downymax > FIELDY)
		downymax = FIELDY;

	leftx = centrex;
	rightx = centrex;
	uppy = centrey;
	downy = centrey;

	for (counter = 1; counter <= strength; counter++)
	{	if (leftx > leftxmax)
		{	leftx--;
			for (y = uppy; y <= downy; y++)
				squareblast(type, player, leftx, y);
		}
		if (rightx < rightxmax)
		{	rightx++;
			for (y = uppy; y <= downy; y++)
				squareblast(type, player, rightx, y);
		}
		if (uppy > uppymax)
		{	uppy--;
			for (x = leftx; x <= rightx; x++)
				squareblast(type, player, x, uppy);
		}
		if (downy < downymax)
		{	downy++;
			for (x = leftx; x <= rightx; x++)
				squareblast(type, player, x, downy);
	}	}

	if (type == HEAD)
		stat(player, SCORELINE);
}

void celebrate(void)
{	BOOL done = FALSE;

	Delay(50);
	clearkybd();
	while (done == FALSE)
	{	if (JMsgPtr = (struct IntuiMessage *) GetMsg(JWindowPtr->UserPort))
		{	ReplyMsg(JMsgPtr);
			done = TRUE;
		}
		if (joy == TRUE && (GetMsg(JJoyPortPtr)))
		{	if (JGameEvent.ie_Code == IECODE_LBUTTON)
				done = TRUE;
			sendreadrequest();
		}
		SetRGB4(&JScreenPtr->ViewPort, 17, rand() % 16, rand() % 16, rand() % 16);
		draw(rand() % (FIELDX + 1), rand() % (FIELDY + 1), BONUS);
	}
	SetRGB4(&JScreenPtr->ViewPort, 17, 14, 4, 4);
	a = GAMEOVER;
}

void cleanexit(void)
{	if (JWindowPtr)
	{	clearkybd();
		ClearMenuStrip(JWindowPtr);
		CloseWindow(JWindowPtr);
		if (gadgets == VERYTRUE)
		{	if (RemoveGList(JWindowPtr, JGListPtr, -1) < 0)
			{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't remove gadgets!\0", 24);
				gadgets = FALSE;
				exit(EXIT_FAILURE);
			}
			FreeGadgets(JGListPtr);
		}
		else if (gadgets == TRUE)
			FreeGadgets(JGListPtr);
		if (JMenuPtr != NULL)
			FreeMenus(JMenuPtr);
		if (JVisualInfoPtr)
			FreeVisualInfo(JVisualInfoPtr);
	}
	if (JScreenPtr)
		CloseScreen(JScreenPtr);
	if (joy == TRUE)
	{	AbortIO(JJoyRqPtr);
		WaitIO(JJoyRqPtr);
		Forbid();
		JController		= GPCT_NOCONTROLLER;
		JJoyRqPtr->io_Command	= GPD_SETCTYPE;
		JJoyRqPtr->io_Length	= 1;
		JJoyRqPtr->io_Data	= (APTR) &JController;
		DoIO(JJoyRqPtr);
		Permit();
	}
	if (!JJoyOpen)
		CloseDevice(JJoyRqPtr);
	if (JJoyRqPtr)
		DeleteExtIO(JJoyRqPtr);
	if (JJoyPortPtr)
		DeletePort(JJoyPortPtr);
	if (timer == TRUE)
	{	AbortIO(JTimerRqPtr);
		WaitIO(JTimerRqPtr);
	}
	if (JPausedValPtr)
		FreeMem(JPausedValPtr, sizeof(struct timeval));
	if (JCurrentValPtr)
		FreeMem(JCurrentValPtr, sizeof(struct timeval));
	if (JStartValPtr)
		FreeMem(JStartValPtr, sizeof(struct timeval));
	if (!JTimerOpen)
		CloseDevice(JTimerRqPtr);
	if (JTimerRqPtr)
		DeleteExtIO(JTimerRqPtr);
	if (JTimerPortPtr)
		DeletePort(JTimerPortPtr);
	if (music == TRUE)
		StopPlayer();
	if (sound == TRUE)
	{	UnLoadModule(JSongPtr);		// this is safe even
		FreePlayer();			// with a NULL pointer
	}
	if (MEDPlayerBase)
		CloseLibrary(MEDPlayerBase);
	if (GadToolsBase)
		CloseLibrary(GadToolsBase);
	if (GfxBase)
		CloseLibrary(GfxBase);
	OpenWorkBench();
	if (IntuitionBase)
		CloseLibrary(IntuitionBase);

	// End of program.
}

void clearkybd(void)
{	while (JIMsgPtr = (struct IntuiMessage *) GT_GetIMsg(JWindowPtr->UserPort))
		GT_ReplyIMsg(JIMsgPtr);
}

void death(void)
{	BYTE player;
	BYTE whichprot;

	if (a == PLAYGAME)
	{	for (player = lo; player <= hi; player++)
			if (worm[player].lives > 0)
			{	if (worm[player].alive == FALSE)
				{	draw(worm[player].x, worm[player].y, SKULL);
					if (--worm[player].lives < 51)
						highlight(player, LIVESLINE, FALSE);
					if (worm[player].victor >= 0 && worm[player].victor != player)
						worm[worm[player].victor].score += KILLWORM * worm[worm[player].victor].multi;
					worm[player].victor = -1;
					draw(-2 + ((FIELDX + 4) * worm[player].statx), (FIELDY / 2 - 2) + (worm[player].staty * 2), worm[player].cause);
					if (worm[player].lives > 0)
					{	worm[player].alive = TRUE;
						worm[player].causewait = r + CAUSEWAIT;
					}
					else
					{	if (ice == player)
							ice = -1;
						field[worm[player].x][worm[player].y] = SKULL;
						for (whichprot = 0; whichprot <= PROTECTORS; whichprot++)
						{	if (protector[player][whichprot].alive == TRUE && protector[player][whichprot].visible == TRUE)
							{	draw(protector[player][whichprot].x, protector[player][whichprot].y, EMPTY);
								field[protector[player][whichprot].x][protector[player][whichprot].y] = EMPTY;
					}	}	}
					stat(player, LIVESLINE);
				}
				stat(player, SCORELINE);
			}
		if (worm[0].lives == 0 && worm[1].lives == 0 && worm[2].lives == 0 && worm[3].lives == 0)
		{	a = GAMEOVER;
			if (lo == hi)
				say("Game over!");
			else if (worm[0].control != NONE && (worm[1].control == NONE || worm[1].score < worm[0].score) && (worm[2].control == NONE || worm[2].score < worm[0].score) && (worm[3].control == NONE || worm[3].score < worm[0].score))
				say("Green wins!");
			else if (worm[1].control != NONE && (worm[0].control == NONE || worm[0].score < worm[1].score) && (worm[2].control == NONE || worm[2].score < worm[1].score) && (worm[3].control == NONE || worm[3].score < worm[1].score))
				say("Red wins!");
			else if (worm[2].control != NONE && (worm[0].control == NONE || worm[0].score < worm[2].score) && (worm[1].control == NONE || worm[1].score < worm[2].score) && (worm[3].control == NONE || worm[3].score < worm[2].score))
				say("Blue wins!");
			else if (worm[3].control != NONE && (worm[0].control == NONE || worm[0].score < worm[3].score) && (worm[1].control == NONE || worm[1].score < worm[3].score) && (worm[2].control == NONE || worm[2].score < worm[3].score))
				say("Yellow wins!");
			else
				say("A draw!");
			Delay(50);
			anykey();
}	}	}

void draw(BYTE x, BYTE y, UBYTE image)
{
	/* Purpose:	To draw images.

	Arguments:	x		x coordinates (in squares)
			y		y coordinates (in squares)
			image		the image to use		*/

	JImage.ImageData = JImageData[image];
	DrawImage(JWindowPtr->RPort, &JImage, x * X + STARTXPIXEL, y * Y + STARTYPIXEL);
}

void fastloop(void)
{	BYTE player;
	BYTE distx, disty, distance, lowdistance;
	UBYTE creature;
	BYTE i, j;
	BYTE which, whichfrag, whichkillfire, whichorb;
	UBYTE this;

	/* Purpose:	All things which must be done very often
			(VERYFAST - ie. 4 * the NORMAL rate).

			Moves killer bullets,
			orb fragments and
			missiles.

	Arguments:	None.						*/

	if (ice == -1)
	{	for (whichkillfire = 0; whichkillfire <= KILLERS; whichkillfire++)
		{	if (killerfire[whichkillfire].alive == TRUE)
			{	if (killerfire[whichkillfire].moved == TRUE)
				{	draw(killerfire[whichkillfire].x, killerfire[whichkillfire].y, EMPTY);
					field[killerfire[whichkillfire].x][killerfire[whichkillfire].y] = EMPTY;
				}
				else
					killerfire[whichkillfire].moved = TRUE;
				killerfire[whichkillfire].x += killerfire[whichkillfire].deltax;
				killerfire[whichkillfire].y += killerfire[whichkillfire].deltay;
				if (!(valid(killerfire[whichkillfire].x, killerfire[whichkillfire].y)))
					killerfire[whichkillfire].alive = FALSE;
				else
				{	this = field[killerfire[whichkillfire].x][killerfire[whichkillfire].y];
					if (this >= GREENHEAD && this <= YELLOWHEAD)
					{	if (worm[this - GREENHEAD].mode != ARMOUR)
						{	worm[this - GREENHEAD].cause = FIRE;
							worm[this - GREENHEAD].alive = FALSE;
						}
						killerfire[whichkillfire].alive = FALSE;
					}
					else
						switch (this)
						{	case ORB:
								for (whichorb = 0; whichorb <= ORBS; whichorb++)
								if (orb[whichorb].alive == TRUE && orb[whichorb].x == killerfire[whichkillfire].x && orb[whichorb].y == killerfire[whichkillfire].y && orb[whichorb].mode != ARMOUR)
								{	orb[whichorb].alive = FALSE;
									draw(orb[whichorb].x, orb[whichorb].y, EMPTY);
									field[orb[whichorb].x][orb[whichorb].y] = EMPTY;
								}
								killerfire[whichkillfire].alive = FALSE;
								break;
							case KILLER:
								for (j = 0; j <= KILLERS; j++)
									if (killer[j].alive == TRUE && killer[j].x == killerfire[whichkillfire].x && killer[j].y == killerfire[whichkillfire].y)
									{	killer[j].alive = FALSE;
										draw(killer[j].x, killer[j].y, BONUS);
										field[killer[j].x][killer[j].y] = BONUS;
									}
								killerfire[whichkillfire].alive = FALSE;
								break;
							case TELEPORT:
								for (j = 0; j <= 3; j++)
								if (teleport[j].alive == TRUE && teleport[j].x == killerfire[whichkillfire].x && teleport[j].y == killerfire[whichkillfire].y)
								{	killerfire[whichkillfire].x = teleport[partner(j)].x + killerfire[whichkillfire].deltax;
									killerfire[whichkillfire].y = teleport[partner(j)].y + killerfire[whichkillfire].deltay;
									if (!(valid(killerfire[whichkillfire].x, killerfire[whichkillfire].y)))
										killerfire[whichkillfire].alive = FALSE;
								}
								break;
							default:
								if (this == SKULL || (this >= FIRSTLETTER && this <= LASTLETTER) || (this >= FIRSTHARD && this <= LASTHARD) || (this >= GREENPROTECTOR && this <= YELLOWPROTECTOR))
									killerfire[whichkillfire].alive = FALSE;
								break;
						}
					if (killerfire[whichkillfire].alive == TRUE)
					{	field[killerfire[whichkillfire].x][killerfire[whichkillfire].y] = FIRE;
						draw(killerfire[whichkillfire].x, killerfire[whichkillfire].y, FIRE);
		}	}	}	}

		for (whichorb = 0; whichorb <= ORBS; whichorb++)
			for (whichfrag = 0; whichfrag <= 7; whichfrag++)
				if (frag[whichorb][whichfrag].alive == TRUE)
				{	draw(frag[whichorb][whichfrag].x, frag[whichorb][whichfrag].y, EMPTY);
					field[frag[whichorb][whichfrag].x][frag[whichorb][whichfrag].y] = EMPTY;
					frag[whichorb][whichfrag].x += frag[whichorb][whichfrag].deltax;
					frag[whichorb][whichfrag].y += frag[whichorb][whichfrag].deltay;
					if (!(valid(frag[whichorb][whichfrag].x, frag[whichorb][whichfrag].y)))
						frag[whichorb][whichfrag].alive = FALSE;
					else
					{	this = field[frag[whichorb][whichfrag].x][frag[whichorb][whichfrag].y];
						switch(this)
						{	case ORB:
								for (j = 0; j <= ORBS; j++)
								if (orb[j].alive == TRUE && orb[j].x == frag[whichorb][whichfrag].x && orb[j].y == frag[whichorb][whichfrag].y && orb[j].mode != ARMOUR)
									orb[j].explode = TRUE;
								frag[whichorb][whichfrag].alive = FALSE;
								break;
							case KILLER:
								for (j = 0; j <= KILLERS; j++)
									if (killer[j].alive == TRUE && killer[j].x == frag[whichorb][whichfrag].x && killer[j].y == frag[whichorb][whichfrag].y)
									{	killer[j].alive = FALSE;
										field[killer[j].x][killer[j].y] = BONUS;
										draw(killer[j].x, killer[j].y, BONUS);
									}
								frag[whichorb][whichfrag].alive = FALSE;
								break;
							case WALL:
								frag[whichorb][whichfrag].alive = FALSE;
								break;
							case TELEPORT:
								for (j = 0; j <= 3; j++)
									if (teleport[j].alive == TRUE && teleport[j].x == frag[whichorb][whichfrag].x && teleport[j].y == frag[whichorb][whichfrag].y)
									{	frag[whichorb][whichfrag].x = teleport[partner(j)].x + frag[whichorb][whichfrag].deltax;
										frag[whichorb][whichfrag].y = teleport[partner(j)].y + frag[whichorb][whichfrag].deltay;
										if (!(valid(frag[whichorb][whichfrag].x, frag[whichorb][whichfrag].y)))
											frag[whichorb][whichfrag].alive = FALSE;
									}
								break;
							case SKULL:
								frag[whichorb][whichfrag].alive = FALSE;
								break;
							default:
								if (field[frag[whichorb][whichfrag].x][frag[whichorb][whichfrag].y] >= GREENPROTECTOR && field[frag[whichorb][whichfrag].x][frag[whichorb][whichfrag].x] <= YELLOWPROTECTOR || field[frag[whichorb][whichfrag].x][frag[whichorb][whichfrag].y] >= FIRSTLETTER && field[frag[whichorb][whichfrag].x][frag[whichorb][whichfrag].y] <= LASTLETTER)
									frag[whichorb][whichfrag].alive = FALSE;
								for (player = lo; player <= hi; player++)
									if (worm[player].lives > 0 && worm[player].x == frag[whichorb][whichfrag].x && worm[player].y == frag[whichorb][whichfrag].y && worm[player].mode != ARMOUR)
									{	worm[player].cause = FRAGMENT;
										worm[player].alive = FALSE;
										frag[whichorb][whichfrag].alive = FALSE;
									}
								break;
					}	}
					if (frag[whichorb][whichfrag].alive == TRUE)
					{	field[frag[whichorb][whichfrag].x][frag[whichorb][whichfrag].y] = FRAGMENT;
						draw(frag[whichorb][whichfrag].x, frag[whichorb][whichfrag].y, FRAGMENT);
	}			}	}

	for (player = lo; player <= hi; player++)
	{	if (missile[player].alive == TRUE && (ice == -1 || ice == player))
		{	lowdistance = 127;
			creature = NULL;
			for (whichorb = 0; whichorb <= ORBS; whichorb++)
				if (orb[whichorb].alive == TRUE && orb[whichorb].mode != ARMOUR)
				{	distx = abs(orb[whichorb].x - missile[player].x);
					disty = abs(orb[whichorb].y - missile[player].y);
					if (distx < disty)
						distance = distx;
					else
						distance = disty;
					if (distance <= lowdistance)
					{	which = whichorb;
						creature = ORB;
						lowdistance = distance;
				}	}
			for (i = lo; i <= hi; i++)
				if (i != player && worm[i].lives > 0 && worm[i].mode != ARMOUR && worm[i].bias == 0)
				{	distx = abs(worm[i].x - missile[i].x);
					disty = abs(worm[i].y - missile[i].y);
					if (distx < disty)
						distance = distx;
					else
						distance = disty;
					if (distance <= lowdistance)
					{	which = i;
						creature = HEAD;
						lowdistance = distance;
				}	}
			switch (creature)
			{	case ORB:
					missile[player].deltax = sign(orb[which].x - missile[player].x);
					missile[player].deltay = sign(orb[which].y - missile[player].y);
					break;
				case HEAD:
					missile[player].deltax = sign(worm[which].x - missile[player].x);
					missile[player].deltay = sign(worm[which].y - missile[player].y);
					break;
				case NULL:
					missile[player].alive = FALSE;
					break;
				default:
					break;
			}
			if (missile[player].alive == TRUE)
			{	missile[player].oldx = missile[player].x;
				missile[player].oldy = missile[player].y;
				missile[player].x += missile[player].deltax;
				missile[player].y += missile[player].deltay;
				if (!(valid(missile[player].x, missile[player].y)))
					missile[player].alive = FALSE;
				else if (field[missile[player].x][missile[player].y] >= FIRSTHARD && field[missile[player].x][missile[player].y] <= LASTSOFT)
				{	if (field[missile[player].x][missile[player].y] <= LASTHARD || (field[missile[player].x][missile[player].y] - GREENTAIL != player && worm[field[missile[player].x][missile[player].y] - GREENTAIL].mode == ARMOUR))
						missile[player].alive = FALSE;
				}
				else if (field[missile[player].x][missile[player].y] == ORB)
				{	for (whichorb = 0; whichorb <= ORB; whichorb++)
						if (orb[whichorb].alive == TRUE && orb[whichorb].x == missile[player].x && orb[whichorb].y == missile[player].y)
						{	if (orb[whichorb].mode != ARMOUR)
							{	orb[whichorb].alive = FALSE;
								draw(orb[whichorb].x, orb[whichorb].y, EMPTY);
								field[orb[whichorb].x][orb[whichorb].y] = EMPTY;
								worm[player].score += orb[whichorb].score * worm[player].multi;
								stat(player, SCORELINE);
							}
							missile[player].alive = FALSE;
				}		}
				else if (field[missile[player].x][missile[player].y] >= GREENPROTECTOR && field[missile[player].x][missile[player].y] <= YELLOWPROTECTOR)
				{	for (i = lo; i <= hi; i++)
						if (i != player && i + GREENPROTECTOR == field[missile[player].x][missile[player].y])
							missile[player].alive = FALSE;
				}
				else if (field[missile[player].x][missile[player].y] >= GREENHEAD && field[missile[player].x][missile[player].y] <= YELLOWHEAD)
				{	for (i = lo; i <= hi; i++)
						if (i != player && i + GREENHEAD == field[missile[player].x][missile[player].y])
						{	missile[player].alive = FALSE;
							if (worm[i].mode != ARMOUR)
							{	worm[i].cause = MISSILE;
								worm[i].victor = player;
								worm[i].alive = FALSE;
				}		}	}
				else if (field[missile[player].x][missile[player].y] == SKULL || field[missile[player].x][missile[player].y] >= FIRSTLETTER && field[missile[player].x][missile[player].y] <= LASTLETTER)
					missile[player].alive = FALSE;
				if (missile[player].alive == TRUE)
				{	draw(missile[player].x, missile[player].y, GREENMISSILE + player);
					field[missile[player].x][missile[player].y] = FIREDMISSILE;
				}
				if (missile[player].moved == TRUE)
				{	draw(missile[player].oldx, missile[player].oldy, EMPTY);
					field[missile[player].oldx][missile[player].oldy] = EMPTY;
				}
				else
					missile[player].moved = TRUE;
}	}	}	}

void fieldedit(void)
{	BYTE thistel = 0, othertel = 1;
	BYTE i;
	BYTE x, y;
	ULONG class;
	UWORD code, qual;
	BYTE saystring[9] = "Level ";
	BOOL modified = FALSE;
	BOOL mouseoversquare;

	static UBYTE square = WALL;
	static BYTE xx = -7;

	ClearMenuStrip(JMenuPtr);
	FreeMenus(JMenuPtr);
	JNewMenu[1].nm_Flags = NULL;
	JNewMenu[2].nm_Flags = NULL;
	JNewMenu[3].nm_Flags = NULL;
	JNewMenu[7].nm_Flags = NULL;
	JNewMenu[8].nm_Flags = NM_ITEMDISABLED;
	JMenuPtr = (struct Menu *) CreateMenus(JNewMenu, TAG_DONE);
	if (JMenuPtr == NULL)
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't create menus!\0", 24);
		exit(EXIT_FAILURE);
	}
	if (!(LayoutMenus(JMenuPtr, JVisualInfoPtr, TAG_DONE)))
	{	DisplayAlert(AT_Recovery, "\0\20\20Worm Wars: Can't lay out menus!\0", 24);
		exit(EXIT_FAILURE);
	}
	say("Field Editor");	
	SetMenuStrip(JWindowPtr, JMenuPtr);
	if (square != TELEPORT && square != SKULL)
		setpointer(square);

	for (i = 0; i <= 4; i++)
		DrawBevelBox(JWindowPtr->RPort, i * 21 + 6, 122, 16, 10, GT_VisualInfo, JVisualInfoPtr);
	for (i = 0; i <= 2; i++)
		DrawBevelBox(JWindowPtr->RPort, i * 21 + 531, 122, 16, 10, GT_VisualInfo, JVisualInfoPtr);
	draw(-19,		FIELDY / 2, GOLD);
	draw(-16,		FIELDY / 2, SILVER);
	draw(-13,		FIELDY / 2, EMPTY);
	draw(-10,		FIELDY / 2, BRICK);
	draw(- 7,		FIELDY / 2, WALL);
	draw(FIELDX +  7,	FIELDY / 2, TELEPORT);
	draw(FIELDX + 10,	FIELDY / 2, SKULL);
	draw(FIELDX + 13,	FIELDY / 2, TELEPORT);
	if (openboard(level))
		updateboard();
	else
		newboard();
	clearkybd();
	leftdown  = FALSE;
	rightdown = FALSE;
	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);

	while (a == FIELDEDIT)
	{	while (JMsgPtr = (struct IntuiMessage *) GetMsg(JWindowPtr->UserPort))
		{	class = JMsgPtr->Class;
			code  = JMsgPtr->Code;
			qual  = JMsgPtr->Qualifier;
			if (class == IDCMP_MENUVERIFY)
				if (JMsgPtr->MouseX >= STARTXPIXEL && JMsgPtr->MouseX <= ENDXPIXEL && JMsgPtr->MouseY >= STARTYPIXEL && JMsgPtr->MouseY <= ENDYPIXEL)
/* There is no need to check for MENUHOT as it will always be set.
(There is only one window on the WormWars screen.) */
				{	JMsgPtr->Code = MENUCANCEL;
					rightdown = TRUE;
				}
				else
					rightdown = FALSE;
			ReplyMsg(JMsgPtr);
			switch (class)
			{	case IDCMP_MENUPICK:
					if (code != MENUNULL)
					{	switch (MENUNUM(code))
						{	case 0:
								// project
								switch (ITEMNUM(code))
								{	case 0:
										// new
										newboard();
										say("Done.");
										break;
									case 1:
										// revert
										ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
										if (openboard(level))
										{	say("Done.");
											updateboard();
										}
										else
											say("Reversion failed!");
										ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
										break;
									case 2:
										// save
										ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
										if (saveboard(level) == TRUE)
										{	say("Done.");
											modified = FALSE;
										}
										else
											say("Save failed!");
										ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
										break;
									case 4:
										// quit
										ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
										if (modified == TRUE && (EasyRequest(JWindowPtr, &JEasyStruct, NULL) == 1))
											saveboard(level);
										exit(EXIT_SUCCESS);
										ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
										break;
									default:
										break;
								}
								break;
							case 1:
								// module, title screen
								a = GAMEOVER;
								break;
							default:
								break;
					}	}
					break;
				case IDCMP_RAWKEY:
					if (code == DELETE)
					{	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
						if (modified == TRUE && (EasyRequest(JWindowPtr, &JEasyStruct, NULL) == 1))
							saveboard(level);
						level--;
						if (level < 1)
							level = LEVELS;
						stci_d(&saystring[6], level);
						say(saystring);
						if (openboard(level))
							updateboard();
						else
							newboard();
						modified = FALSE;
						ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
					}
					else if (code == HELP)
					{	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
						if (modified == TRUE && (EasyRequest(JWindowPtr, &JEasyStruct, NULL) == 1))
							saveboard(level);
						level++;
						if (level > LEVELS)
							level = 1;
						stci_d(&saystring[6], level);
						say(saystring);
						if (openboard(level))
							updateboard();
						else
							newboard();
						modified = FALSE;
						ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
					}
					else if (code == key[MUSIC].scancode && tune == TRUE)
					{	if (music == TRUE)
							muzak(FALSE);
						else
						{	ContModule(JSongPtr);
							muzak(TRUE);
					}	}
					else if (code == SPACEBAR || code == 0x43 || code == 0x44)
					{	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
						if (modified == TRUE && (EasyRequest(JWindowPtr, &JEasyStruct, NULL) == 1))
							saveboard(level);
						a = GAMEOVER;
						ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
					}
					else if (code == key[EXIT].scancode)
					{	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
						if (modified == TRUE && (EasyRequest(JWindowPtr, &JEasyStruct, NULL) == 1))
							saveboard(level);
						if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
							exit(EXIT_SUCCESS);
						else
							a = GAMEOVER;
						ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
					}
					else if (code == N)
					{	newboard();
						say("Done.");
					}
					else if (code == R)
					{	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
						if (openboard(level))
						{	say("Done.");
							updateboard();
						}
						else
							say("Reversion failed!");
						ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
					}
					else if (code == S)
					{	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
						if (saveboard(level) == TRUE)
						{	say("Done.");
							modified = FALSE;
						}
						else
							say("Save failed!");
						ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW | IDCMP_MENUVERIFY);
					}
					break;
				case IDCMP_MOUSEBUTTONS:
					switch (code)
					{	case SELECTUP:
							leftdown = JUSTLIFTED;
							break;
						case SELECTDOWN:
							leftdown = TRUE;
							break;
						case MENUUP:
							rightdown = FALSE;
							break;
						default:
							break;
					}
					break;
				default:
					break;
// This default may in fact be executed, as it will catch a MENUVERIFY.
		}	}
		if (leftdown == TRUE)
		{	x = xpixeltosquare(JWindowPtr->MouseX);
			y = ypixeltosquare(JWindowPtr->MouseY);
			if (valid(x, y))
			{	if (square == SKULL)
				{	if (board[x][y] != TELEPORT && (x != startx || y != starty))
					{	draw(startx, starty, EMPTY);
						board[startx][starty] = EMPTY;
						draw(x, y, SKULL);
						startx = x;
						starty = y;
						modified = TRUE;
				}	}
				else if (square == TELEPORT)
				{	if ((x != startx || y != starty) && (teleport[thistel].alive == FALSE || x != teleport[thistel].x || y != teleport[thistel].y) && (teleport[othertel].alive == FALSE || x != teleport[othertel].x || y != teleport[othertel].y))
					{	board[x][y] = TELEPORT;
						draw(x, y, TELEPORT);
						if (teleport[thistel].alive == TRUE)
						{	draw(teleport[thistel].x, teleport[thistel].y, EMPTY);
							board[teleport[thistel].x][teleport[thistel].y] = EMPTY;
						}
						else
							teleport[thistel].alive = TRUE;
						teleport[thistel].x = x;
						teleport[thistel].y = y;
						modified = TRUE;
				}	}
				else
				{	if (board[x][y] == TELEPORT)
					{	for (i = 0; i <= TELEPORTS; i++)
							if (teleport[i].alive == TRUE && teleport[i].x == x && teleport[i].y == y)
								teleport[i].alive = FALSE;
						draw(x, y, square);
						board[x][y] = square;
						modified = TRUE;
					}
					else if (x != startx || y != starty)
					{	draw(x, y, square);
						board[x][y] = square;
						modified = TRUE;
			}	}	}
			else
			{	if (x != xx)
				{	switch (xx)
					{	case -19:
							draw(-19, FIELDY / 2, GOLD);
							break;
						case -16:
							draw(-16, FIELDY / 2, SILVER);
							break;
						case -13:
							draw(-13, FIELDY / 2, EMPTY);
							break;
						case -10:
							draw(-10, FIELDY / 2, BRICK);
							break;
						case -7:
							draw( -7, FIELDY / 2, WALL);
							break;
						case FIELDX + 7:
							draw(FIELDX +  7, FIELDY / 2, TELEPORT);
							break;
						case FIELDX + 10:
							draw(FIELDX + 10, FIELDY / 2, SKULL);
							break;
						case FIELDX + 13:
							draw(FIELDX + 13, FIELDY / 2, TELEPORT);
							break;
						default:
							break;
				}	}
				if (y == FIELDY / 2 && (x == -19 || x == -16 || x == -13 || x == -10 || x == -7 || x == FIELDX + 7 || x == FIELDX + 10 || x == FIELDX + 13))
				{	xx = x;
					blitmode(WHITE);
					draw(xx, FIELDY / 2, ANYTHING);
					blitmode(NORMAL);
		}	}	}
		else if (leftdown == JUSTLIFTED)
		{	leftdown = FALSE;
			if (y == FIELDY / 2)
			{	mouseoversquare = TRUE;
				switch (x)
				{	case -19:
						square = GOLD;
						setpointer(GOLD);
						break;
					case -16:
						square = SILVER;
						setpointer(SILVER);
						break;
					case -13:
						square = EMPTY;
						setpointer(EMPTY);
						break;
					case -10:
						square = BRICK;
						setpointer(BRICK);
						break;
					case -7:
						square = WALL;
						setpointer(WALL);
						break;
					case FIELDX + 7:
						square = TELEPORT;
						thistel = 0;
						othertel = 1;
						setpointer(NORMAL);
						break;
					case FIELDX + 10:
						square = SKULL;
						setpointer(NORMAL);
						break;
					case FIELDX + 13:
						square = TELEPORT;
						thistel = 1;
						othertel = 0;
						setpointer(NORMAL);
						break;
					default:
						mouseoversquare = FALSE;
						break;
				}
				if (mouseoversquare == TRUE)
					draw(xx, FIELDY / 2, square);
		}	}
		if (rightdown == TRUE)
		{	x = xpixeltosquare(JWindowPtr->MouseX);
			y = ypixeltosquare(JWindowPtr->MouseY);
			if (board[x][y] == TELEPORT)
			{	for (i = 0; i <= TELEPORTS; i++)
					if (teleport[i].alive == TRUE && teleport[i].x == x && teleport[i].y == y)
						teleport[i].alive = FALSE;
				draw(x, y, EMPTY);
				board[x][y] = EMPTY;
				modified = TRUE;
			}
			else if (x != startx || y != starty)
			{	draw(x, y, EMPTY);
				board[x][y] = EMPTY;
				modified = TRUE;
			}
			if (JMsgPtr->MouseX < STARTXPIXEL || JMsgPtr->MouseX > ENDXPIXEL || JMsgPtr->MouseY < STARTYPIXEL || JMsgPtr->MouseY > ENDYPIXEL)
				rightdown = FALSE;
	}	}
	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
	setpointer(NORMAL);
	if (teleport[0].alive == FALSE && teleport[1].alive == TRUE)
	{	teleport[1].alive = FALSE;
		board[teleport[1].x][teleport[1].y] = EMPTY;
	}
	else if (teleport[0].alive == TRUE && teleport[1].alive == FALSE)
	{	teleport[0].alive = FALSE;
		board[teleport[0].x][teleport[0].y] = EMPTY;
	}
	blitmode(BLACK);
	draw(-19,		FIELDY / 2, ANYTHING);
	draw(-16,		FIELDY / 2, ANYTHING);
	draw(-13,		FIELDY / 2, ANYTHING);
	draw(-10,		FIELDY / 2, ANYTHING);
	draw(- 7,		FIELDY / 2, ANYTHING);
	draw(FIELDX +  7,	FIELDY / 2, ANYTHING);
	draw(FIELDX + 10,	FIELDY / 2, ANYTHING);
	draw(FIELDX + 13,	FIELDY / 2, ANYTHING);
	blitmode(NORMAL);
	SetAPen(JWindowPtr->RPort, BLACK);
	RectFill(JWindowPtr->RPort, 6, 122, 106, 133);
	RectFill(JWindowPtr->RPort, 531, 122, 589, 133);
}

void highlight(BYTE player, BYTE line, BOOL state)
{	if (state == TRUE)
		SetAPen(JWindowPtr->RPort, WHITE);
	else
		SetAPen(JWindowPtr->RPort, worm[player].colour);
	Move(JWindowPtr->RPort, 16 + worm[player].statx * 504, 9 + line * 8 + worm[player].staty * 136);
	Text(JWindowPtr->RPort, attr[line], 7);
}

void muzak(BOOL flag)
{	if (flag == TRUE)
	{	// you do have to PlayModule() or ContModule() yourself.
		draw(-4, FIELDY / 2 - 1, MUZAK);
		draw(FIELDX + 4, FIELDY / 2 - 1, MUZAK);
		music = TRUE;
	}
	else
	{	StopPlayer();
		blitmode(BLACK);
		draw(-4, FIELDY / 2 - 1, ANYTHING);
		draw(FIELDX + 4, FIELDY / 2 - 1, ANYTHING);
		blitmode(NORMAL);
		music = FALSE;
}	}

void newboard(void)
{	int x, y;

	teleport[0].alive = FALSE;
	teleport[1].alive = FALSE;
	startx = FIELDX / 2;
	starty = FIELDY / 2;
	if (a == FIELDEDIT)
	{	for (x = 0; x <= FIELDX; x++)
			for (y = 0; y <= FIELDY; y++)
			{	board[x][y] = EMPTY;
				draw(x, y, EMPTY);
			}
		draw(startx, starty, SKULL);
	}
	else
	{	for (x = 0; x <= FIELDX; x++)
			for (y = 0; y <= FIELDY; y++)
			{	board[x][y] = EMPTY;
				field[x][y] = EMPTY;
				draw(x, y, EMPTY);
}	}		}

BOOL openboard(BYTE level)
{	int i;
	int x, y;
	BYTE JLevelString[3];

	stci_d(JLevelString, level);
	if (!(JFilePtr = fopen(JLevelString, "r")))
		return(FALSE);
	else if (fgetc(JFilePtr) != HELP)
		return(FALSE);
	else
	{	startx = fgetc(JFilePtr);
		starty = fgetc(JFilePtr);
		for (i = 0; i <= 1; i++)
		{	teleport[i].alive = fgetc(JFilePtr);
			teleport[i].x = fgetc(JFilePtr);
			teleport[i].y = fgetc(JFilePtr);
		}
		for (x = 0; x <= FIELDX; x++)
			for (y = 0; y <= FIELDY; y++)
			{	board[x][y] = fgetc(JFilePtr);
				field[x][y] = board[x][y];
			}
		if (fclose(JFilePtr))
			return(FALSE);
		else
			return(TRUE);
}	}

void orbexplosion(BYTE whichorb)
{	BYTE i;

	orb[whichorb].alive = FALSE;
	for (i = 0; i <= 7; i++)
	{	frag[whichorb][i].alive = TRUE;
		frag[whichorb][i].x = orb[whichorb].x;
		frag[whichorb][i].y = orb[whichorb].y;
		switch (i)
		{	case 0:
				frag[whichorb][i].deltax = 0;
				frag[whichorb][i].deltay = -1;
				break;
			case 1:
				frag[whichorb][i].deltax = 1;
				frag[whichorb][i].deltay = -1;
				break;
			case 2:
				frag[whichorb][i].deltax = 1;
				frag[whichorb][i].deltay = 0;
				break;
			case 3:
				frag[whichorb][i].deltax = 1;
				frag[whichorb][i].deltay = 1;
				break;
			case 4:
				frag[whichorb][i].deltax = 0;
				frag[whichorb][i].deltay = 1;
				break;
			case 5:
				frag[whichorb][i].deltax = -1;
				frag[whichorb][i].deltay = 1;
				break;
			case 6:
				frag[whichorb][i].deltax = -1;
				frag[whichorb][i].deltay = 0;
				break;
			case 7:
				frag[whichorb][i].deltax = -1;
				frag[whichorb][i].deltay = -1;
				break;
			default:
				break;
}	}	}

void orbloop(BYTE whichorb)
{	BYTE player;
	BYTE i, j;
	UBYTE lastbounce;
	UBYTE this;
	BYTE x, xx, y, yy, newx, newy;

	if (orb[whichorb].moved == TRUE && field[orb[whichorb].x][orb[whichorb].y] != ORB)
		orb[whichorb].moved = FALSE;
	if (orb[whichorb].moved == TRUE)
	{	draw(orb[whichorb].x, orb[whichorb].y, orb[whichorb].last);
		field[orb[whichorb].x][orb[whichorb].y] = orb[whichorb].last;
	}
	orb[whichorb].moved = TRUE;

	if (orb[whichorb].mode == TONGUE || orb[whichorb].mode == EATER)
		lastbounce = LASTHARD;
	else
		lastbounce = LASTSOFT;
	x  = xwrap(orb[whichorb].x + orb[whichorb].deltax);	// look what's in front
	y  = ywrap(orb[whichorb].y + orb[whichorb].deltay);
	xx = xwrap(orb[whichorb].x - orb[whichorb].deltax);	// look what's behind
	yy = ywrap(orb[whichorb].y - orb[whichorb].deltay);
	if (field[x][y] >= FIRSTHARD && field[x][y] <= lastbounce)	// if we're going to hit something
	{	if (field[x][y] == KILLER)
		{	for (i = 0; i <= KILLERS; i++)
				if (killer[i].alive == TRUE && killer[i].x == x && killer[i].y == y)
				{	killer[i].alive = FALSE;
					draw(x, y, BONUS);
					field[x][y] = BONUS;
					orb[whichorb].score += KILLKILLER * orb[whichorb].multi;
		}		}
		newx = -orb[whichorb].deltax;	// default bounce angle is 180°
		newy = -orb[whichorb].deltay;
		if (field[x][yy] >= FIRSTHARD && field[x][yy] <= lastbounce && (field[xx][y] < FIRSTHARD || field[xx][y] > lastbounce))
			newy = orb[whichorb].deltay;
		else if (field[xx][y] >= FIRSTHARD && field[xx][y] <= lastbounce && (field[x][yy] < FIRSTHARD || field[x][yy] > lastbounce))
			newx = orb[whichorb].deltax;
		x = xwrap(orb[whichorb].x + newx);
		y = ywrap(orb[whichorb].y + newy);
		orb[whichorb].deltax = newx;
		orb[whichorb].deltay = newy;
		orb[whichorb].x = x;
		orb[whichorb].y = y;
	}
	else
	{	orb[whichorb].x = xwrap(orb[whichorb].x + orb[whichorb].deltax);
		orb[whichorb].y = ywrap(orb[whichorb].y + orb[whichorb].deltay);
	}
	orb[whichorb].last = EMPTY;

	// collision detection

	if (orb[whichorb].alive == TRUE)
	{	this = field[orb[whichorb].x][orb[whichorb].y];
		if (this >= GREENPROTECTOR && this <= YELLOWPROTECTOR)
		{	orb[whichorb].alive = FALSE;
			worm[this - GREENPROTECTOR].score += orb[whichorb].score * worm[this - GREENPROTECTOR].multi;
			stat(this - GREENPROTECTOR, SCORELINE);
		}
		else if (this >= GREENTAIL && this <= YELLOWTAIL)
		{	if (orb[whichorb].mode == EATER)
			{	if (worm[this - GREENTAIL].bias > 0)
					orb[whichorb].last = GOLD;
				else
					orb[whichorb].last = SILVER;
		}	}
		else if (this >= GREENHEAD && this <= YELLOWHEAD)
		{	if (worm[this - GREENHEAD].mode != ARMOUR)
			{	worm[this - GREENHEAD].cause = ORB;
				worm[this - GREENHEAD].alive = FALSE;
				if (orb[whichorb].mode == ARMOUR)
					orb[whichorb].score += KILLWORM * orb[whichorb].multi;
				else
					orb[whichorb].alive = FALSE;
			}
			else
			{	orb[whichorb].alive = FALSE;
				worm[this - GREENHEAD].score += orb[whichorb].score * worm[this - GREENHEAD].multi;
				stat(this - GREENHEAD, SCORELINE);
			}
			orb[whichorb].last = this;
		}
		else if (this >= FIRSTLETTER && this <= LASTLETTER)
			putletter(rand() % 4);
		else
		{	if (this <= LASTOBJECT)
				orb[whichorb].score += object[this].score * orb[whichorb].multi;
			switch (this)
			{	case EMPTY:
					orb[whichorb].score += EMPTYPOINT * orb[whichorb].multi;
					break;
				case SILVER:
					orb[whichorb].score += SILVERPOINT * orb[whichorb].multi;
					break;
				case GOLD:
					orb[whichorb].score += GOLDPOINT * orb[whichorb].multi;
					break;
				case AMMO:
					if (orb[whichorb].mode != ARMOUR)
						orb[whichorb].explode = TRUE;
					break;
				case ARMOUR:
					orb[whichorb].armour += MODEADD + (rand() % MODERAND) + 1;
					orb[whichorb].mode = ARMOUR;
					break;
				case TONGUE:
					orb[whichorb].tongue += MODEADD + (rand() % MODERAND) + 1;
					orb[whichorb].mode = TONGUE;
					break;
				case NITRO:
					orb[whichorb].speed /= 2;
					if (orb[whichorb].speed < VERYFAST)
						orb[whichorb].speed = VERYFAST;
					break;
				case BOMB:
					bombblast(ORB, whichorb, orb[whichorb].x, orb[whichorb].y);
					break;
				case SLAYER:
					if (orb[whichorb].mode != ARMOUR)
						orb[whichorb].explode = TRUE;
					break;
				case PROTECTOR:
					for (player = lo; player <= hi; player++)
						if (worm[player].lives > 0)
							for (i = 0; i <= PROTECTORS; i++)
								if (protector[player][i].alive == TRUE)
								{	protector[player][i].alive = FALSE;
									if (protector[player][i].visible == TRUE)
									{	draw(protector[player][i].x, protector[player][i].y, EMPTY);
										field[protector[player][i].x][protector[player][i].y] = EMPTY;
								}	}
					break;
				case POWER:
					orb[whichorb].speed /= 2;
					if (orb[whichorb].speed < VERYFAST)
						orb[whichorb].speed = VERYFAST;
					break;
				case MISSILE:
					for (player = lo; player <= hi; player++)
						if (missile[player].alive == TRUE)
						{	missile[player].alive = FALSE;
							draw(missile[player].x, missile[player].y, EMPTY);
							field[missile[player].x][missile[player].y] = EMPTY;
						}
					break;
				case LIFE:
					orbsplit(whichorb);
					break;
				case MULTIPLIER:
					orb[whichorb].multi *= 2;
					if (orb[whichorb].multi > 16)
						orb[whichorb].multi = 16;
					break;
				case ICE:
					orbsplit(whichorb);
					break;
				case GROWER:
					orbsplit(whichorb);
					break;
				case TELEPORT:
					for (i = 0; i <= TELEPORTS; i++)
					{	if (teleport[i].alive == TRUE && orb[whichorb].x == teleport[i].x && orb[whichorb].y == teleport[i].y)
						{	orb[whichorb].score += TELPOINT * orb[whichorb].multi;
							orb[whichorb].x = xwrap(teleport[partner(i)].x + orb[whichorb].deltax);
							orb[whichorb].y = ywrap(teleport[partner(i)].y + orb[whichorb].deltay);
					}	}
					break;
				case EATER:
					orb[whichorb].eater += MODEADD + (rand() % MODERAND) + 1;
					orb[whichorb].mode = EATER;
					break;
				case BIAS:
					for (player = lo; player <= hi; player++)
						worm[player].bias = 0;
					break;
				case DIAGONAL:
					orb[whichorb].speed /= 2;
					if (orb[whichorb].speed < VERYFAST)
						orb[whichorb].speed = VERYFAST;
					break;
				case ORB:
			 		for (i = 0; i <= ORBS; i++)
						if (i != whichorb && orb[i].alive == TRUE && orb[i].x == orb[whichorb].x && orb[i].y == orb[whichorb].y)
						{	if (orb[i].mode != ARMOUR)
							{	orb[i].alive = FALSE;
								if (orb[whichorb].mode != ARMOUR)
									orb[whichorb].alive = FALSE;
							}
							else
							{	orb[whichorb].alive = FALSE;
								if (orb[whichorb].mode == ARMOUR)
									orb[i].alive = FALSE;
							}
							if (orb[whichorb].alive == TRUE && orb[i].alive == FALSE)
								orb[whichorb].score += orb[i].score * orb[whichorb].multi;
							if (orb[whichorb].alive == FALSE && orb[i].alive == TRUE)
								orb[i].score += orb[whichorb].score * orb[i].multi;
							if (orb[whichorb].alive == FALSE && orb[i].alive == FALSE)
							{	draw(orb[whichorb].x, orb[whichorb].y, BONUS);
								field[orb[whichorb].x][orb[whichorb].y] = BONUS;
						}	}
					break;
				case FRAGMENT:
					for (i = 0; i <= ORBS; i++)
						for (j = 0; j <= 7; j++)
							if (frag[i][j].alive == TRUE && frag[i][j].x == orb[whichorb].x && frag[i][j].y == orb[whichorb].y)
							{	frag[i][j].alive = FALSE;
								if (orb[whichorb].mode != ARMOUR)
									orb[whichorb].explode = TRUE;
							}
					break;
				case FIRE:
					if (orb[whichorb].mode != ARMOUR)
					{	orb[whichorb].alive = FALSE;
						draw(orb[whichorb].x, orb[whichorb].y, EMPTY);
						field[orb[whichorb].x][orb[whichorb].y] = EMPTY;
					}
					for (i = 0; i <= KILLERS; i++)
						if (killerfire[i].alive == TRUE && killerfire[i].x == orb[whichorb].x && killerfire[i].y == orb[whichorb].y)
							killerfire[i].alive = FALSE;
					break;
				case FIREDMISSILE:
					for (player = lo; player <= hi; player++)
						if (missile[player].alive == TRUE && missile[player].x == orb[whichorb].x && missile[player].y == orb[whichorb].y)
							if (orb[whichorb].mode != ARMOUR)
							{	worm[player].score += orb[whichorb].score * worm[player].multi;
								stat(player, SCORELINE);
								orb[whichorb].alive = FALSE;
								draw(orb[whichorb].x, orb[whichorb].y, BONUS);
								field[orb[whichorb].x][orb[whichorb].y] = BONUS;
							}
					break;
				case SKULL:
					orb[whichorb].score += SKULLPOINT * orb[whichorb].multi;
					break;
				default:
					break;
	}	}	}

	// update field

	if (orb[whichorb].alive == TRUE)
	{	if (orb[whichorb].mode == EATER)
			draw(orb[whichorb].x, orb[whichorb].y, ORBEATER);
		else if (orb[whichorb].mode == TONGUE)
			draw(orb[whichorb].x, orb[whichorb].y, ORBTONGUE);
		else if (orb[whichorb].mode == ARMOUR)
			draw(orb[whichorb].x, orb[whichorb].y, ORBARMOUR);
		else
			draw(orb[whichorb].x, orb[whichorb].y, ORB);
		field[orb[whichorb].x][orb[whichorb].y] = ORB;
}	}

void orbsplit(BYTE whichorb)
{	BOOL available;
	BYTE copy = 0;
	BYTE i;
	BYTE whichfrag;

	for (i = 0; i <= ORBS; i++)
	{	available = FALSE;
		for (whichfrag = 0; whichfrag <= 7; whichfrag++)
			if (frag[i][whichfrag].alive == TRUE)
				available = FALSE;
		if (orb[i].alive == FALSE && copy <= 3 && available == TRUE)
		{	orb[i].x = orb[whichorb].x;
			orb[i].y = orb[whichorb].y;
			orb[i].score = orb[whichorb].score;
			orb[i].armour = orb[whichorb].armour;
			orb[i].tongue = orb[whichorb].tongue;
			orb[i].mode = orb[whichorb].mode;
			orb[i].speed = orb[whichorb].speed;
			orb[i].explode = FALSE;
			orb[i].multi = orb[whichorb].multi;
			orb[i].moved = TRUE;
			orb[i].eater = orb[whichorb].eater;
			switch (copy)
			{	case 0:
					if (orb[whichorb].deltax != -1 || orb[whichorb].deltay != -1)
					{	orb[i].deltax = -1;
						orb[i].deltay = -1;
						orb[i].alive = TRUE;
					}
					break;
				case 1:
					if (orb[whichorb].deltax != 1 || orb[whichorb].deltay != 1)
					{	orb[i].deltax = 1;
						orb[i].deltay = 1;
						orb[i].alive = TRUE;
					}
					break;
				case 2:
					if (orb[whichorb].deltax != 1 || orb[whichorb].deltay != -1)
					{	orb[i].deltax = 1;
						orb[i].deltay = -1;
						orb[i].alive = TRUE;
					}
					break;
				case 3:
					if (orb[whichorb].deltax != -1 || orb[whichorb].deltay != 1)
					{	orb[i].deltax = -1;
						orb[i].deltay = 1;
						orb[i].alive = TRUE;
					}
					break;
				default:
					break;
			}
			copy++;
}	}	}

int partner(WORD teleport)
{	if (teleport % 2 == 0)
		return(teleport + 1);
	else
		return(teleport - 1);
}

void putletter(BYTE player)
{	BOOL complete;
	BYTE i;
	BYTE x, y;
	UBYTE letter;

	do
	{	x = rand() % (FIELDX + 1);
		y = rand() % (FIELDY + 1);
	} while (field[x][y] < FIRSTEMPTY || field[x][y] > LASTEMPTY);
	complete = TRUE;
	for (i = 0; i <= LETTERS - 1; i++)
		if (letters[player][i] == FALSE)
			complete = FALSE;
	if (complete == FALSE)
	{	do
			letter = rand() % LETTERS;
		while (letters[player][letter] == TRUE);
	}
	else
		letter = (rand() % LETTERS);
	field[x][y] = letter + FIRSTLETTER;
	draw(x, y, letter + FIRSTLETTER);
}

BOOL saveboard(BYTE level)
{	int i;
	int x, y;
	BYTE JLevelString[3];

	stci_d(JLevelString, level);
	JFilePtr = fopen(JLevelString, "w");
	if (!JFilePtr)
		return(FALSE);
	else
	{	if (teleport[0].alive == FALSE && teleport[1].alive == TRUE)
		{	teleport[1].alive = FALSE;
			board[teleport[1].x][teleport[1].y] = EMPTY;
			draw(teleport[1].x, teleport[1].y, EMPTY);
		}
		else if (teleport[1].alive == FALSE && teleport[0].alive == TRUE)
		{	teleport[0].alive = FALSE;
			board[teleport[0].x][teleport[0].y] = EMPTY;
			draw(teleport[0].x, teleport[0].y, EMPTY);
		}
		fputc(HELP, JFilePtr);
		fputc(startx, JFilePtr);
		fputc(starty, JFilePtr);
		for (i = 0; i <= 1; i++)
		{	fputc(teleport[i].alive, JFilePtr);
			fputc(teleport[i].x, JFilePtr);
			fputc(teleport[i].y, JFilePtr);
		}
		for (x = 0; x <= FIELDX; x++)
			for (y = 0; y <= FIELDY; y++)
				fputc(board[x][y], JFilePtr);
		if (fclose(JFilePtr))
			return(FALSE);
		else
			return(TRUE);
}	}

void say(STRPTR sentence)
{	BYTE length = (BYTE) strlen(sentence);

	SetAPen(JWindowPtr->RPort, BLACK);
	RectFill(JWindowPtr->RPort, 160, 0, 320 - length * 4, 8);
	RectFill(JWindowPtr->RPort, 320 + length * 4, 0, 480, 8);
	SetAPen(JWindowPtr->RPort, WHITE);
	Move(JWindowPtr->RPort, 320 - length * 4, 7);
	Text(JWindowPtr->RPort, sentence, length);
}

void sendreadrequest(void)
{	JJoyRqPtr->io_Command	= GPD_READEVENT;
	JJoyRqPtr->io_Flags	= 0;
	JJoyRqPtr->io_Length	= sizeof(struct InputEvent);
	JJoyRqPtr->io_Data	= (APTR) &JGameEvent;
	SendIO(JJoyRqPtr);
}

void setpointer(UBYTE pointer)
{	switch (pointer)
	{	case GOLD:
			SetRGB4(&JScreenPtr->ViewPort, 18,  8,  6,  0);
			SetRGB4(&JScreenPtr->ViewPort, 19, 15, 15, 15);
			SetPointer(JWindowPtr, &JGoldPointer, 6, 4, -2, -3);
			break;
		case SILVER:
			SetRGB4(&JScreenPtr->ViewPort, 17,  5,  5,  5);
			SetRGB4(&JScreenPtr->ViewPort, 18,  3,  3,  3);
			SetRGB4(&JScreenPtr->ViewPort, 19, 15, 15, 15);
			SetPointer(JWindowPtr, &JSilverPointer, 6, 4, -2, -3);
			break;
		case EMPTY:
			SetRGB4(&JScreenPtr->ViewPort, 17,  5,  5,  5);
			SetRGB4(&JScreenPtr->ViewPort, 18,  2,  2,  2);
			SetRGB4(&JScreenPtr->ViewPort, 19,  3,  3,  3);
			SetPointer(JWindowPtr, &JEmptyPointer, 6, 4, -2, -3);
			break;
		case BRICK:
			SetRGB4(&JScreenPtr->ViewPort, 17, 11,  6, 11);
			SetRGB4(&JScreenPtr->ViewPort, 18,  9,  0,  9);
			SetPointer(JWindowPtr, &JBrickPointer, 6, 4, -2, -3);
			break;
		case WALL:
			SetRGB4(&JScreenPtr->ViewPort, 17,  0,  0,  0);
			SetRGB4(&JScreenPtr->ViewPort, 18,  3,  3,  3);
			SetRGB4(&JScreenPtr->ViewPort, 19, 15, 15, 15);
			SetPointer(JWindowPtr, &JWallPointer, 6, 4, -2, -3);
			break;
		case NORMAL:
			SetRGB4(&JScreenPtr->ViewPort, 17, 14,  4,  4);
			SetRGB4(&JScreenPtr->ViewPort, 18,  3,  3,  3);
			SetRGB4(&JScreenPtr->ViewPort, 19, 12, 12, 12);
			ClearPointer(JWindowPtr);
			break;
		default:
			break;
}	}

BYTE sign(BYTE value)
{	if (value < 0)
		return (-1);
	else if (value > 0)
		return (1);
	else
		return (0);
}

void slowloop(void)
{
	/* Purpose:	All things which must be done occasionally.
			(VERYSLOW - ie. ¼ the NORMAL rate.)

			Ticks the internal-iteration count.
			Handles level completion.
			Decrements strength of modes, ice and bias.
			Creates orbs.
			Creates killers.
			Amiga killer control.
			Object and teleport creation.
			Handles cause-blanking.

	Arguments:	None.						*/

	BOOL available;
	BYTE player;
	BYTE complete;
	BYTE i, j;
	BYTE whichfrag, whichorb;
	BYTE x, y;
	BYTE saystring[9] = "Level ";

	r++;

	// check for level completion

	for (player = lo; player <= hi; player++)
	{	complete = TRUE;
		for (i = 0; i <= LETTERS - 1; i++)
			if (letters[player][i] == FALSE)
				complete = FALSE;
		if (complete == TRUE)
		{	worm[player].score += LEVELBONUS * worm[player].multi;
			stat(player, SCORELINE);
			level++;
			stci_d(&saystring[6], level);
			say(saystring);
			if (level > LEVELS)
			{	worm[player].score += (CHAMPION + WINGAME) * worm[player].multi;
				if (worm[player].score < 10000000)
					say("*!! CHAMPION !!*");
				else
					say("*!! SUPREME CHAMPION !!*");
				stat(player, SCORELINE);
				celebrate();
				blitmode(BLACK);
				for (i = lo; i <= hi; i++)
					for (j = 0; j <= LETTERS - 1; j++)
					{	letters[i][j] = FALSE;
						draw(j - 18 + (FIELDX + 28) * worm[i].statx, (FIELDY / 2) - 2 + worm[i].staty * 2, ANYTHING);
					}
				blitmode(NORMAL);
			}
			else
			{	if (openboard(level))
					updateboard();
				else
					newboard();
				blitmode(BLACK);
				for (i = lo; i <= hi; i++)
					if (worm[i].lives > 0)
					{	for (j = 0; j <= LETTERS - 1; j++)
						{	letters[i][j] = FALSE;
							draw(j - 18 + (FIELDX + 28) * worm[i].statx, (FIELDY / 2) - 2 + worm[i].staty * 2, ANYTHING);
						}
						worm[i].x = startx;
						worm[i].y = starty;
						switch (i)
						{	case 0:
								worm[0].deltax = -1;
								worm[0].deltay = 0;
								break;
							case 1:
								worm[1].deltax = 1;
								worm[1].deltay = 0;
								break;
							case 2:
								worm[2].deltax = 0;
								worm[2].deltay = -1;
								break;
							case 3:
								worm[3].deltax = 0;
								worm[3].deltay = 1;
								break;
							default:
								break;
						}
						worm[i].oldx = worm[i].x;
						worm[i].oldy = worm[i].y;
					}
				blitmode(NORMAL);
				putletter(player);
				for (i = 0; i <= KILLERS; i++)
					if (killer[i].alive == TRUE)
					{	field[killer[i].x][killer[i].y] = KILLER;
						draw(killer[i].x, killer[i].y, KILLER);
	}	}	}		}

	// decrement strength

	for (player = lo; player <= hi; player++)
	{	if (worm[player].lives > 0 && ice == -1 || ice == player)
		{	if (worm[player].bias > 0)
			{	if (--worm[player].bias == 0)
					highlight(player, BIASLINE, FALSE);
				stat(player, BIASLINE);
			}
			if (worm[player].ice > 0)
				if (--worm[player].ice == 0)
				{	ice = -1;
					for (i = lo; i <= hi; i++)
						if (worm[i].ice > 0)
							ice = i;
				}
			if (worm[player].mode == ARMOUR)
			{	if (--worm[player].armour == 0)
				{	highlight(player, ARMOURLINE, FALSE);
					if (worm[player].eater > 0)
						worm[player].mode = EATER;
					else if (worm[player].tongue > 0)
						worm[player].mode = TONGUE;
					else
						worm[player].mode = NULL;
				}
				stat(player, ARMOURLINE);
			}
			else if (worm[player].mode == TONGUE)
			{	if (--worm[player].tongue == 0)
				{	highlight(player, TONGUELINE, FALSE);
					if (worm[player].eater > 0)
						worm[player].mode = EATER;
					else if (worm[player].armour > 0)
						worm[player].mode = ARMOUR;
					else
						worm[player].mode = NULL;
				}
				stat(player, TONGUELINE);
			}
			else if (worm[player].mode == EATER)
			{	if (--worm[player].eater == 0)
				{	highlight(player, EATERLINE, FALSE);
					if (worm[player].tongue > 0)
						worm[player].mode = TONGUE;
					else if (worm[player].armour > 0)
						worm[player].mode = ARMOUR;
					else
						worm[player].mode = NULL;
				}
				stat(player, EATERLINE);
	}	}	}
	if (ice == -1)
	{	for (whichorb = 0; whichorb <= ORBS; whichorb++)
			if (orb[whichorb].alive == TRUE)
				if (orb[whichorb].mode == ARMOUR)
				{	orb[whichorb].armour--;
					if (orb[whichorb].armour == 0)
					{	if (orb[whichorb].eater > 0)
							orb[whichorb].mode = EATER;
						else if (orb[whichorb].tongue > 0)
							orb[whichorb].mode = TONGUE;
						else
							orb[whichorb].mode = NULL;
				}	}
				else if (orb[whichorb].mode == TONGUE)
				{	orb[whichorb].tongue--;
					if (orb[whichorb].tongue == 0)
						if (orb[whichorb].eater > 0)
							orb[whichorb].mode = EATER;
						else if (orb[whichorb].armour > 0)
							orb[whichorb].mode = ARMOUR;
						else
							orb[whichorb].mode = NULL;
				}
				else if (orb[whichorb].mode == EATER)
				{	orb[whichorb].eater--;
					if (orb[whichorb].eater == 0)
						if (orb[whichorb].tongue > 0)
							orb[whichorb].mode = TONGUE;
						else if (orb[whichorb].armour > 0)
							orb[whichorb].mode = ARMOUR;
						else
							orb[whichorb].mode = NULL;
				}

		// create and control killers

		for (i = 0; i <= KILLERS; i++)
		{	if (killer[i].alive == FALSE && (rand() % KILLERFREQ) == 0)
			{	killer[i].x = rand() % (FIELDX + 1);
				killer[i].y = rand() % (FIELDY + 1);
				if (field[killer[i].x][killer[i].y] >= FIRSTHARD && field[killer[i].x][killer[i].y] <= LASTSOFT && field[killer[i].x][killer[i].y] != KILLER)
				{	killer[i].alive = TRUE;
					killer[i].last = field[killer[i].x][killer[i].y];
					field[killer[i].x][killer[i].y] = KILLER;
					draw(killer[i].x, killer[i].y, KILLER);
			}	}
			if (killer[i].alive == TRUE)
			{	x = (rand() % 3) - 1;
				y = (rand() % 3) - 1;
				if (valid(killer[i].x + x, killer[i].y + y) && (x != 0 || y != 0) && field[killer[i].x + x][killer[i].y + y] >= FIRSTHARD && field[killer[i].x + x][killer[i].y + y] <= LASTSOFT && field[killer[i].x + x][killer[i].y + y] != KILLER)
				{	field[killer[i].x][killer[i].y] = killer[i].last;
					draw(killer[i].x, killer[i].y, killer[i].last);
					killer[i].x += x;
					killer[i].y += y;
					killer[i].last = field[killer[i].x][killer[i].y];
					field[killer[i].x][killer[i].y] = KILLER;
					draw(killer[i].x, killer[i].y, KILLER);
				}
				if (killerfire[i].alive == FALSE && rand() % KILLERFIREFREQ == 0)
				{	x = (rand() % 3) - 1;
					y = (rand() % 3) - 1;
					if ((x != 0 || y != 0) && (field[killer[i].x + x][killer[i].y + y] < FIRSTHARD || field[killer[i].x + x][killer[i].y + y] > LASTSOFT))
					{	killerfire[i].alive = TRUE;
						killerfire[i].x = xwrap(killer[i].x + x);
						killerfire[i].y = ywrap(killer[i].y + y);
						killerfire[i].deltax = x;
						killerfire[i].deltay = y;
						killerfire[i].moved = FALSE;
		}	}	}	}

		// create orbs

		for (whichorb = 0; whichorb <= ORBS; whichorb++)
			if (orb[whichorb].alive == FALSE && rand() % ORBFREQ == 0)
			{	orb[whichorb].x = rand() % (FIELDX + 1);
				orb[whichorb].y = rand() % (FIELDY + 1);
				available = TRUE;
				for (whichfrag = 0; whichfrag <= 7; whichfrag++)
					if (frag[whichorb][whichfrag].alive == TRUE)
						available = FALSE;
				if (available == TRUE && field[orb[whichorb].x][orb[whichorb].y] >= FIRSTEMPTY && field[orb[whichorb].x][orb[whichorb].y] <= LASTEMPTY)
				{	orb[whichorb].deltax = (rand() % 2) * 2 - 1;
					orb[whichorb].deltay = (rand() % 2) * 2 - 1;
					if (field[orb[whichorb].x + orb[whichorb].deltay][orb[whichorb].y + orb[whichorb].deltay] >= FIRSTEMPTY && field[orb[whichorb].x + orb[whichorb].deltax][orb[whichorb].y + orb[whichorb].deltay] <= LASTEMPTY)
					{	orb[whichorb].score = KILLORB;
						orb[whichorb].alive = TRUE;
						orb[whichorb].armour = 0;
						orb[whichorb].tongue = 0;
						orb[whichorb].eater = 0;
						orb[whichorb].mode = NULL;
						orb[whichorb].speed = NORMAL;
						orb[whichorb].explode = FALSE;
						orb[whichorb].multi = 1;
						orb[whichorb].moved = FALSE;
	}		}	}	}

	// create objects

	for (i = 0; i <= LASTOBJECT; i++)
	{	if ((rand() % object[i].frequency) == 0)
		{	do
			{	x = rand() % (FIELDX + 1);
				y = rand() % (FIELDY + 1);
			} while (field[x][y] < FIRSTEMPTY || field[x][y] > LASTEMPTY);
			field[x][y] = i;
			draw(x, y, i);
	}	}

	if (rand() % TELFREQ == 0 && teleport[2].alive == FALSE && teleport[3].alive == FALSE)
	{	do
		{	teleport[2].x = rand() % (FIELDX + 1);
			teleport[2].y = rand() % (FIELDY + 1);
		} while (field[teleport[2].x][teleport[2].y] < FIRSTEMPTY || field[teleport[2].x][teleport[2].y] > LASTEMPTY);
		teleport[2].alive = TRUE;
		field[teleport[2].x][teleport[2].y] = TELEPORT;
		draw(teleport[2].x, teleport[2].y, TELEPORT);
		do
		{	teleport[3].x = rand() % (FIELDX + 1);
			teleport[3].y = rand() % (FIELDY + 1);
		} while (field[teleport[3].x][teleport[3].y] < FIRSTEMPTY || field[teleport[3].x][teleport[3].y] > LASTEMPTY);
		teleport[3].alive = TRUE;
		field[teleport[3].x][teleport[3].y] = TELEPORT;
		draw(teleport[3].x, teleport[3].y, TELEPORT);
	}

	// blank out old causes

	for (player = lo; player <= hi; player++)
	{	if (worm[player].lives > 0 && r > worm[player].causewait)
		{	blitmode(BLACK);
			draw(-2 + ((FIELDX + 4) * worm[player].statx), (FIELDY / 2 - 2) + (worm[player].staty * 2), ANYTHING);
			blitmode(NORMAL);
			worm[player].causewait = (UWORD) ~0;	// most future time possible
}	}	}

void squareblast(UBYTE type, BYTE player, BYTE x, BYTE y)
{	LONG score = 0;
	BYTE i;

	if (field[x][y] <= LASTOBJECT)
		score += object[field[x][y]].score;
	if (field[x][y] == FIREDMISSILE)
	{	for (i = lo; i <= hi; i++)
			if (missile[i].alive == TRUE && missile[i].x == x && missile[i].y == y && (type == ORB || player != i))
			{	missile[i].alive = FALSE;
				draw(x, y, EMPTY);
				field[x][y] = EMPTY;
	}		}
	else if (field[x][y] == ORB)
	{	for (i = 0; i <= ORBS; i++)
			if (orb[i].alive == TRUE && orb[i].x == x && orb[i].y == y && orb[i].mode != ARMOUR)
			{	orb[i].alive = FALSE;
				score += orb[i].score;
				draw(x, y, EMPTY);
				field[x][y] = EMPTY;
	}		}
	else if (field[x][y] >= GREENHEAD && field[x][y] <= YELLOWHEAD)
	{	for (i = lo; i <= hi; i++)
			if (worm[i].x == x && worm[i].y == y && worm[i].mode != ARMOUR && (type == ORB || player != i))
			{	worm[i].cause = BOMB;
				worm[i].alive = FALSE;
				stat(i, LIVESLINE);
				if (type == HEAD)
					worm[i].victor = player;
				else
					score += KILLWORM;
	}		}
	else if ((field[x][y] < FIRSTHARD || field[x][y] > LASTHARD) && (field[x][y] < GREENPROTECTOR || field[x][y] > YELLOWPROTECTOR) && field[x][y] != TELEPORT && field[x][y] != SKULL && field[x][y] != BRICK && field[x][y] != GOLD && field[x][y] != SILVER && (field[x][y] < FIRSTLETTER || field[x][y] > LASTLETTER))
	{	draw(x, y, EMPTY);
		field[x][y] = EMPTY;
	}
	if (type == HEAD)
		worm[player].score += score * worm[player].multi;
	else
		orb[player].score += score * orb[player].multi;
}

void stat(BYTE player, BYTE line)
{	BYTE i;
	BYTE num[8];
	BOOL print = TRUE;

	strcpy(num, "       ");
	switch (line)
	{	case SCORELINE:
			do
			{	worm[player].oldscore += (10000 - (worm[player].oldscore % 10000));
				if (worm[player].score >= worm[player].oldscore)
				{	worm[player].lives++;
					if (worm[player].lives > 50)
						highlight(player, LIVESLINE, TRUE);
					stat(player, LIVESLINE);
				}
			} while (worm[player].score > worm[player].oldscore);
			worm[player].oldscore = worm[player].score;
			if (worm[player].multi > 1)
				SetAPen(JWindowPtr->RPort, WHITE);
			else
				SetAPen(JWindowPtr->RPort, worm[player].colour);
			if (worm[player].score >= 10000000)
			{	strcpy(num, "*YEAH!*");
				Move(JWindowPtr->RPort, 80 + worm[player].statx * 504, 9 + line * 8 + worm[player].staty * 136);
				Text(JWindowPtr->RPort, num, 7);
				if (level <= LEVELS)
				{	say("*!! YOU CLOCKED TEN MILLION !!*");
					celebrate();
				}
				print = FALSE;
				break;
			}
			else
			{	stcl_d(num, worm[player].score);
				for (i = 0; i <= 6; i++)
					if (num[i] == 0)
					{	num[i] = ' ';
						break;
			}		}
			break;
		case LIVESLINE:
			if (worm[player].lives > 50)
			{	SetAPen(JWindowPtr->RPort, WHITE);
				if (worm[player].lives > 100)
					worm[player].lives = 100;
			}
			else
				SetAPen(JWindowPtr->RPort, worm[player].colour);
			stci_d(num, worm[player].lives);
			for (i = 0; i <= 6; i++)
				if (num[i] == 0)
				{	num[i] = ' ';
					break;
				}
			break;
		case BIASLINE:
			if (worm[player].bias > 0)
			{	if (worm[player].bias > 100)
					worm[player].bias = 100;
				SetAPen(JWindowPtr->RPort, WHITE);
			}
			else
				SetAPen(JWindowPtr->RPort, worm[player].colour);
			stci_d(num, worm[player].bias);
			for (i = 0; i <= 6; i++)
				if (num[i] == 0)
				{	num[i] = ' ';
					break;
				}
			break;
		case AMMOLINE:
			if (worm[player].ammo > 0)
			{	if (worm[player].ammo > 100)
					worm[player].ammo = 100;
				SetAPen(JWindowPtr->RPort, WHITE);
			}
			else
				SetAPen(JWindowPtr->RPort, worm[player].colour);
			stci_d(num, worm[player].ammo);
			for (i = 0; i <= 6; i++)
				if (num[i] == 0)
				{	num[i] = ' ';
					break;
				}
			break;
		case POWERLINE:
			switch(worm[player].power)
			{	case 0:
					SetAPen(JWindowPtr->RPort, worm[player].colour);
					strcpy(num, "Single ");
					break;
				case 2:
					SetAPen(JWindowPtr->RPort, WHITE);
					strcpy(num, "Triple ");
					break;
				case 4:
					SetAPen(JWindowPtr->RPort, WHITE);
					strcpy(num, "Quint. ");
					break;
				case 6:
					SetAPen(JWindowPtr->RPort, WHITE);
					strcpy(num, "Sept.  ");
					break;
				default:
					break;
			}
			break;
		case ARMOURLINE:
			if (worm[player].armour > 0)
			{	if (worm[player].armour > 100)
					worm[player].armour = 100;
				SetAPen(JWindowPtr->RPort, WHITE);
			}
			else
				SetAPen(JWindowPtr->RPort, worm[player].colour);
			stci_d(num, worm[player].armour);
			for (i = 0; i <= 6; i++)
				if (num[i] == 0)
				{	num[i] = ' ';
					break;
				}
			break;
		case TONGUELINE:
			if (worm[player].tongue > 0)
			{	if (worm[player].tongue > 100)
					worm[player].tongue = 100;
				SetAPen(JWindowPtr->RPort, WHITE);
			}
			else
				SetAPen(JWindowPtr->RPort, worm[player].colour);
			stci_d(num, worm[player].tongue);
			for (i = 0; i <= 6; i++)
				if (num[i] == 0)
				{	num[i] = ' ';
					break;
				}
			break;
		case EATERLINE:
			if (worm[player].eater > 0)
			{	SetAPen(JWindowPtr->RPort, WHITE);
				if (worm[player].eater > 100)
					worm[player].eater = 100;
			}
			else
				SetAPen(JWindowPtr->RPort, worm[player].colour);
			stci_d(num, worm[player].eater);
			for (i = 0; i <= 6; i++)
				if (num[i] == 0)
				{	num[i] = ' ';
					break;
				}
			break;
		case SPEEDLINE:
			if (worm[player].nitro == TRUE)
			{	SetAPen(JWindowPtr->RPort, WHITE);
				if (worm[player].speed == VERYFAST)
					strcpy(num, "V. Fast");
				else if (worm[player].speed == VERYSLOW)
					strcpy(num, "V. Slow");
			}
			else
				SetAPen(JWindowPtr->RPort, worm[player].colour);
			if (worm[player].speed == FAST)
				strcpy(num, "Fast   ");
			else if (worm[player].speed == NORMAL)
				strcpy(num, "Normal ");
			else if (worm[player].speed == SLOW)
				strcpy(num, "Slow   ");
			break;
		case DIAGONALLINE:
			if (worm[player].diagonal == TRUE)
			{	SetAPen(JWindowPtr->RPort, WHITE);
				strcpy(num, "On     ");
			}
			else
			{	SetAPen(JWindowPtr->RPort, worm[player].colour);
				strcpy(num, "Off    ");
			}
			break;
		default:
			print = FALSE;
			break;
	}

	if (print == TRUE)
	{	Move(JWindowPtr->RPort, 80 + worm[player].statx * 504, 9 + line * 8 + worm[player].staty * 136);
		Text(JWindowPtr->RPort, num, 7);
}	}

void turnworm(BYTE player, BYTE deltax, BYTE deltay)
{	if (worm[player].diagonal == TRUE || abs(deltax) == 0 || abs(deltay) == 0)
	{	if (worm[player].deltax == deltax && worm[player].deltay == deltay)
		{	worm[player].speed /= 2;
			if (worm[player].speed < FAST)
			{	if (worm[player].nitro == TRUE)
					worm[player].speed = VERYFAST;
				else
					worm[player].speed = FAST;
			}
			stat(player, SPEEDLINE);
		}
		else if (worm[player].deltax == -deltax && worm[player].deltay == -deltay)
		{	worm[player].speed *= 2;
			if (worm[player].speed > SLOW)
			{	if (worm[player].nitro == TRUE)
					worm[player].speed = VERYSLOW;
				else
					worm[player].speed = SLOW;
			}
			stat(player, SPEEDLINE);
		}
		else if (worm[player].x + deltax != worm[player].oldx || worm[player].y + deltay != worm[player].oldy)
		{	worm[player].deltax = deltax;
			worm[player].deltay = deltay;
		}
		else
		{	worm[player].xmemory = deltax;
			worm[player].ymemory = deltay;
}	}	}

void updateboard(void)
{	BYTE x, y;

	for (x = 0; x <= FIELDX; x++)
		for (y = 0; y <= FIELDY; y++)
			draw(x, y, board[x][y]);
	if (a == FIELDEDIT)
		draw(startx, starty, SKULL);
}

BYTE valid(BYTE x, BYTE y)
{	if (x >= 0 && x <= FIELDX && y >= 0 && y <= FIELDY)
		return(TRUE);
	else
		return(FALSE);
}

void wormbullet(BYTE player)
{	BOOL moved, finished;
	BYTE distance;
	UBYTE this;
	BYTE i, j;
	BYTE whichorb;

	if (worm[player].ammo == 0)
	{	stat(player, SCORELINE);
		if (worm[player].speed == FAST)
			distance = 8;
		else if (worm[player].speed == NORMAL)
			distance = 6;
		else if (worm[player].speed == SLOW)
			distance = 4;
		else if (worm[player].speed == VERYFAST)
			distance = 11;
		else
			distance = 2;
		if ((field[worm[player].x + worm[player].deltax * distance][worm[player].y + worm[player].deltay * distance] < FIRSTHARD || field[worm[player].x + worm[player].deltax * distance][worm[player].y + worm[player].deltay * distance] > LASTHARD) && abs(worm[player].deltax) <= 1 && abs(worm[player].deltay) <= 1)
		{	worm[player].deltax *= distance;
			worm[player].deltay *= distance;
	}	}
	else
	{	if (--worm[player].ammo == 0)
			highlight(player, AMMOLINE, FALSE);
		stat(player, AMMOLINE);
		if (missile[player].alive == FALSE && worm[player].bias > 0)
		{	missile[player].alive = TRUE;
			missile[player].x = worm[player].x;
			missile[player].y = worm[player].y;
			missile[player].moved = FALSE;
		}
		for (i = 0; i <= worm[player].power; i++)
		{	bullet[i].alive = TRUE;
			if (i % 2 == 0)
				distance = i / 2;
			else
				distance = -((i + 1) / 2);
			if (worm[player].deltax == 0)
			{	bullet[i].x = worm[player].x + distance;
				bullet[i].y = worm[player].y + worm[player].deltay;
			}
			else if (worm[player].deltay == 0)
			{	bullet[i].x = worm[player].x + worm[player].deltay;
				bullet[i].y = worm[player].y + distance;
			}
			else if (sign(worm[player].deltax) != sign(worm[player].deltay))
			{	bullet[i].x = worm[player].x + distance;
				bullet[i].y = worm[player].y + distance;
			}
			else
			{	bullet[i].x = worm[player].x - distance;
				bullet[i].y = worm[player].y + distance;
			}
			if (!(valid(bullet[i].x, bullet[i].y)))
				bullet[i].alive = FALSE;
		}
		moved = FALSE;
		finished = FALSE;
		while (finished == FALSE)
		{	finished = TRUE;
			for (i = 0; i <= worm[player].power; i++)
			{	if (bullet[i].alive == TRUE)
				{	finished = FALSE;
					if (moved == TRUE)
					{	draw(bullet[i].x, bullet[i].y, EMPTY);
						field[bullet[i].x][bullet[i].y] = EMPTY;
					}
					bullet[i].oldx = bullet[i].x;
					bullet[i].oldy = bullet[i].y;
					bullet[i].x += worm[player].deltax;
					bullet[i].y += worm[player].deltay;
					if (!(valid(bullet[i].x, bullet[i].y)))
						bullet[i].alive = FALSE;
					else
					{	this = field[bullet[i].x][bullet[i].y];
						if (this >= GREENHEAD && this <= YELLOWHEAD)
							if (worm[this - GREENHEAD].mode != ARMOUR)
							{	worm[this - GREENHEAD].cause = GREENFIRE + player;
								worm[this - GREENHEAD].victor = player;
								worm[this - GREENHEAD].alive = FALSE;
								worm[player].score += HITSHOT;
							}
							else
								bullet[i].alive = FALSE;
						else if (this >= GREENPROTECTOR && this <= YELLOWPROTECTOR)
						{	if (player != this - GREENPROTECTOR)
								bullet[i].alive = FALSE;
						}
						else if (this >= FIRSTLETTER && this <= LASTLETTER)
							bullet[i].alive = FALSE;
						else
							switch(this)
							{	case ORB:
									for (whichorb = 0; whichorb <= ORBS; whichorb++)
										if (orb[whichorb].alive == TRUE && orb[whichorb].x == bullet[i].x && orb[whichorb].y == bullet[i].y)
											if (orb[whichorb].mode == ARMOUR)
												bullet[i].alive = FALSE;
											else
											{	orb[whichorb].alive = FALSE;
												worm[player].score += (orb[whichorb].score + HITSHOT) * worm[player].multi;
											}
									break;
								case TELEPORT:
									for (j = 0; j <= TELEPORTS; j++)
										if (teleport[j].alive == TRUE && teleport[j].x == bullet[i].x && teleport[j].y == bullet[i].y)
										{	moved = FALSE;
											bullet[i].x = xwrap(teleport[partner(j)].x + worm[player].deltax);
											bullet[i].y = ywrap(teleport[partner(j)].y + worm[player].deltay);
											worm[player].score += TELPOINT * worm[player].multi;
										}
									break;
								case SILVER:
									worm[player].score += SILVERPOINT * worm[player].multi;
									break;
								case GOLD:
									worm[player].score += GOLDPOINT * worm[player].multi;
									break;
								case BRICK:
									if (worm[player].bias == 0)
										bullet[i].alive = FALSE;
									break;
								case WALL:
									bullet[i].alive = FALSE;
									break;
								case KILLER:
									for (j = 0; j <= KILLERS; j++)
										if (killer[j].alive == TRUE && killer[j].x == bullet[i].x && killer[j].y == bullet[i].y)
										{	killer[j].alive = FALSE;
											draw(bullet[i].x, bullet[i].y, BONUS);
											field[bullet[i].x][bullet[i].y] = BONUS;
											worm[player].score += (HITSHOT + KILLKILLER) * worm[player].multi;
										}
									bullet[i].alive = FALSE;
									break;
								case SKULL:
									worm[player].score += SKULLPOINT * worm[player].multi;
									break;
								default:
									break;
							}
						if (bullet[i].alive == TRUE)
							draw(bullet[i].x, bullet[i].y, GREENFIRE + player);
			}	}	}
			moved = TRUE;
}	}	}

void wormloop(BYTE player)
{	BYTE count, died, direction, whichorb;
	BYTE deltax, deltay, bestdeltax, bestdeltay;
	BOOL bestfire, blocked, complete, done, fire;
	BYTE i, j;
	ULONG class;
	UWORD code, qual;
	LONG points;
	UBYTE this, creature;
	BYTE thisletter, thistel;
	BYTE thisprot = -1;
	LONG worth, bestworth;
	BYTE x, xx, y, yy;

	/* human worm control

	keyboard buffer */

	if (worm[player].xmemory != 0 || worm[player].ymemory != 0)
		turnworm(player, worm[player].xmemory, worm[player].ymemory);
	worm[player].xmemory = 0;
	worm[player].ymemory = 0;

	// keyboard and mouse

	while (JMsgPtr = (struct IntuiMessage *) GetMsg(JWindowPtr->UserPort))
	{	class = JMsgPtr->Class;
		code = JMsgPtr->Code;
		qual = JMsgPtr->Qualifier;
		ReplyMsg(JMsgPtr);
		switch (class)
		{	case IDCMP_RAWKEY:
				for (i = 0; i <= NUMKEYS; i++)
				{	if (code == key[i].scancode)
					{	if (key[i].special == PAUSE)
						{	clearkybd();
							say("Paused...press P to continue");
							ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY);
							GetSysTime(JCurrentValPtr);
							done = FALSE;
							while (done == FALSE)
							{	JSignal = Wait(JSignalMask);
								if (JSignal & JSignalMask)
								{	if (JMsgPtr = (struct IntuiMessage *) GetMsg(JWindowPtr->UserPort))
									{	code = JMsgPtr->Code;
										qual = JMsgPtr->Qualifier;
										ReplyMsg(JMsgPtr);
										if (code == key[EXIT].scancode)
										{	if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
												exit(EXIT_SUCCESS);
											else
											{	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
												a = GAMEOVER;
												worm[0].lives = 0;
												worm[1].lives = 0;
												worm[2].lives = 0;
												worm[3].lives = 0;
												done = TRUE;
										}	}
										else if (code == key[MUSIC].scancode && tune == TRUE)
										{	if (music == TRUE)
												muzak(FALSE);
											else
											{	ContModule(JSongPtr);
												muzak(TRUE);
										}	}
										else if (code == key[PAUSE].scancode)
										{	ModifyIDCMP(JWindowPtr, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | CYCLEIDCMP | IDCMP_REFRESHWINDOW);
											say("");
											done = TRUE;
							}	}	}	}
							GetSysTime(JPausedValPtr);
							SubTime(JPausedValPtr, JCurrentValPtr);
							AddTime(JStartValPtr, JPausedValPtr);
						}
						else if (key[i].special == EXIT)
						{	if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
								exit(EXIT_SUCCESS);
							else
							{	a = GAMEOVER;
								worm[0].lives = 0;
								worm[1].lives = 0;
								worm[2].lives = 0;
								worm[3].lives = 0;
						}	}
						else if (key[i].special == AMMO)
						{	if (worm[key[i].player].control == HUMAN && worm[key[i].player].lives > 0)
								wormbullet(key[i].player);
							else if (key[i].player == 1 && worm[0].control == HUMAN && (worm[1].lives == 0 || worm[1].control != HUMAN))
								wormbullet(0);
							else if (key[i].player == 0 && worm[1].control == HUMAN && (worm[0].lives == 0 || worm[0].control != HUMAN))
								wormbullet(1);
						}
						else if (key[i].special == NULL)
						{	if (worm[key[i].player].control == HUMAN && worm[key[i].player].lives > 0)
								turnworm(key[i].player, key[i].deltax, key[i].deltay);
							else if (key[i].player == 1 && worm[0].control == HUMAN && worm[1].control != HUMAN)
								turnworm(0, key[i].deltax, key[i].deltay);
							else if (key[i].player == 0 && worm[1].control == HUMAN && worm[0].control != HUMAN)
								turnworm(1, key[i].deltax, key[i].deltay);
						}
						else if (key[i].special == MUSIC && tune == TRUE)
						{	if (music == TRUE)
								muzak(FALSE);
							else
							{	ContModule(JSongPtr);
								muzak(TRUE);
						}	}
						else if (key[i].special == TRAINER)
						{	/* Looks like you've discovered
							the cheat modes, guru! :-) */
							switch(key[i].scancode)
							{	case 0x4A:
									if (trainer == 1)
										trainer = 2;
									traintype = '-';
									break;
								case 0x5C:
									if (trainer == 1)
										trainer = 2;
									traintype = '/';
									break;
								case 0x5D:
									if (trainer == 1)
										trainer = 2;
									traintype = '*';
									break;
								case 0x5E:
									if (trainer == 1)
										trainer = 2;
									traintype = '+';
									break;
								case 0x5F:
									if (trainer == 0)
										trainer = 1;
									else if (trainer == 2)
									{	trainer = 0;
										switch (traintype)
										{	case '/':
												for (player = lo; player <= hi; player++)
													for (i = 0; i <= LETTERS - 1; i++)
														letters[player][i] = TRUE;
												break;
											case '*':
												for (x = 0; x <= FIELDX; x++)
													for (y = 0; y <= FIELDY; y++)
														if (field[x][y] != WALL && field[x][y] != KILLER && field[x][y] != ORB)
														{	draw(x, y, GOLD);
															field[x][y] = GOLD;
														}
												break;
											case '-':
												for (x = 0; x <= FIELDX; x++)
													for (y = 0; y <= FIELDY; y++)
														if (field[x][y] != WALL && field[x][y] != KILLER && field[x][y] != ORB)
														{	draw(x, y, EMPTY);
															field[x][y] = EMPTY;
														}
												break;
											case '+':
												for (i = lo; i <= hi; i++)
													if (worm[i].lives > 0)
													{	worm[i].lives = 100;
														stat(i, LIVESLINE);
														highlight(i, LIVESLINE, TRUE);
														worm[i].tongue = 10000;
														worm[i].mode = TONGUE;
														stat(i, TONGUELINE);
														highlight(i, TONGUELINE, TRUE);
													}
												break;
											default:
												break;
				}	}	}	}		}	}
				break;
			case IDCMP_MOUSEBUTTONS:
				if (worm[3].control == HUMAN)
				{	switch (code)
					{	case SELECTUP:
							leftdown = FALSE;
							break;
						case SELECTDOWN:
							if (leftdown == FALSE)
							{	leftdown = TRUE;
								if (xpixeltosquare(JWindowPtr->MouseX) < 0 || xpixeltosquare(JWindowPtr->MouseX) > FIELDX)
									if (ypixeltosquare(JWindowPtr->MouseY) <= FIELDY / 2)
										wormbullet(3);
									else
										turnworm(3, worm[3].deltax, worm[3].deltay);
								else
								{	for (j = 0; j <= 7; j++)
										if (worm[3].deltax == rotate[j].deltax && worm[3].deltay == rotate[j].deltay)
											direction = j;
									direction += 2;
									if (direction > 7)
										direction -= 8;
									worm[3].deltax = rotate[direction].deltax;
									worm[3].deltay = rotate[direction].deltay;
							}	}
							break;
						case MENUUP:
							rightdown = FALSE;
							break;
						case MENUDOWN:
							if (rightdown == FALSE)
							{	rightdown = TRUE;
								if (xpixeltosquare(JWindowPtr->MouseX) < 0 || xpixeltosquare(JWindowPtr->MouseX) > FIELDX)
									if (ypixeltosquare(JWindowPtr->MouseY) <= FIELDY / 2)
										wormbullet(3);
									else
										turnworm(3, -worm[3].deltax, -worm[3].deltay);
								else
								{	for (j = 0; j <= 7; j++)
										if (worm[3].deltax == rotate[j].deltax && worm[3].deltay == rotate[j].deltay)
											direction = j;
									direction -= 2;
									if (direction < 0)
										direction += 8;
									worm[3].deltax = rotate[direction].deltax;
									worm[3].deltay = rotate[direction].deltay;
							}	}
							break;
						default:
							break;
				}	}
				break;
			default:
				break;
	}	}

	// Joystick. Blue worm.

	if (worm[2].control == HUMAN && worm[2].lives > 0)
		if (GetMsg(JJoyPortPtr))
		{	if (JGameEvent.ie_position.ie_xy.ie_x != 0 || JGameEvent.ie_position.ie_xy.ie_y != 0)
				turnworm(2, JGameEvent.ie_position.ie_xy.ie_x, JGameEvent.ie_position.ie_xy.ie_y);
			if (JGameEvent.ie_Code == IECODE_LBUTTON)
				wormbullet(2);
			sendreadrequest();
		}

	// Amiga worm control

	if (worm[player].control == AMIGA)
	{	done = FALSE;
		direction = -1;
		bestworth = -32768;

		if (rand() % (AMIGASKILL * ((player + 1) * (player + 1))) == 0)
		{	if (rand() % 2 == 0)
			{	bestdeltax = (rand() % 2) * 2 - 1;
				bestdeltay = 0;
			}
			else
			{	bestdeltax = 0;
				bestdeltay = (rand() % 2) * 2 - 1;
			}
			bestworth = 32767;
			bestfire = FALSE;
			done = TRUE;
		}
		while (done == FALSE)
		{	if (++direction == 8 || worm[player].diagonal == FALSE && direction == 4)
				done = TRUE;
			else
			{	switch (direction)
				{	case 0:
						deltax = -1;
						deltay =  0;
						break;
					case 1:
						deltax =  1;
						deltay =  0;
						break;
					case 2:
						deltax =  0;
						deltay = -1;
						break;
					case 3:
						deltax =  0;
						deltay =  1;
						break;
					case 4:
						deltax = -1;
						deltay = -1;
						break;
					case 5:
						deltax =  1;
						deltay = -1;
						break;
					case 6:
						deltax =  1;
						deltay =  1;
						break;
					case 7:
						deltax = -1;
						deltay =  1;
						break;
					default:
						break;
				}
				worth = 0;
				x = worm[player].x;
				y = worm[player].y;
				count = 0;
				fire = FALSE;
				blocked = FALSE;
				if (worm[player].mode == EATER && worm[player].deltax == -deltax && worm[player].deltay == -deltay)
				{	done = TRUE;
					points = -1000;
				}
				while (done == FALSE)
				{	points = 0;
					count++;
					x = xwrap(x + deltax);
					y = ywrap(y + deltay);
					if (count > LOOKAHEAD)
						done = TRUE;
					else
					{	this = field[x][y];
						if (this >= GREENTAIL && this <= YELLOWTAIL)
						{	if (worm[player].mode == EATER)
								if (player == this - GREENTAIL)
									points = SILVER;
								else
									points = GOLD;
							else if (worm[player].mode == NULL || (worm[player].mode == ARMOUR && (player != this - GREENTAIL || lo == hi)))
								points = -1000;
						}
						else if (this >= GREENHEAD && this <= YELLOWHEAD)
						{	if (worm[player].ammo > 0 && worm[this - GREENHEAD].mode != ARMOUR && blocked == FALSE)
							{	fire = TRUE;
								points = KILLWORM + HITSHOT;
							}
							else if (worm[this - GREENHEAD].ammo > 0 && blocked == FALSE)
								points = -5000;
							else if (worm[player].mode == TONGUE || worm[player].mode == EATER)
								if (worm[this - GREENHEAD].mode != TONGUE && worm[this - GREENHEAD].mode != EATER)
									points += KILLWORM;
								else
									points += CROSSHEADS;
							else
								points = -2000;
						}
						else if (this >= GREENPROTECTOR && this <= YELLOWPROTECTOR)
						{	if (player != this - GREENPROTECTOR && worm[player].armour == FALSE)
								points = -1000;
							else
								points = EMPTYPOINT * 3;
						}
						else if (this >= FIRSTLETTER && this <= LASTLETTER)
						{	if ((this - FIRSTLETTER) % 4 == player)
								points = MYLETTER;
							else
								points = YOURLETTER;
							if (letters[player][this - FIRSTLETTER] == FALSE)
								points *= 2;
						}
						else
						{	if (this <= LASTOBJECT)
								points = object[this].score;
							switch(this)
							{	case EMPTY:
									points = EMPTYPOINT * 3;
									break;
								case ORB:
									if (worm[player].mode == ARMOUR)
									{	for (whichorb = 0; whichorb <= ORBS; whichorb++)
											if (orb[whichorb].alive == TRUE && orb[whichorb].x == worm[player].x && orb[whichorb].y == worm[player].y)
												points = orb[whichorb].score;
									}
									else
										points = -1000;
									break;
								case WALL:
									points = -3000;
									blocked = TRUE;
									break;
								case KILLER:
									if (worm[player].ammo > 0 && blocked == FALSE && count > 2)
									{	fire = TRUE;
										points = KILLKILLER;
									}
									else
										points = -3000;	// as bad as running into a wall
									blocked = TRUE;
									break;
								case FRAGMENT:
									if (worm[player].mode != ARMOUR)
										points = -1000;
									break;
								case BONUS:
									points *= 2;
									break;
								case AMMO:
									if (worm[player].bias > 0)
										points *= 4;
									else
										points *= 2;
									break;
								case ARMOUR:
									if (worm[player].bias > 0)
										points *= 4;
									else if (worm[player].mode == NULL)
										points *= 3;
									else
										points *= 2;
									break;
								case EATER:
									if (worm[player].bias > 0)
										points *= 4;
									else if (worm[player].mode == NULL)
										points *= 3;
									else
										points *= 2;
									break;
								case TONGUE:
									if (worm[player].bias > 0)
										points *= 4;
									else if (worm[player].mode == NULL)
										points *= 3;
									else
										points *= 2;
									break;
								case SLAYER:
									points *= 2;
									break;
								case NITRO:
									if (worm[player].nitro == TRUE)
										points /= 2;
									break;
								case BOMB:
									if (worm[player].bias == 0)
									{	for (i = lo; i <= hi; i++)
											if (worm[i].x >= x - BOMBADD - BOMBRAND / 2 && worm[i].x <= x + BOMBADD + BOMBRAND / 2)
												points += KILLWORM * 2;
									}
									else
									{	for (i = lo; i <= hi; i++)
											if (worm[i].x >= x - BOMBADD - BOMBRAND && worm[i].x <= x + BOMBADD + BOMBRAND)
												points += KILLWORM * 2;
										points *= 2;
									}
									break;
								case PROTECTOR:
									complete = TRUE;
									for (i = 0; i <= PROTECTORS; i++)
										if (protector[player][i].alive == FALSE)
											complete = FALSE;
									if (complete == TRUE)
										points *= 2;
									break;
								case POWER:
									if (worm[player].power < POWERMAX)
										points *= 2;
									break;
								case MISSILE:
									if (missile[player].alive == FALSE)
										points *= 2;
									break;
								case LIFE:
									if (worm[player].bias > 0)
										points *= 4;
									else
										points *= 2;
									break;
								case MULTIPLIER:
									if (worm[player].multi < 16)
										points *= 2;
									break;
								case ICE:
									points *= 2;
									break;
								case GROWER:
									points *= 2;
									break;
								case BIAS:
									if (worm[player].bias > 0)
										points *= 4;
									else
										points *= 2;
									break;
								case DIAGONAL:
									if (worm[player].diagonal == FALSE)
										points *= 2;
									break;
								case TELEPORT:
									for (i = 0; i <= TELEPORTS; i++)
										if (teleport[i].alive == TRUE && teleport[i].x == x && teleport[i].y == y)
											thistel = i;
									if (field[teleport[thistel].x + deltax][teleport[thistel].y + deltay] < FIRSTHARD || field[teleport[thistel].x + deltax][teleport[thistel].y + deltay] > LASTSOFT || ((worm[player].mode == TONGUE || worm[player].mode == EATER) && field[teleport[thistel].x + deltax][teleport[thistel].y + deltay] > LASTHARD))
										points += TELPOINT;
									break;
								case SILVER:
									points += SILVERPOINT;
									break;												
								case GOLD:
									points += GOLDPOINT;
									break;
								case SKULL:
									points += SKULLPOINT * 3;
									break;
								case FIREDMISSILE:
									if (missile[player].alive == FALSE || missile[player].x != x || missile[player].y != y)
										points = -1500;
									break;										
								case FIRE:
									if (worm[player].mode != ARMOUR)
										points = -1000;
									break;
								default:
									break;
						}	}
						points /= (count * count);
						worth += points;
				}	}
				done = FALSE;
				if (worth > bestworth)
				{	bestworth = worth;
					bestdeltax = deltax;
					bestdeltay = deltay;
					bestfire = fire;
		}	}	}
		turnworm(player, bestdeltax, bestdeltay);
		if (bestworth < -500 || bestfire == TRUE)
			wormbullet(player);
	}

	// update player positions

	worm[player].oldx = worm[player].x;
	worm[player].oldy = worm[player].y;
	worm[player].x = xwrap(worm[player].x + worm[player].deltax);
	worm[player].y = ywrap(worm[player].y + worm[player].deltay);
	worm[player].deltax = sign(worm[player].deltax);
	worm[player].deltay = sign(worm[player].deltay);
	worm[player].oldlast = worm[player].last;
	if (worm[player].mode != EATER)
		worm[player].last = GREENTAIL + player;
	else
		worm[player].last = EMPTY;

	for (i = 0; i <= PROTECTORS; i++)
	{	if (protector[player][i].alive == TRUE)
		{	if (protector[player][i].visible == TRUE)
			{	draw(protector[player][i].x, protector[player][i].y, EMPTY);
				field[protector[player][i].x][protector[player][i].y] = EMPTY;
			}
			protector[player][i].visible = TRUE;

			if (protector[player][i].relx == 1 && protector[player][i].rely == -1)
			{	protector[player][i].deltax = 0;
				protector[player][i].deltay = 1;
			}
			else if (protector[player][i].relx == 1 && protector[player][i].rely == 1)
			{	protector[player][i].deltax = -1;
				protector[player][i].deltay = 0;
			}
			else if (protector[player][i].relx == -1 && protector[player][i].rely == 1)
			{	protector[player][i].deltax = 0;
				protector[player][i].deltay = -1;
			}
			else if (protector[player][i].relx == -1 && protector[player][i].rely == -1)
			{	protector[player][i].deltax = 1;
				protector[player][i].deltay = 0;
			}
			protector[player][i].relx += protector[player][i].deltax;
			protector[player][i].rely += protector[player][i].deltay;
			protector[player][i].x = xwrap(worm[player].x + protector[player][i].relx);
			protector[player][i].y = ywrap(worm[player].y + protector[player][i].rely);
	}	}

	// collision detection

	creature = HEAD;
	while (creature != NULL)
	{	if (creature == HEAD)
		{	x = worm[player].x;
			y = worm[player].y;
		}
		else
		{	x = protector[player][thisprot].x;
			y = protector[player][thisprot].y;
		}
		this = field[x][y];

		if (this >= GREENHEAD && this <= YELLOWHEAD && player != this - GREENHEAD)
		{	died = 0;
			if (worm[this - GREENHEAD].mode != TONGUE && worm[this - GREENHEAD].mode != EATER)
			{	worm[this - GREENHEAD].cause = GREENHEAD + player;
				worm[this - GREENHEAD].alive = FALSE;
				worm[this - GREENHEAD].victor = player;
				died++;
			}
			if (worm[player].mode != TONGUE && worm[player].mode != EATER)
			{	worm[player].cause = this;
				worm[player].alive = FALSE;
				worm[player].victor = this - GREENHEAD;
				died++;
			}
			if (died == 0)
			{	worm[this - GREENHEAD].score += CROSSHEADS * worm[this - GREENHEAD].multi;
				worm[player].score += CROSSHEADS * worm[player].multi;
			}
			else if (died == 2)
			{	worm[this - GREENHEAD].victor = -1;
				worm[player].victor = -1;
		}	}
		else if (this >= GREENTAIL && this <= YELLOWTAIL)
		{	if (creature == PROTECTOR)
			{	if (player == this - GREENTAIL || worm[player].mode == EATER)
					protector[player][thisprot].visible = FALSE;
			}
			else if (worm[player].mode == EATER)
			{	if (player == this - GREENTAIL)
				{	worm[player].score += EATYOURTAIL * worm[player].multi;
					worm[player].last = SILVER;
				}
				else
				{	worm[player].score += EATOTHERTAIL * worm[player].multi;
					worm[player].last = GOLD;
			}	}
			else if (worm[player].mode != TONGUE && (worm[player].mode != ARMOUR || lo == hi || player != this - GREENTAIL))
			{	worm[player].cause = this;
				worm[player].alive = FALSE;
				worm[player].victor = this - GREENTAIL;
		}	}
		else if (this >= GREENPROTECTOR && this <= YELLOWPROTECTOR && player != this - GREENPROTECTOR)
		{	if (creature == HEAD && worm[player].mode != ARMOUR)
			{	worm[player].cause = this;
				worm[player].victor = this - GREENPROTECTOR;
				worm[player].alive = FALSE;
			}
			if (creature == PROTECTOR)
			{	protector[player][thisprot].alive = FALSE;
				for (i = 0; i <= PROTECTORS; i++)
					if (protector[this - GREENPROTECTOR][i].alive == TRUE && protector[this - GREENPROTECTOR][i].x == x && protector[this - GREENPROTECTOR][i].y == y)
						protector[this - GREENPROTECTOR][i].alive = FALSE;
				draw(x, y, EMPTY);
				field[x][y] = EMPTY;
		}	}
		else if (this >= FIRSTLETTER && this <= LASTLETTER)
		{	letters[player][this - FIRSTLETTER] = TRUE;
			draw(this - FIRSTLETTER - 18 + (FIELDX + 28) * worm[player].statx, (FIELDY / 2) - 2 + worm[player].staty * 2, this);
			if ((this - FIRSTLETTER) % 4 == player)
				worm[player].score += MYLETTER * worm[player].multi;
			else
				worm[player].score += YOURLETTER * worm[player].multi;
			putletter(player);
		}
		else
		{	if (this <= LASTOBJECT)
				worm[player].score += object[this].score * worm[player].multi;
			switch (this)
			{	case EMPTY:
					worm[player].score += EMPTYPOINT * worm[player].multi;
					break;
				case SILVER:
					worm[player].score += SILVERPOINT * worm[player].multi;
					break;
				case GOLD:
					worm[player].score += GOLDPOINT * worm[player].multi;
					break;
				case WALL:
					if (creature == PROTECTOR)
						protector[player][thisprot].visible = FALSE;
					else
					{	worm[player].cause = WALL;
						worm[player].alive = FALSE;
						worm[player].x = xwrap(worm[player].x - worm[player].deltax);
						worm[player].y = ywrap(worm[player].y - worm[player].deltay);
					}
					break;
				case BRICK:
					if (creature == PROTECTOR)
						protector[player][thisprot].visible = FALSE;
					else
						if (worm[player].mode != TONGUE && worm[player].mode != EATER)
						{	worm[player].cause = BRICK;
							worm[player].alive = FALSE;
						}
					break;
				case BONUS:
					if (worm[player].bias == 0)
						thisletter = rand() % LETTERS;
					else
					{	complete = TRUE;
						for (i = 0; i <= LETTERS - 1; i++)
							if (letters[player][i] == FALSE)
							{	complete = FALSE;
								do
									thisletter = rand() % LETTERS;
								while (letters[player][thisletter] == TRUE);
								break;
							}
						if (complete == TRUE)
							thisletter = (rand() % LETTERS);
					}
					letters[player][thisletter] = TRUE;
					draw((thisletter - 18) + (((FIELDX) + 28) * worm[player].statx), (((FIELDY) / 2) - 2) + (worm[player].staty * 2), FIRSTLETTER + thisletter);
					break;
				case AMMO:
					if (worm[player].bias > 0)
						worm[player].ammo += MAXROUNDS;
					else
						worm[player].ammo += (rand() % MAXROUNDS) + 1;
					highlight(player, AMMOLINE, TRUE);
					stat(player, AMMOLINE);
					break;
				case ARMOUR:
					if (worm[player].bias > 0)
						worm[player].armour += MODEADD + MODERAND;
					else
						worm[player].armour += MODEADD + (rand() % MODERAND) + 1;
					worm[player].mode = ARMOUR;
					highlight(player, ARMOURLINE, TRUE);
					stat(player, ARMOURLINE);
					break;
				case TONGUE:
					if (worm[player].bias > 0)
						worm[player].tongue += MODEADD + MODERAND;
					else
						worm[player].tongue += MODEADD + (rand() % MODERAND) + 1;
					worm[player].mode = TONGUE;
					highlight(player, TONGUELINE, TRUE);
					stat(player, TONGUELINE);
					break;
				case NITRO:
					worm[player].nitro = TRUE;
					highlight(player, SPEEDLINE, TRUE);
					stat(player, SPEEDLINE);
					break;
				case BOMB:
					bombblast(HEAD, player, worm[player].x, worm[player].y);
					break;
				case POWER:
					if (worm[player].power < POWERMAX)
					{	worm[player].power += 2;
						highlight(player, POWERLINE, TRUE);
						stat(player, POWERLINE);
					}
					break;
				case SLAYER:
					for (whichorb = 0; whichorb <= ORBS; whichorb++)
						if (orb[whichorb].alive == TRUE)
						{	worm[player].score += orb[whichorb].score * worm[player].multi;
							orb[whichorb].explode = TRUE;
						}
					for (i = 0; i <= KILLERS; i++)
						if (killer[i].alive == TRUE)
						{	killer[i].alive = FALSE;
							worm[player].score += KILLKILLER * worm[player].multi;
							field[killer[i].x][killer[i].y] = BONUS;
							draw(killer[i].x, killer[i].y, BONUS);
						}
					for (i = lo; i <= hi; i++)
						if (player != i && worm[i].mode != ARMOUR)
						{	worm[i].alive = FALSE;
							worm[i].cause = SLAYER;
							worm[i].victor = player;
						}
					break;
				case PROTECTOR:
					done = FALSE;
					for (i = 0; i <= PROTECTORS; i++)
						if (protector[player][i].alive == FALSE && done == FALSE)
						{	do
							{	protector[player][i].relx = (rand() % 2) * 2 - 1;	// protectors always
								protector[player][i].rely = (rand() % 2) * 2 - 1;	// born at corners
								for (j = 0; j <= PROTECTORS; j++)
									if (protector[player][j].alive == FALSE || protector[player][j].x != xwrap(worm[player].x + protector[player][i].relx) || protector[player][j].y != ywrap(worm[player].y + protector[player][i].rely))
									{	done = TRUE;
										protector[player][i].alive = TRUE;
										protector[player][i].visible = FALSE;
									}
							} while (done == FALSE);
						}
						// Note that as only 3 other protectors can be
						// at the corners we are safe from an endless loop.
					break;
				case MISSILE:
					if (missile[player].alive == FALSE)
					{	missile[player].alive = TRUE;
						missile[player].x = worm[player].x;
						missile[player].y = worm[player].y;
						missile[player].moved = FALSE;
					}
					break;
				case TELEPORT:
					if (creature == HEAD)
					{	for (i = 0; i <= TELEPORTS; i++)
							if (teleport[i].alive == TRUE && worm[player].x == teleport[i].x && worm[player].y == teleport[i].y)
							{	worm[player].score += TELPOINT * worm[player].multi;
								worm[player].x = xwrap(teleport[partner(i)].x + worm[player].deltax);
								worm[player].y = ywrap(teleport[partner(i)].y + worm[player].deltay);
					}		}
					else
					{	protector[player][thisprot].visible = FALSE;
						worm[player].score += TELPOINT * worm[player].multi;
						stat(player, SCORELINE);
					}
					break;
				case LIFE:
					if (worm[player].bias == 0)
						worm[player].lives += (rand() % MAXLIVES) + 1;
					else
						worm[player].lives += MAXLIVES;
					if (worm[player].lives > 50)
						highlight(player, LIVESLINE, TRUE);
					stat(player, LIVESLINE);
					break;
				case MULTIPLIER:
					if (worm[player].multi < 16)
					{	worm[player].multi *= 2;
						highlight(player, SCORELINE, TRUE);
					}
					break;
				case EATER:
					if (worm[player].bias > 0)
						worm[player].eater += MODEADD + MODERAND;
					else
						worm[player].eater += MODEADD + (rand() % MODERAND) + 1;
					worm[player].mode = EATER;
					worm[player].oldlast = EMPTY;
					worm[player].last = EMPTY;
					highlight(player, EATERLINE, TRUE);
					stat(player, EATERLINE);
					break;
				case BIAS:
					if (worm[player].bias > 0)
						worm[player].bias += MODEADD + MODERAND;
					else
						worm[player].bias += MODEADD + (rand() % MODERAND) + 1;
					highlight(player, BIASLINE, TRUE);
					stat(player, BIASLINE);
					break;
				case ICE:
					if (worm[player].bias > 0)
						worm[player].ice += ICEADD + ICERAND;
					else
						worm[player].ice += ICEADD + (rand() % ICERAND) + 1;
					ice = player;
					break;
				case DIAGONAL:
					worm[player].diagonal = TRUE;
					highlight(player, DIAGONALLINE, TRUE);
					stat(player, DIAGONALLINE);
					break;
				case GROWER:
					for (x = 0; x <= FIELDX; x++)
						for (y = 0; y <= FIELDY; y++)
							if (field[x][y] == GOLD || field[x][y] == SILVER)
								for (xx = x - 1; xx <= x + 1; xx++)
									for (yy = y - 1; yy <= y + 1; yy++)
										if ((valid(xx, yy)) && field[xx][yy] == EMPTY)
											if (field[x][y] == GOLD)
												field[xx][yy] = TEMPGOLD;
											else
												field[xx][yy] = TEMPSILVER;
					for (x = 0; x <= FIELDX; x++)
						for (y = 0; y <= FIELDY; y++)
							if (field[x][y] == TEMPGOLD)
							{	field[x][y] = GOLD;
								draw(x, y, GOLD);
							}
							else if (field[x][y] == TEMPSILVER)
							{	field[x][y] = SILVER;
								draw(x, y, SILVER);
							}
					break;
				case FIREDMISSILE:
					for (i = lo; i <= hi; i++)
						if (missile[i].alive == TRUE && worm[player].x == missile[i].x && worm[player].y == missile[i].y)
							if (worm[player].mode == ARMOUR || creature == PROTECTOR)
								missile[i].alive = FALSE;
							else
							{	worm[player].alive = FALSE;
								worm[player].cause = MISSILE;
								worm[player].victor = i;
							}
					break;
				case ORB:
					if (worm[player].mode == ARMOUR || creature == PROTECTOR)
					{	for (whichorb = 0; whichorb <= ORBS; whichorb++)
						{	if (orb[whichorb].alive == TRUE && x == orb[whichorb].x && y == orb[whichorb].y)
							{	worm[player].score += orb[whichorb].score * worm[player].multi;
								orb[whichorb].alive = FALSE;
					}	}	}
					else
					{	for (whichorb = 0; whichorb <= ORBS; whichorb++)
							if (orb[whichorb].alive == TRUE && x == orb[whichorb].x && y == orb[whichorb].y)
								if (orb[whichorb].mode == ARMOUR)
									orb[whichorb].score += KILLWORM * orb[whichorb].multi;
								else
									orb[whichorb].alive = FALSE;
						worm[player].cause = ORB;
						worm[player].alive = FALSE;
					}
					break;
				case FRAGMENT:
					for (i = 0; i <= ORBS; i++)
						for (j = 0; j <= 7; j++)
							if (frag[i][j].alive == TRUE && frag[i][j].x == x && frag[i][j].y == y)
							{	frag[i][j].alive = FALSE;
								if (worm[player].mode != ARMOUR || creature == PROTECTOR)
								{	worm[player].cause = FRAGMENT;
									worm[player].alive = FALSE;
							}	}
					break;
				case FIRE:
					if (creature == HEAD && worm[player].mode != ARMOUR)
					{	worm[player].cause = FIRE;
						worm[player].alive = FALSE;
					}
					for (i = 0; i <= KILLERS; i++)
						if (killerfire[i].alive == TRUE && killerfire[i].x == x && killerfire[i].y == y)
							killerfire[i].alive = FALSE;
					break;
				case KILLER:
					if (creature == HEAD)
					{	worm[player].cause = KILLER;
						worm[player].alive = FALSE;
						worm[player].x = xwrap(worm[player].x - worm[player].deltax);
						worm[player].y = ywrap(worm[player].y - worm[player].deltay);
					}
					else
					{	protector[player][thisprot].visible = FALSE;
						if (worm[player].bias > 0)
							for (i = 0; i <= KILLERS; i++)
								if (killer[i].alive == TRUE && x == x && killer[i].y == y)
								{	killer[i].alive = FALSE;
									draw(killer[i].x, killer[i].y, BONUS);
									field[killer[i].x][killer[i].y] = BONUS;
					}			}
					break;
				case SKULL:
					worm[player].score += SKULLPOINT * worm[player].multi;
					for (i = lo; i <= hi; i++)
					{	if (worm[i].alive == FALSE && worm[i].x == worm[player].x && worm[i].y == worm[player].y)
							j = i;
					}
					worm[player].bias	+= worm[j].bias;
					if (worm[player].bias > 0)
					{	highlight(player, BIASLINE, TRUE);
						stat(player, BIASLINE);
					}
					worm[player].multi	*= worm[j].multi;
					if (worm[player].multi > 1)
					{	highlight(player, SCORELINE, TRUE);
						if (worm[player].multi > 16)
							worm[player].multi = 16;
						stat(player, SCORELINE);
					}
					worm[player].power	+= worm[j].power;
					if (worm[player].power > 1)
					{	highlight(player, POWERLINE, TRUE);
						if (worm[player].power > POWERMAX)
							worm[player].power = POWERMAX;
						stat(player, POWERLINE);
					}
					worm[player].ammo	+= worm[j].ammo;
					if (worm[player].ammo > 0)
					{	highlight(player, AMMOLINE, TRUE);
						stat(player, AMMOLINE);
					}
					worm[player].armour	+= worm[j].armour;
					if (worm[player].armour > 0)
					{	highlight(player, ARMOURLINE, TRUE);
						stat(player, ARMOURLINE);
					}
					worm[player].tongue	+= worm[j].tongue;
					if (worm[player].tongue > 0)
					{	highlight(player, TONGUELINE, TRUE);
						stat(player, TONGUELINE);
					}
					worm[player].eater	+= worm[j].eater;
					if (worm[player].eater > 0)
					{	highlight(player, EATERLINE, TRUE);
						stat(player, EATERLINE);
					}
					if (worm[player].armour > 0 || worm[player].tongue > 0 || worm[player].eater > 0)
					{	if (worm[player].armour >= worm[player].tongue && worm[player].armour >= worm[player].eater)
							worm[player].mode = ARMOUR;
						else if (worm[player].tongue >= worm[player].eater)
							worm[player].mode = TONGUE;
						else
							worm[player].mode = EATER;
					}
					if (worm[j].nitro == TRUE)
					{	worm[player].nitro = TRUE;
						highlight(player, SPEEDLINE, TRUE);
						stat(player, SPEEDLINE);
					}
					if (worm[j].diagonal == TRUE)
					{	worm[player].diagonal = TRUE;
						highlight(player, DIAGONALLINE, TRUE);
						stat(player, DIAGONALLINE);
					}
					for (i = 0; i <= LETTERS - 1; i++)
						if (letters[j][i] == TRUE && letters[player][i] == FALSE)
						{	letters[player][i] = TRUE;
							draw((i - 18) + (((FIELDX) + 28) * worm[player].statx), (((FIELDY) / 2) - 2) + (worm[player].staty * 2), FIRSTLETTER + i);
						}
					break;
				default:
					break;
		}	}

		if (creature == PROTECTOR)
		{	if (protector[player][thisprot].alive == TRUE && protector[player][thisprot].visible == TRUE)
			{	field[protector[player][thisprot].x][protector[player][thisprot].y] = GREENPROTECTOR + player;
				draw(protector[player][thisprot].x, protector[player][thisprot].y, GREENPROTECTOR + player);
			}
			done = TRUE;
			i = thisprot;
			while (done == TRUE && i <= PROTECTORS - 1)
			{	i++;
				if (protector[player][i].alive == TRUE)
				{	done = FALSE;
					thisprot = i;
			}	}
			if (done == TRUE)
				creature = NULL;
		}
		if (creature == HEAD)
		{	thisprot = -1;
			done = TRUE;
			i = thisprot;
			while (done == TRUE && i <= PROTECTORS - 1)
			{	i++;
				if (protector[player][i].alive == TRUE)
				{	done = FALSE;
					thisprot = i;
			}	}
			if (done == FALSE)
				creature = PROTECTOR;
			else
				creature = NULL;
			draw(worm[player].oldx, worm[player].oldy, worm[player].oldlast);
			field[worm[player].oldx][worm[player].oldy] = worm[player].oldlast;
			field[worm[player].x][worm[player].y] = GREENHEAD + player;
			if (worm[player].alive == TRUE)
			{	switch (worm[player].mode)
				{
				case NULL:
					draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
					break;
				case EATER:
					if (worm[player].eater < 10 && r % 2 == 0)
						blitmode(WHITE);
					else
						blitmode(NORMAL);
					draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
					blitmode(NORMAL);
					break;
				case TONGUE:
					if (worm[player].tongue < 10 && r % 2 == 0)
						blitmode(WHITE);
					else
						blitmode(NORMAL);
					draw(worm[player].x, worm[player].y, eachworm[player][2][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
					blitmode(NORMAL);
					break;
				case ARMOUR:
					if (worm[player].armour < 10 && r % 2 == 0)
						blitmode(WHITE);
					else
						blitmode(NORMAL);
					draw(worm[player].x, worm[player].y, eachworm[player][3][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
					blitmode(NORMAL);
					break;
				default:
					break;
			}	}
			else
				draw(worm[player].x, worm[player].y, SKULL);
}	}	}

/* Purpose:	These two functions are crucial for mouse support.
		They can convert between x and y pixels and squares.
		There are separate x and y functions because of the
		C limitation of only one return parameter.

Notes:		The results are not bounds-checked, deliberately.

Arguments:	x or y coordinate, as appropriate, in pixels.

Results:	x or y coordinate, as appropriate, in squares.		*/

WORD xpixeltosquare(WORD x)
{	x = (x - STARTXPIXEL) / X;
	if (x < 0)
		x--;
	return (x);
}
WORD ypixeltosquare(WORD y)
{	y = (y - STARTYPIXEL) / Y;
	if (y < 0)
		y--;
	return (y);
}

/* Purpose:	These two functions are for coordinates which 'wrap'.
		They take a coordinate and convert it into a legal
		field position. There are separate x and y functions
		because of the C limitation of only one return parameter.

Arguments:	x or y coordinate, as appropriate, in squares.

Results:	x or y coordinate, as appropriate, in squares.		*/

BYTE xwrap(BYTE x)
{	if (x < 0)
		x += FIELDX + 1;
	else if (x > FIELDX)
		x -= FIELDX + 1;
	return(x);
}
BYTE ywrap(BYTE y)
{	if (y < 0)
		y += FIELDY + 1;
	else if (y > FIELDY)
		y -= FIELDY + 1;
	return(y);
}

// Must have blank line at EOF.
