/* $Filename: WormWars/Source/engine.c
 * $VER:      WormWars 6.6
 *
 * © Copyright 2001 James R. Jacobs. Freely distributable.
 */

#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h> /* EXIT_SUCCESS, EXIT_FAILURE */
#include <assert.h>
#define ASSERT

#include "stdafx.h"
#include "diff.h"
#include "same.h"
#include "engine.h"

/* PRIVATE STRUCTURES -------------------------------------------------- */

struct
{   SBYTE x, y, deltax, deltay;
    ABOOL alive, moved, teleported, visible, reflected;
} bullet[9];
struct
{   UWORD freq;
    ULONG score;
} object[LASTOBJECT + 1] =
{   {1280,  60}, // AFFIXER
    {  60,  20}, // AMMO
    { 110,  20}, // ARMOUR
    {  70,  50}, // BIAS
    { 150,  30}, // BOMB
    {  80,  10}, // BONUS
    {1020,  60}, // CLOCK
    { 380,  50}, // CONVERTER
    { 160,  80}, // CUTTER
    { 250,  90}, // CYCLONE
    { 500,  30}, // FREEDOM
    { 320,  50}, // GROWER
    {1900,  90}, // HEALER
    {1360,  60}, // ICE
    { 140,  60}, // LIFE
    { 160,  80}, // LIGHTNING
    { 970,  80}, // MAGNET
    { 240,  40}, // MISSILE
    { 640,  50}, // MULTIPLIER
    { 400,  10}, // NITRO
    { 240,  30}, // POWER
    { 480,  50}, // PROTECTOR
    { 210,  40}, // PULSE
    { 300,  50}, // PUSHER
    { 400,  40}, // REMNANTS
    { 500,  30}, // SIDESHOT
    { 600,  40}, // SLAYER
    { 980,  40}, // SLOWER
    { 730,  70}, // SWITCHER
    { 320,  20}, // TONGUE
    {1400, 120}, // TREASURE
    {3800, 140}  // UMBRELLA
};

/*  -200    common
    220-400 uncommon
    420-980 rare
    1000+   very rare */

struct
{   SBYTE x, y, deltax, deltay, relx, rely;
    ABOOL alive, last, visible;
} protector[4][PROTECTORS + 1];
struct
{   SBYTE deltax, deltay;
} thewormqueue[4][WORMQUEUELIMIT + 1];
struct
{   SBYTE deltax, deltay;
} thedogqueue[CREATURES + 1][DOGQUEUELIMIT + 1];
struct
{   ABOOL alive;
    SBYTE x, y, player;
    UBYTE object;
} magnet[MAGNETS + 1];

SBYTE eachworm[4][2][9] =
{ { { GREENHEADUP,   GREENHEADUP,   GREENHEADUP,
      GREENHEADLEFT, ANYTHING,      GREENHEADRIGHT,
      GREENHEADDOWN, GREENHEADDOWN, GREENHEADDOWN
    },
    { GREENMODEUP,   GREENMODEUP,   GREENMODEUP,
      GREENMODELEFT, ANYTHING,      GREENMODERIGHT,
      GREENMODEDOWN, GREENMODEDOWN, GREENMODEDOWN
  } },
  { { REDHEADUP,     REDHEADUP,     REDHEADUP,
      REDHEADLEFT,   ANYTHING,      REDHEADRIGHT,
      REDHEADDOWN,   REDHEADDOWN,   REDHEADDOWN
    },
    { REDMODEUP,     REDMODEUP,     REDMODEUP,
      REDMODELEFT,   ANYTHING,      REDMODERIGHT,
      REDMODEDOWN,   REDMODEDOWN,   REDMODEDOWN
  } },
  { { BLUEHEADUP,    BLUEHEADUP,    BLUEHEADUP,
      BLUEHEADLEFT,  ANYTHING,      BLUEHEADRIGHT,
      BLUEHEADDOWN,  BLUEHEADDOWN,  BLUEHEADDOWN
    },
    { BLUEMODEUP,    BLUEMODEUP,    BLUEMODEUP,
      BLUEMODELEFT,  ANYTHING,      BLUEMODERIGHT,
      BLUEMODEDOWN,  BLUEMODEDOWN,  BLUEMODEDOWN
  } },
  { { YELLOWHEADUP,  YELLOWHEADUP,  YELLOWHEADUP,
      YELLOWHEADLEFT,ANYTHING,      YELLOWHEADRIGHT,
      YELLOWHEADDOWN,YELLOWHEADDOWN,YELLOWHEADDOWN
    },
    { YELLOWMODEUP,  YELLOWMODEUP,  YELLOWMODEUP,
      YELLOWMODELEFT,ANYTHING,      YELLOWMODERIGHT,
      YELLOWMODEDOWN,YELLOWMODEDOWN,YELLOWMODEDOWN
} } };

/* Rules for variable types:

SBYTE is used for field coordinates and queue indexes
UBYTE is used for field contents
SWORD is used for frequencies
ULONG is used for scores */

struct
{   ABOOL alive, explode, visible;
    SBYTE x, y, deltax, deltay, pos, time, dir;
    UBYTE mode, dormant, multi, speed, last, oldlast, species,
          type; // type is relevant worm 0-3 (for drips, missiles and dogs)
    SWORD armour, tongue, freq;
    ULONG score;
} creature[CREATURES + 1];

// MODULE VARIABLES (used only within engine.c) ---------------------------

MODULE ABOOL banging = FALSE, letters[4][LETTERS + 1], trainer;
MODULE SBYTE freq, ice, lettertype, letterx, lettery, leveltype,
             treasurer;
MODULE UBYTE infector[FIELDX + 1][FIELDY + 1];
MODULE ULONG octopusspeed;

// GLOBAL VARIABLES (owned by engine.c, imported by system.c) -------------

ABOOL clearthem  = FALSE,
      modified   = FALSE,
      randomflag = FALSE,
      randomarray[MAXLEVELS + 1];
UBYTE board[MAXLEVELS + 1][FIELDX + 1][FIELDY + 1],
      field[FIELDX + 1][FIELDY + 1];
SBYTE a = GAMEOVER,
      players,
      level = 1, levels, reallevel, sourcelevel,
      startx[MAXLEVELS + 1], starty[MAXLEVELS + 1];
SWORD secondsleft, secondsperlevel;
TEXT  pathname[81],
      date[DATELENGTH + 1],
      times[TIMELENGTH + 1];
ULONG delay, r;

AGLOBAL struct HiScoreStruct  hiscore[HISCORES + 1];
AGLOBAL struct WormStruct     worm[4];
AGLOBAL struct TeleportStruct teleport[MAXLEVELS + 1][4];

/* FUNCTIONS --------------------------------------------------------------

NAME        align -- right-justify a string within another string
SYNOPSIS    align(STRPTR, SBYTE, TEXT);
FUNCTION    Moves all text in a string to the right, padding with
            spaces. Does not itself add a null terminator.
INPUTS      string - pointer to the string of text
              size - size in characters of the containing string
            filler - what to pad the left of the string with
NOTE        Null terminators are written over by this function, but that
            does not matter, because calling functions use Text() with an
            explicit length. This function only works with monospaced
            fonts.
MODULE      engine.c */

void align(STRPTR string, SBYTE size, TEXT filler)
{   SBYTE i, shift, length;

    length = strlen((const char*) string);
    shift = size - length;
    for (i = 1; i <= length; i++)
        *(string + size - i) = *(string + size - i - shift);
    for (i = 0; i <= shift - 1; i++)
        *(string + i) = filler;
}

MODULE ABOOL blocked(SBYTE which, SBYTE deltax, SBYTE deltay)
{   UBYTE c = field[xwrap(teleport[level][partner(which)].x + deltax)][ywrap(teleport[level][partner(which)].y + deltay)];
    if ((c < STONE || c > GOAT) && c != METAL)
        return FALSE;
    else return TRUE;
}

MODULE void bombblast(SBYTE triggerer, SBYTE player, SBYTE centrex, SBYTE centrey)
{   SBYTE counter, downy, downymax, leftx, leftxmax, rightx, rightxmax, strength, uppy, uppymax, x, y;
    ULONG score = 0;

    effect(FXUSE_BOMB);

    strength = BOMBADD + (rand() % 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++)
                score += squareblast(triggerer, player, field[leftx][y], leftx, y, FALSE);
        }
		if (rightx < rightxmax)
		{	rightx++;
			for (y = uppy; y <= downy; y++)
				score += squareblast(triggerer, player, field[rightx][y], rightx, y, FALSE);
		}
		if (uppy > uppymax)
		{	uppy--;
			for (x = leftx; x <= rightx; x++)
				score += squareblast(triggerer, player, field[x][uppy], x, uppy, FALSE);
		}
		if (downy < downymax)
		{	downy++;
			for (x = leftx; x <= rightx; x++)
				score += squareblast(triggerer, player, field[x][downy], x, downy, FALSE);
	}	}

	if (triggerer == HEAD)
        wormscore(player, score);
    elif (triggerer == ORB)
        orbscore(player, score);
    if (worm[player].bias)
        stat(player, LIFE);
}

MODULE void bouncegoat(SBYTE which, SBYTE x, SBYTE y)
{   if (field[x][y] == GOAT)
    {   creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
        orbscore(which, KILLGOAT);
        change(x, y, BONUS);
}   }

MODULE ABOOL bounceorb(SBYTE which, SBYTE x, SBYTE y)
{   if (field[x][y] == METAL)
        return TRUE;
    elif (creature[which].mode == NONE)
    {   if (field[x][y] >= FIRSTNONE && field[x][y] <= LASTNONE)
            return TRUE;
        else return FALSE;
    } elif (creature[which].mode == TONGUE)
    {   if (field[x][y] >= FIRSTTONGUE && field[x][y] <= LASTTONGUE)
            return TRUE;
        else return FALSE;
    } else
    {   // assert(creature[which].mode == ARMOUR);
        if (field[x][y] >= FIRSTARMOUR && field[x][y] <= LASTARMOUR)
            return TRUE;
        else return FALSE;
}   }

MODULE SBYTE bsign(SBYTE value)
{   if (value < 0)
        return (-1);
    elif (value > 0)
        return (1);
    else return (0);
}

MODULE void changefield(void)
{   SBYTE x, y;

    if (randomflag && a == PLAYGAME && level)
    {   do
        {    sourcelevel = (rand() % levels) + 1;
        } while (randomarray[level]);
        randomarray[level] = TRUE;
    } else sourcelevel = level;

    for (x = 0; x <= FIELDX; x++)
        for (y = 0; y <= FIELDY; y++)
            field[x][y] = board[sourcelevel][x][y];
}

void clearhiscores(void)
{   SBYTE i;
	
    clearthem = FALSE;
    for (i = 0; i <= HISCORES; i++)
    {   hiscore[i].player = -1;
        hiscore[i].level = 0;
        hiscore[i].score = 0;
        hiscore[i].fresh = FALSE;
        hiscore[i].name[0] = 0;
        hiscore[i].time[0] = 0;
        hiscore[i].date[0] = 0;
}   }

MODULE void clearletters(void)
{   SBYTE player, which;

    for (player = 0; player <= 3; player++)
        for (which = 0; which <= LETTERS; which++)
        {   letters[player][which] = FALSE;
            drawletter(player, FIRSTLETTER + which, BLACK);
}       }

MODULE void copyfield(SBYTE source, SBYTE destination)
{   SBYTE which, x, y;

    for (x = 0; x <= FIELDX; x++)
        for (y = 0; y <= FIELDY; y++)
            board[destination][x][y] = board[source][x][y];
    startx[destination] = startx[source];
    starty[destination] = starty[source];
    for (which = 0; which <= 1; which++)
    {   teleport[destination][which].alive = teleport[source][which].alive;
        teleport[destination][which].x     = teleport[source][which].x;
        teleport[destination][which].y     = teleport[source][which].y;
}   }

MODULE void death(void)
{   SBYTE pain, player, which;
    ABOOL slow;

    for (player = 0; player <= 3; player++)
    {   if (worm[player].lives)
        {   if (!worm[player].alive)
            {   slow = FALSE;
                pain = 0;
                if (worm[player].cause >= FIRSTTAIL && worm[player].cause <= LASTTAIL)
                {   if (player == worm[player].cause - FIRSTTAIL)
                        pain = TAILPAIN;
                    else pain = OTHERTAILPAIN;
                    slow = TRUE;
                } elif (worm[player].cause >= FIRSTFIRE && worm[player].cause <= LASTFIRE)
                    pain = WORMFIREPAIN;
                elif (worm[player].cause >= FIRSTHEAD && worm[player].cause <= LASTHEAD)
                    pain = HEADPAIN;
                elif (worm[player].cause >= FIRSTPROTECTOR && worm[player].cause <= LASTPROTECTOR)
                    pain = PROTECTORPAIN;
                elif (worm[player].cause >= FIRSTMISSILE && worm[player].cause <= LASTMISSILE)
                    pain = MISSILEPAIN;
                elif (worm[player].cause >= FIRSTDRIP && worm[player].cause <= LASTDRIP)
                    pain = DRIPPAIN;
                else switch (worm[player].cause)
                {
                case BOMB:
                    pain = BOMBPAIN;
                break;
                case WOOD:
                    pain = WOODPAIN;
                    slow = TRUE;
                break;
                case FRAGMENT:
                    pain = FRAGMENTPAIN;
                break;
                case GOAT:
                    pain = GOATPAIN;
                    slow = TRUE;
                break;
                case OCTOPUS:
                    pain = OCTOPUSPAIN;
                    slow = TRUE;
                break;
                case SLAYER:
                    pain = SLAYERPAIN;
                break;
                case STONE:
                    pain = STONEPAIN;
                    slow = TRUE;
                break;
                case TELEPORT:
                    pain = TELEPORTPAIN;
                    slow = TRUE;
                break;
                case SLIME:
                    pain = SLIMEPAIN;
                    slow = TRUE;
                break;
                case METAL:
                    pain = METALPAIN;
                    slow = TRUE;
                break;
                case REMNANTS:
                    pain = REMNANTPAIN;
                    slow = TRUE;
                break;
                case LIGHTNING:
                    pain = LIGHTNINGPAIN;
                break;
                case PENGUIN:
                    pain = PENGUINPAIN;
                break;
                case WHIRLWIND:
                    pain = WHIRLWINDPAIN;
                break;
                case DOG:
                    pain = DOGPAIN;
                break;
                case CLOUD:
                    pain = CLOUDPAIN;
                break;
                case ORB:
                    pain = ORBPAIN;
                break;
                default:
                    // assert(0);
                break;
                }
                if (worm[player].victor >= 0 && worm[player].victor != player)
                {   wormscore(worm[player].victor, KILLWORM);
                    if (worm[worm[player].victor].bias)
                    {   worm[worm[player].victor].lives += pain;
                        stat(worm[player].victor, LIFE);
                }   }
                if (slow)
                {   worm[player].speed = slowdown(worm[player].speed, worm[player].nitro);
                    stat(player, NITRO); 
                }
                if (pain > worm[player].lives)
                    worm[player].lives = 0;
                else worm[player].lives -= pain;
                draw(worm[player].x, worm[player].y, SKULL);
                drawcause(player, NORMAL);
                stat(player, LIFE);
                if (level)
                    worm[player].levelreached = level;
                else worm[player].levelreached = reallevel;
                if (worm[player].lives)
                {   effect(FXPAIN + player);
                    worm[player].alive = TRUE;
                    worm[player].causewait = r + CAUSEWAIT;
                } else
                {   /* kill worm */
                    effect(FXDEATH_WORM);
                    if (ice == player)
                        ice = -1;
                    field[worm[player].x][worm[player].y] = SKULL;
                    updatearrow(worm[player].y);
                    for (which = 0; which <= PROTECTORS; which++)
                        if (protector[player][which].alive && protector[player][which].visible)
                            change(protector[player][which].x, protector[player][which].y, EMPTY);
                    for (which = 0; which <= MAGNETS; which++)
                        if (magnet[which].player == player)
                            magnet[which].alive = FALSE;
                    if (worm[player].score >= worm[player].hiscore)
                        worm[player].hiscore = worm[player].score;
    }   }   }   }

    if (!worm[0].lives && !worm[1].lives && !worm[2].lives && !worm[3].lives)
    {   /* End of game */
        for (player = 0; player <= 3; player++)
            if (worm[player].control != NONE && worm[player].score >= worm[player].hiscore)
                worm[player].hiscore = worm[player].score;
        newhiscores();
        effect(FXGAMEOVER);
        a = GAMEOVER;
        if (players == 1)
            say((STRPTR) "Game over!", worm[onlyworm(FALSE)].colour);
        elif (worm[0].control && ((!worm[1].control) || worm[1].score < worm[0].score) && ((!worm[2].control) || worm[2].score < worm[0].score) && ((!worm[3].control) || worm[3].score < worm[0].score))
            say((STRPTR) "Green wins!", GREEN);
        elif (worm[1].control && ((!worm[0].control) || worm[0].score < worm[1].score) && ((!worm[2].control) || worm[2].score < worm[1].score) && ((!worm[3].control) || worm[3].score < worm[1].score))
            say((STRPTR) "Red wins!", RED);
        elif (worm[2].control && ((!worm[0].control) || worm[0].score < worm[2].score) && ((!worm[1].control) || worm[1].score < worm[2].score) && ((!worm[3].control) || worm[3].score < worm[2].score))
            say((STRPTR) "Blue wins!", BLUE);
        elif (worm[3].control && ((!worm[0].control) || worm[0].score < worm[3].score) && ((!worm[1].control) || worm[1].score < worm[3].score) && ((!worm[2].control) || worm[2].score < worm[3].score))
            say((STRPTR) "Yellow wins!", YELLOW);
        else say((STRPTR) "A draw!", WHITE);
        waitasec();
        anykey(FALSE);
}   }

MODULE void drawcause(SBYTE player, SBYTE state)
{	if (state == BLACK)
                draw(-2 + ((FIELDX + 5) * worm[player].statx), (FIELDY / 2) - CAUSEYDISTANCE + (worm[player].staty * CAUSEYDISTANCE * 2), BLACKENED);
        else draw(-2 + ((FIELDX + 5) * worm[player].statx), (FIELDY / 2) - CAUSEYDISTANCE + (worm[player].staty * CAUSEYDISTANCE * 2), worm[player].cause);
}

MODULE void drawletter(SBYTE player, SBYTE letter, SBYTE state)
{   UBYTE c;
	
    if (state == BLACK)
        c = BLACKENED;
    else c = letter;
    if (!worm[player].statx)
        if (!worm[player].staty)
            draw(-7 + ((letter - FIRSTLETTER) % 4),
                 (FIELDY / 2) - 2 + ((letter - FIRSTLETTER) / 4),
                 c);
        else
            draw(-7 + ((letter - FIRSTLETTER) % 4),
                 (FIELDY / 2) + 1 + ((letter - FIRSTLETTER) / 4),
                 c);
    elif (!worm[player].staty)
        draw(FIELDX + 5 + ((letter - FIRSTLETTER) % 4),
             (FIELDY / 2) - 2 + ((letter - FIRSTLETTER) / 4),
             c);
    else
        draw(FIELDX + 5 + ((letter - FIRSTLETTER) % 4),
             (FIELDY / 2) + 1 + ((letter - FIRSTLETTER) / 4),
             c);
}

/* NAME     enginesetup -- once-only initialization of engine variables
SYNOPSIS    enginesetup(void);
FUNCTION    Sets up the unchanging worm variables.
MODULE      engine.c */

void enginesetup(void)
{   worm[0].statx = worm[0].staty = worm[1].staty = worm[2].statx = 0;
    worm[1].statx = worm[2].staty = worm[3].statx = worm[3].staty = 1;
    worm[0].colour = GREEN;
    worm[1].colour = RED;
    worm[2].colour = BLUE;
    worm[3].colour = YELLOW;
    worm[0].name[0] = worm[1].name[0] = worm[2].name[0] = worm[3].name[0] = 0;
    worm[0].dynamitescore =
    worm[1].dynamitescore =
    worm[2].dynamitescore =
    worm[3].dynamitescore = 0;

    strcpy(pathname, DEFAULTSET);

    systemsetup();
}

/* NAME     fastloop -- things done often
SYNOPSIS    fastloop(void);
FUNCTION    Checks for and handles level completion.
MODULE      engine.c */

MODULE void fastloop(void)
{   AUTO ABOOL complete;
    AUTO SBYTE advancer = -1, i, player, which, x, y;

    /* update joystick `1' */
    joy0();
    /* update joysticks `3' and `4' */
    ReadAdapterJoystick(2);
    ReadAdapterJoystick(3);

    if (!(rand() % octopusspeed))
    {   for (i = 0; i <= CREATURES; i++)
        {   if (creature[i].alive && creature[i].species == OCTOPUS && creature[i].dir >= 0)
            {   octopusfire(i);
    }   }   }

    if (banging)
    {   banging = FALSE;
        for (x = 0; x <= FIELDX; x++)
            for (y = 0; y <= FIELDY; y++)
                if (field[x][y] == BANGDYNAMITE)
                {   change(x, y, SILVER);
                    bangdynamite(x, y, infector[x][y]);
                }
        for (i = 0; i <= 3; i++)
            if (worm[i].dynamitescore)
            {   wormscore(i, worm[i].dynamitescore);
                worm[i].dynamitescore = 0;
            }
        for (x = 0; x <= FIELDX; x++)
            for (y = 0; y <= FIELDY; y++)
                if (field[x][y] == TEMPBANGDYNAMITE)
                {   banging = TRUE;
                    field[x][y] = BANGDYNAMITE;
    }           }

    /* DYNAMITE PHILOSOPHY:

    Worm gets dynamite. Instantly the dynamite infects the surrounding
    dynamite into bang-dynamite.
        Each fastloop, you scan the field for bang-dynamite. For each
    piece you find, it turns to silver and infects any surrounding dynamite.

    flash letter */
    if (level)
        if (r % 8 == 1)
            draw(letterx, lettery, WHITENED);
        elif (r % 8 == 2)
            draw(letterx, lettery, lettertype);

	// flash icons

	for (player = 0; player <= 3; player++)
        {   icon(player, ICE);
            icon(player, FREEDOM);
            icon(player, CUTTER);
	}

    /* handle level completion */
    for (player = 0; player <= 3; player++)
    {   complete = TRUE;
        for (which = 0; which <= LETTERS; which++)
            if (!letters[player][which])
                complete = FALSE;
        if (complete)
            advancer = player;
    }
    if (advancer != -1)
    {   if (level++ == 0)
        {   level = reallevel + 1;
            reallevel = 0;
        }
        stopfx();
        if (level > levels)
            effect(FXCELEBRATE);
        newlevel(advancer);
}   }

MODULE ABOOL findempty(SBYTE* x, SBYTE* y, SBYTE first, SBYTE last)
{	SBYTE count = 0, xx, yy;

	do
	{	xx = rand() % (FIELDX + 1);
		yy = rand() % (FIELDY + 1);
	} while ((field[xx][yy] < first || field[xx][yy] > last) && ++count < PATIENCE);
	if (count < PATIENCE)
	{	*x = xx;
		*y = yy;
		return(TRUE);
	} else return(FALSE);
}

void gameloop(void)
{   SBYTE i, player;

    if (a == PLAYGAME)
    {   fastloop();
        gameinput();
    }
    if (a == PLAYGAME)
        for (player = 0; player <= 3; player++)
            if (worm[player].lives && (!(r % worm[player].speed)) && (ice == -1 || ice == player))
                wormloop(player);
    joy0();
    if (a == PLAYGAME)
        for (i = 0; i <= CREATURES; i++)
            if (creature[i].alive && (!(r % creature[i].speed)) && (ice == -1 || (creature[i].species == MISSILE && ice == creature[i].type)))
                creatureloop(i);
    joy0();
    if (a == PLAYGAME && !(r % MAGNETSPEED))
        magnetloop();
    joy0();
    if (a == PLAYGAME)
        death();
    joy0();
    if (a == PLAYGAME && !(r % VERYSLOW))
        slowloop();
    joy0();
    timing();
}

MODULE void killall(void)
{   UBYTE i;

    for (i = 0; i <= CREATURES; i++)
        creature[i].alive = FALSE;
    for (i = 0; i <= MAGNETS; i++)
        magnet[i].alive = FALSE;
    teleport[level][2].alive = FALSE;
    teleport[level][3].alive = FALSE;
}

void levelappend(void)
{	UBYTE oldlevel;

	if (levels < MAXLEVELS)
	{	oldlevel = level;
		level = ++levels;
		newfield();
		level = oldlevel;
		saylevel(WHITE);
}	}

void leveldelete(void)
{	SBYTE i;

	/* pull boards */

	if (levels > 1)
	{	if (level < levels)
			for (i = level; i < levels; i++)
				copyfield(i + 1, i);
		else
			level--;
		levels--;
		saylevel(WHITE);
		turborender();
}	}

void levelerase(void)
{	newfield();
	turborender();
}

void levelinsert(void)
{	SBYTE i;

	/* push boards
 */

	if (levels < MAXLEVELS)
	{	for (i = levels; i >= level; i--)
			copyfield(i, i + 1);
		levels++;
		saylevel(WHITE);
		newfield();
		turborender();
}	}

SBYTE loadfields(STRPTR fieldname)
{   SBYTE i, j, x, y;
    TEXT  IOBuffer[NAMELENGTH + 1];
    UBYTE ver;

    /* This routine is not entirely robust, especially regarding
    failures part way through reading. Also, field data values must be
    those supported by the field editor (ie. objects, and the squares
    represented by F1-F8), or undefined behaviour may result. None of
    this is currently checked for. Provided that the fieldset was
    created with the official field editor, and the file is not
    corrupt, these failures should never happen anyway.

    open file */

    // say("Opening...", WHITE);

    if (!ZOpen(fieldname, FALSE))
        return 1; /* no harm done */

    /* read header */

    // say("Reading header...", WHITE);

    if (!ZRead(IOBuffer, 10))
    {	ZClose();
        return 2; /* no harm done */
    }
    if (!strcmp(IOBuffer, "FSET 6.6"))
        ver = 66;
    elif (!strcmp(IOBuffer, "FSET 6.3"))
        ver = 63;
    elif (!strcmp(IOBuffer, "FSET 6.2"))
        ver = 62;
    elif (!strcmp(IOBuffer, "FSET 6.0"))
        ver = 60;
    elif (!strcmp(IOBuffer, "FSET 5.6"))
        ver = 56;
    elif (!strcmp(IOBuffer, "FSET 5.5"))
        ver = 55;
    elif (!strcmp(IOBuffer, "FSET 5.3"))
        ver = 53;
    elif (!strcmp(IOBuffer, "FSET 5.1"))
        ver = 51;
    elif (!strcmp(IOBuffer, "FSET 5.0"))
        ver = 50;
    else
    {   ZClose();
        return 3; /* no harm done */
    }
    levels = IOBuffer[9];

    /* read high score table */

    // say("Reading high score table...", WHITE);

    for (i = 0; i <= HISCORES; i++)
    {   if (!ZRead(IOBuffer, 6))
        {    ZClose();
             return 4; /* incorrect levels */
        }
        hiscore[i].fresh                        =  FALSE;
        hiscore[i].player                       =  IOBuffer[0];
        hiscore[i].level                        =  IOBuffer[1];
        hiscore[i].score                        = (IOBuffer[2] * 16777216)
                                                + (IOBuffer[3] * 65536)
                                                + (IOBuffer[4] * 256)
                                                +  IOBuffer[5];

        if (!ZRead(IOBuffer, NAMELENGTH + 1))
        {   ZClose();
            return 5; /* incorrect levels, corrupted high scores */
        }
        for (j = 0; j <= NAMELENGTH; j++)
        {   hiscore[i].name[j]              = IOBuffer[j];
        }
        if (!ZRead(IOBuffer, TIMELENGTH + 1))
        {   ZClose();
            return 6; /* incorrect levels, corrupted high scores */
        }
        for (j = 0; j <= TIMELENGTH; j++)
        {   hiscore[i].time[j]      = IOBuffer[j];
        }
        if (!ZRead(IOBuffer, DATELENGTH + 1))
        {   ZClose();
            return 7; /* incorrect levels, corrupted high scores */
        }
        for (j = 0; j <= DATELENGTH; j++)
        {   hiscore[i].date[j]      = IOBuffer[j];
    }   }

    // say("Reading level data...", WHITE);

    /* read level data */

    for (i = 0; i <= levels; i++)
    {   if (!ZRead(IOBuffer, 8))
        {   ZClose();
            return 9;
            /* incorrect levels, corrupted high scores,
            incorrect startx, teleports, field data */
        }
        startx[i]            =  IOBuffer[0];
        starty[i]            =  IOBuffer[1];
        teleport[i][0].alive =  IOBuffer[2];
        teleport[i][0].x     =  IOBuffer[3];
        teleport[i][0].y     =  IOBuffer[4];
        teleport[i][1].alive =  IOBuffer[5];
        teleport[i][1].x     =  IOBuffer[6];
        teleport[i][1].y     =  IOBuffer[7];

        if (!ZRead((char *) &board[i][0][0], LEVELSIZE))
        {   ZClose();
            return 10;
            /* incorrect levels, corrupted high scores,
            incorrect startx, teleports, field data */
        } else
        {   if (ver <= 50)
            {   // convert from FSET 5.0 to FSET 5.1 format
                for (x = 0; x <= FIELDX; x++)
                     for (y = 0; y <= FIELDY; y++)
                          if (board[i][x][y] >= 11)
                              board[i][x][y]++;
            }
            if (ver <= 51)
            {   // convert from FSET 5.1 to FSET 5.3 format
                for (x = 0; x <= FIELDX; x++)
                    for (y = 0; y <= FIELDY; y++)
                        if (board[i][x][y] >= 7 && board[i][x][y] <= 63)
                            board[i][x][y]++;
            }
            if (ver <= 53)
            {   // convert from FSET 5.3 to FSET 5.5 format
                for (x = 0; x <= FIELDX; x++)
                    for (y = 0; y <= FIELDY; y++)
                        if (board[i][x][y] >= 20)
                            board[i][x][y]++;
            }
            if (ver <= 55)
            {   // convert from FSET 5.5 to FSET 5.6 format
                for (x = 0; x <= FIELDX; x++)
                    for (y = 0; y <= FIELDY; y++)
                        if (board[i][x][y] >= 13)
                            board[i][x][y]++;
            }
            if (ver <= 56)
            {   // convert from FSET 5.6 to FSET 6.0 format
                for (x = 0; x <= FIELDX; x++)
                    for (y = 0; y <= FIELDY; y++)
                        if (board[i][x][y] >= 8 && board[i][x][y] <= 20)
                            board[i][x][y]++;
                        elif (board[i][x][y] >= 21)
                            board[i][x][y] += 2;
            }
            if (ver <= 60)
            {   // convert from FSET 6.0 to FSET 6.2 format
                for (x = 0; x <= FIELDX; x++)
                    for (y = 0; y <= FIELDY; y++)
                        if (board[i][x][y] >= 7)
                            board[i][x][y]++;
            }
            if (ver <= 62)
            {   // convert from FSET 6.2 to FSET 6.3/6.6 format
                for (x = 0; x <= FIELDX; x++)
                    for (y = 0; y <= FIELDY; y++)
                        if (board[i][x][y] >= 7)
                            board[i][x][y]++;
    }   }   }

    // say("Open done.", WHITE);

    // no need to read version string
    ZClose();
    modified = FALSE;
    return 0;
}

MODULE void magnetloop(void)
{   SBYTE i;
    UBYTE c;

    for (i = 0; i <= MAGNETS; i++)
    {   if (magnet[i].alive)
        {   // defensive programming to ensure magnet is still valid
            if (field[magnet[i].x][magnet[i].y] != magnet[i].object)
            {   magnet[i].alive = FALSE;
            } else
            {   change(magnet[i].x, magnet[i].y, EMPTY);
                magnet[i].x += bsign(worm[magnet[i].player].x - magnet[i].x);
                magnet[i].y += bsign(worm[magnet[i].player].y - magnet[i].y);
                c = field[magnet[i].x][magnet[i].y];
           
                if ((c >= FIRSTEMPTY && c <= LASTEMPTY)
                 || (c >= FIRSTTAIL  && c <= LASTTAIL))
                    change(magnet[i].x, magnet[i].y, magnet[i].object);
                elif (c >= FIRSTHEAD && c <= LASTHEAD)
                {   change(magnet[i].x, magnet[i].y, magnet[i].object);
                    wormscore(c - FIRSTHEAD, wormobject(c - FIRSTHEAD, magnet[i].x, magnet[i].y));
                    change(magnet[i].x, magnet[i].y, FIRSTHEAD + magnet[i].player); // not entirely the right head image
                } else magnet[i].alive = FALSE;
}   }   }   }

AGLOBAL void matchteleports(void)
{   AUTO UWORD teleports, octopi;
    AUTO SBYTE x, y, i;

    say("Checking fieldset...", WHITE);
    for (i = 0; i <= levels; i++)
    {   teleports = octopi = 0;
        teleport[level][0].alive = teleport[level][1].alive = FALSE;

        for (y = 0; y <= FIELDY; y++)
        {   for (x = 0; x <= FIELDX; x++)
            {   if (board[level][x][y] == TELEPORT)
                {   teleports++;
                    if (teleports <= 2)
                    {   teleport[level][teleports - 1].x = x;
                        teleport[level][teleports - 1].y = y;
                    } else
                    {   board[level][x][y] = EMPTY;
                }   }
                elif (board[level][x][y] == OCTOPUS)
                {   octopi++;
                    if (octopi > 12)
                    {   board[level][x][y] = EMPTY;
        }   }   }   }

        if (teleports == 1)
        {   board[level][teleport[level][0].x][teleport[level][0].y] = EMPTY;
        } elif (teleports >= 2)
        {   teleport[level][0].alive = teleport[level][1].alive = TRUE;
}   }   }

MODULE void newfield(void)
{   SBYTE x, y;

	teleport[level][0].alive = FALSE;
	teleport[level][1].alive = FALSE;
	startx[level] = FIELDX / 2;
	starty[level] = FIELDY / 2;

	if (level)
		for (x = 0; x <= FIELDX; x++)
			for (y = 0; y <= FIELDY; y++)
				board[level][x][y] = EMPTY;
	else for (x = 0; x <= FIELDX; x++)
		for (y = 0; y <= FIELDY; y++)
			board[0][x][y] = SILVER;
}

void newfields(void)
{   if (verify())
    {   strcpy(pathname, DEFAULTSET);
		levels = DEFAULTLEVELS;
		modified = FALSE;
		for (level = 0; level <= levels; level++)
			newfield();
		clearhiscores();
		level = 1;
		if (a == FIELDEDIT)
		{	turborender();
			saylevel(WHITE);
		} else hiscores();
}	}

void newgame(void)
{   SBYTE i, player;

    players = 0;
    for (player = 0; player <= 3; player++)
    {   if (worm[player].control != NONE)
            players++;
        worm[player].lives = 0;
        worm[player].speed = NORMAL;
        worm[player].hiscore = 0;
    }
    for (i = 1; i <= MAXLEVELS; i++)
       randomarray[i] = FALSE;

    r         = -1;
    trainer   = FALSE;
    ice       = -1;
    reallevel = 0;

    level     = 1;
    a         = PLAYGAME;
    clearscreen();
    newlevel(rand() % 4);
    timing();
}

MODULE void newhiscores(void)
{   PERSIST TEXT  amiganame[4][NAMELENGTH + 1] = {"Jay Miner", "Carl Sassenrath", "R. J. Mical", "Dave Morse"};
    AUTO    SBYTE i, j, player;

    datestamp();
    for (player = 0; player <= 3; player++)
        for (i = 0; i <= HISCORES; i++)
            if (worm[player].control != NONE && worm[player].score >= hiscore[i].score)
            {   /* push all worse hiscores down */

                if (i < HISCORES)
                    for (j = HISCORES; j >= i + 1; j--)
                    {   hiscore[j].player     = hiscore[j - 1].player;
                        hiscore[j].level      = hiscore[j - 1].level;
                        hiscore[j].score      = hiscore[j - 1].score;
                        hiscore[j].fresh      = hiscore[j - 1].fresh;
                        strcpy(hiscore[j].name, hiscore[j - 1].name);
                        strcpy(hiscore[j].date, hiscore[j - 1].date);
                        strcpy(hiscore[j].time, hiscore[j - 1].time);
                    }
                modified = TRUE;
                hiscore[i].player = player;
                hiscore[i].level  = worm[player].levelreached;
                hiscore[i].score  = worm[player].hiscore;
                if (worm[player].control == AMIGA)
                {   strcpy(hiscore[i].name, amiganame[player]);
                    hiscore[i].fresh = FALSE;
                } else
                {   strcpy(hiscore[i].name, "(New)");
                    hiscore[i].fresh = TRUE;
                }
                strcpy(hiscore[i].time, times);
                strcpy(hiscore[i].date, date);
                break; /* vital */
}           }

MODULE void newlevel(SBYTE player)
{   SBYTE i, j;
    UWORD octopi = 0;
    UBYTE x, y;

    if (level >= 2)
        rundown(player);
    if (a == PLAYGAME)
    {   if (level > levels)
        {   for (i = 0; i <= 3; i++)
            {   if (worm[i].lives)
                    worm[i].levelreached = -1;
                if (worm[player].control != NONE && worm[player].score >= worm[player].hiscore)
                    worm[player].hiscore = worm[player].score;
            }
            celebrate();
            newhiscores();
            titlescreen();
        } else
        {   saylevel(WHITE);
            for (i = 0; i <= 3; i++)
            {   worm[i].multi = (SBYTE) atleast(worm[i].multi / 2, 1);
                worm[i].remnants = FALSE;
                worm[i].pusher = FALSE;
            }
            killall();
            clearletters();
            changefield();
            octopusspeed = FAST;
            for (x = 0; x <= FIELDX; x++)
            {   for (y = 0; y <= FIELDY; y++)
                {   if (board[level][x][y] == OCTOPUS)
                    {   octopi++;
                        if (octopi <= 12)
                        {   creature[octopi].alive   = TRUE;
                            creature[octopi].species = OCTOPUS;
                            creature[octopi].visible = TRUE;
                            creature[octopi].x       = x;
                            creature[octopi].y       = y;
                            creature[octopi].speed   = VERYSLOW;
                            creature[octopi].dir     = -1;
                            creature[octopi].deltax  = creature[octopi].deltay = 0;
            }   }   }   }
            for (i = 0; i <= 3; i++)
            {   worm[i].speed = NORMAL;
                if (worm[i].lives)
                    stat(i, NITRO);
                worm[i].moved = FALSE;
                worm[i].x = startx[sourcelevel];
                worm[i].y = starty[sourcelevel];
                worm[i].arrowy = worm[i].y;
		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;
            }   }
            turborender();
            delay = atleast(DELAY_MAX - (level * DELAY_DEC), DELAY_MIN);

            if (level)
            {   secondsperlevel = SECONDSPERLEVEL;
                putletter(-1);
                freq = (SBYTE) atleast(FREQ_MAX - (level * FREQ_DEC), FREQ_MIN);
                for (i = 0; i <= 3; i++)
                {   if (!worm[i].lives && worm[i].control != NONE)
                    {   /* create (or resurrect) a worm */

                        worm[i].lives      = STARTLIVES;
                        worm[i].score      = 0;
                        worm[i].oldscore   = 0;
                        worm[i].armour     = 0;
                        worm[i].tongue     = 0;
                        worm[i].alive      = TRUE;
                        worm[i].nitro      = FALSE;
                        worm[i].flashed    = FALSE;
                        worm[i].mode       = NOMODE;
                        worm[i].power      = 0;
                        worm[i].bias       = 0;
                        worm[i].multi      = 1;
                        worm[i].ice        = 0;
                        worm[i].victor     = -1;
                        worm[i].ammo       = 0;
                        worm[i].remnants   = FALSE;
                        worm[i].affixer    = FALSE;
                        worm[i].sideshot   = FALSE;
                        worm[i].pusher     = FALSE;
                        worm[i].rammed     = FALSE;
                        worm[i].freedom    = 0;
                        worm[i].causewait  = (ULONG) -1;
                        worm[i].last       = FIRSTTAIL + i;
                        worm[i].pos        = -1;
                        worm[i].cutter     = 0;
                        for (j = 0; j <= PROTECTORS; j++)
                            protector[i][j].alive = FALSE;
                        for (j = 0; j <= LASTOBJECT; j++)
                            stat(i, j);
            }   }   }
            icon(i, ICE);
            icon(i, REMNANTS);
            icon(i, AFFIXER);
            icon(i, SIDESHOT);
            icon(i, PUSHER);
            icon(i, FREEDOM);
            icon(i, CUTTER);
    }   }
    clearjoystick();
    clearkybd();
    resettime();
}

MODULE void explosion(SBYTE x, SBYTE y, SBYTE exceptionx, SBYTE exceptiony, UBYTE creator)
{   UBYTE i, generated = 0;

    effect(FXGET_PULSE);
    for (i = 0; i <= CREATURES; i++)
    {   if ((!(creature[i].alive)) && generated <= 7)
        {   if (creator == OCTOPUS)
            {   creature[i].last = OCTOPUS;
                creature[i].visible = FALSE;
            } else
            {   creature[i].last = EMPTY;
                creature[i].visible = TRUE;
            }
            creature[i].x = x;
            creature[i].y = y;
            if (level)
            {   creature[i].speed = (SBYTE) atleast(FRAGSPEED - (level / 2), 1);
            } else creature[i].speed = BONUSFRAGSPEED;
	    creature[i].species   = FRAGMENT;

            switch (generated)
            {
            case 0:
                creature[i].deltax = 0;
                creature[i].deltay = -1;
            break;
            case 1:
                creature[i].deltax = 1;
                creature[i].deltay = -1;
            break;
            case 2:
                creature[i].deltax = 1;
                creature[i].deltay = 0;
            break;
            case 3:
                creature[i].deltax = 1;
                creature[i].deltay = 1;
            break;
            case 4:
                creature[i].deltax = 0;
                creature[i].deltay = 1;
            break;
            case 5:
                creature[i].deltax = -1;
                creature[i].deltay = 1;
            break;
            case 6:
                creature[i].deltax = -1;
                creature[i].deltay = 0;
            break;
            case 7:
                creature[i].deltax = -1;
                creature[i].deltay = -1;
            break;
            default:
            break;
            }

            generated++;
            if (creature[i].deltax != exceptionx || creature[i].deltay != exceptiony)
            {   creature[i].alive = TRUE;
                if (generated == 1 && creator != OCTOPUS)
                {   change(x, y, FRAGMENT);
}   }   }   }   }

/* Many creatures take advantage of shared characteristics.
11 creature types (orbs, goats, drips, fragments, missiles, penguins,
cyclones, dogs, clouds, timebombs, octopus) use the creature structure.
Independent of it are worms, protectors, worm bullets and teleports. */

MODULE void creatureloop(SBYTE which)
{   ABOOL   happy = FALSE;
    UBYTE   bestdistance = 255, distance, player, c, i;
    SBYTE   x, y, xx, yy, xxx, yyy, frontx, fronty, rearx, reary;

    x = creature[which].x;
    y = creature[which].y;

    if (!valid(x, y)) // defensive programming
    {   creature[which].alive = FALSE;
        return;

        /* TEXT temp1[SAYLIMIT + 1], temp2[8];

        strcpy(temp1, "BAD CREATURE AT x: ");
        stci_d(temp2, x);
        strcat(temp1, temp2);
        strcat(temp1, ", y: ");
        stci_d(temp2, y);
        strcat(temp1, temp2);
        strcat(temp1, "!");
        say(temp1, PURPLE);
        draw(FIELDX + 1, 0, creature[which].species); // indicates which creature
        Delay(250);
        clearkybd();
        anykey(FALSE); */
    }

    if (creature[which].species == ORB && creature[which].explode)
    {   creature[which].alive = FALSE;
        explosion(x, y, -2, -2, ORB);
        return;
    }

/* decide whether and where to move */

switch(creature[which].species)
{
case CLOUD:
        if (creature[which].x == 0 || creature[which].x == FIELDX)
            creature[which].deltax = -creature[which].deltax;
break;
case TIMEBOMB:
	/* decrement and explode timebombs */
        if (field[x][y] != TIMEBOMB)
            creature[which].alive = FALSE;
        else
        {   effect(FXDO_BOMB);
            creature[which].time--;
            if (creature[which].time < 0)
            {   creature[which].alive = FALSE;
                bombblast(BOMB, 0, x, y);
                change(x, y, EMPTY);
            } else draw(x, y, ZERO + creature[which].time);
        }
        return; // not a bug
break;
case DOG:
        /* remove a movement from the dog queue */

        if (creature[which].dormant == CHASING)
        {   if (creature[which].pos != -1)
            {   creature[which].deltax = thedogqueue[which][0].deltax;
                creature[which].deltay = thedogqueue[which][0].deltay;
                if (--creature[which].pos != -1)
                {   for (i = 0; i <= creature[which].pos; i++)
                    {   thedogqueue[which][i].deltax = thedogqueue[which][i + 1].deltax;
                        thedogqueue[which][i].deltay = thedogqueue[which][i + 1].deltay;
            }   }   }
            else creature[which].alive = FALSE;
        }
break;
case PENGUIN:
	do
	{	xx = (rand() % 3) - 1;
		yy = (rand() % 3) - 1;
	} while (!valid(x + xx, y + yy));
        c = field[x + xx][y + yy];
        if (c >= FIRSTEMPTY && c <= LASTEMPTY)
	{	creature[which].deltax = xx;
		creature[which].deltay = yy;
	} else
	{	creature[which].deltax = 0;
		creature[which].deltay = 0;
	}
break;
case WHIRLWIND:
	/* Whirlwinds has a slight upwards drift.
	Higher values of UNDRIFT make it less buoyant. */

	creature[which].deltax = (rand() % 3) - 1;
	if (!(rand() % UNDRIFT))
		creature[which].deltay = (rand() % 2) - 1;
	else creature[which].deltay = (rand() % 3) - 1;
break;
case MISSILE:
    for (player = 0; player <= 3; player++)
    {   if (creature[which].type != player && worm[player].lives && (!worm[player].bias))
        {   xx = abs(worm[player].x - x);
            yy = abs(worm[player].y - y);
            if (xx < yy)
                distance = xx;
            else distance = yy;
            if (distance <= bestdistance)
            {   bestdistance = distance;
                creature[which].deltax = bsign(worm[player].x - x);
                creature[which].deltay = bsign(worm[player].y - y);
        }   }
	for (i = 0; i <= CREATURES; i++)
            if (creature[i].alive && which != i)
            {   if
                (   creature[i].species == ORB
                ||  (creature[i].species == MISSILE && creature[i].type != creature[which].type)
                ||  creature[i].species == GOAT
                )
                {   xx = abs(creature[i].x - x);
                    yy = abs(creature[i].y - y);
                    if (xx < yy)
                        distance = xx;
                    else distance = yy;
                    if (distance <= bestdistance)
                    {   bestdistance = distance;
                        creature[which].deltax = bsign(creature[i].x - x);
                        creature[which].deltay = bsign(creature[i].y - y);
    }       }   }   }
    if (bestdistance == 255)
        creature[which].alive = FALSE;
break;
case ORB:
    frontx  = xwrap(x + creature[which].deltax);  /* look in front */
    fronty  = ywrap(y + creature[which].deltay);
    rearx   = xwrap(x - creature[which].deltax);  /* look behind */
    reary   = ywrap(y - creature[which].deltay);
    if (bounceorb(which, frontx, fronty))
    {   bouncegoat(which, frontx, fronty);
        xx = -creature[which].deltax; /* default bounce angle is 180° */
        yy = -creature[which].deltay;
    	if (!bounceorb(which, frontx, reary))
    	{	if (bounceorb(which, rearx, fronty))
            {   bouncegoat(which, rearx, fronty);
                xx = creature[which].deltax;
    	}	}
    	elif (!bounceorb(which, rearx, fronty))
        {   bouncegoat(which, rearx, fronty);
            yy = creature[which].deltay;
    	}
        creature[which].deltax = xx;
        creature[which].deltay = yy;
    }
break;
case GOAT:
    /* decide whether to move */
    if (!(rand() % GOATMOVE))
    {   for (xx = x - 1; xx <= x + 1; xx++)
            for (yy = y - 1; yy <= y + 1; yy++)
                if (valid(xx, yy) && field[xx][yy] >= FIRSTEMPTY && field[xx][yy] <= LASTEMPTY)
				{   happy = TRUE;
					break;
	}			}
    creature[which].deltax = 0;
    creature[which].deltay = 0;
    if (!happy)
	{	xx = (rand() % 3) - 1;
		yy = (rand() % 3) - 1;
		if (valid(x + xx, y + yy) && (xx || yy))
                {       c = field[x + xx][y + yy];
            if (c >= FIRSTGOAT && c <= LASTGOAT && c != GOAT)
        	{	creature[which].last = creature[which].oldlast;
                                creature[which].oldlast = c;
				creature[which].deltax = xx;
				creature[which].deltay = yy;
	}	}	}
break;
default:
break;
}

/* now move */

if (creature[which].deltax || creature[which].deltay)
{	if (creature[which].visible)
	{   /* erase previous image */
	    change(x, y, creature[which].last);
            creature[which].last = EMPTY;
	}
	if (creature[which].alive)
	{   creature[which].x += creature[which].deltax;
	    creature[which].y += creature[which].deltay;
	    if (creature[which].species == ORB)
	    {   creature[which].x = xwrap(creature[which].x);
	        creature[which].y = ywrap(creature[which].y);
	    } elif (creature[which].species == FRAGMENT || creature[which].species == DRIP || creature[which].species == WHIRLWIND || creature[which].species == DOG)
	    {	if (!valid(creature[which].x, creature[which].y))
                creature[which].alive = FALSE;
}	}   }

creature[which].visible = TRUE;
x = creature[which].x;
y = creature[which].y;

/* Collision detection.
Timebombs don't need to go through this. */

if
(    creature[which].alive
 &&  creature[which].species != GOAT
 &&  creature[which].species != PENGUIN
 &&  creature[which].species != OCTOPUS
 && (creature[which].deltax || creature[which].deltay)
)
{   c = field[x][y];

    if (c >= FIRSTHEAD && c <= LASTHEAD)
    {   if (creature[which].species == MISSILE)
            wormmissile(x, y, c - FIRSTHEAD, which);
        elif (creature[which].species == DOG)
            wormdog(x, y, c - FIRSTHEAD, which);
        elif (creature[which].species == FRAGMENT)
            wormfrag(x, y, c - FIRSTHEAD, which);
        elif (creature[which].species == DRIP)
            wormdrip(x, y, c - FIRSTHEAD, which);
    	elif (creature[which].species == WHIRLWIND)
            wormwhirlwind(x, y, c - FIRSTHEAD, which);
        elif (creature[which].species == CLOUD)
            cloudworm(x, y, which, c - FIRSTHEAD);
        elif (creature[which].species == ORB)
        {   wormorb(x, y, c - FIRSTHEAD, which);
            creature[which].last = c - FIRSTHEAD + FIRSTTAIL; /* note sign issues */
    }   }
    elif (c == TIMEBOMB)
    {   creature[whichcreature(x, y, TIMEBOMB, which)].alive = FALSE;
        bombblast(BOMB, 0, x, y);
        change(x, y, EMPTY);
    } elif (c == OCTOPUS)
    {   if (creature[which].species == ORB)
        {   effect(FXDEATH_ORB);
        } elif (creature[which].species == FRAGMENT)
        {   effect(FXDEATH_FRAGMENT);
        }
        creature[which].alive = FALSE;
    } elif (c >= FIRSTTAIL && c <= LASTTAIL)
    {   if (creature[which].species == ORB)
        {   if (creature[which].mode == TONGUE)
                effect(FXUSE_TONGUE);
            else
            {   if (worm[c - FIRSTTAIL].alive)
                {   effect(FXDEATH_ORB);
                    wormscore(c - FIRSTTAIL, creature[which].score);
                    if (worm[c - FIRSTTAIL].bias)
                    {   worm[c - FIRSTTAIL].lives += ORBBLOOD;
                        stat(c - FIRSTTAIL, LIFE);
                }   }
                creature[which].alive = FALSE;
                change(x, y, BONUS);
    }   }   }
    elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR)
    {   if (creature[which].species == DOG)
            protdog(x, y, c - FIRSTPROTECTOR, which);
        elif (creature[which].species == DRIP)
            protdrip(x, y, c - FIRSTPROTECTOR, which);
        elif (creature[which].species == MISSILE)
            protmissile(x, y, c - FIRSTPROTECTOR, which);
        elif (creature[which].species == FRAGMENT)
            protfrag(x, y, c - FIRSTPROTECTOR, which);
        elif (creature[which].species == ORB)
            protorb(x, y, c - FIRSTPROTECTOR, which);
        elif (creature[which].species == PENGUIN)
            protpenguin(x, y, c - FIRSTPROTECTOR, which);
        elif (creature[which].species == WHIRLWIND)
            protwhirlwind(x, y, c - FIRSTPROTECTOR, which);
        elif (creature[which].species == CLOUD)
            cloudprot(x, y, which, c - FIRSTPROTECTOR);
    } elif (c >= FIRSTLETTER && c <= LASTLETTER)
    {   if (creature[which].species == DRIP || creature[which].species == FRAGMENT || creature[which].species == MISSILE || creature[which].species == CLOUD)
        {   effect(FXDEATH_FRAGMENT);
            creature[which].alive = FALSE;
        } elif (creature[which].species == ORB)
        {   for (player = 0; player <= 3; player++)
            {   letters[player][c - FIRSTLETTER] = FALSE;
                drawletter(player, c, BLACK);
            }
            putletter(-1);
            orbscore(which, LETTERPOINT);
        } elif (creature[which].species == WHIRLWIND)
            putletter(-1);
    } elif (c >= FIRSTDRIP && c <= LASTDRIP)
    {   i = whichcreature(x, y, DRIP, which);
        if (creature[which].species == FRAGMENT)
            dripfrag(x, y, i, which);
        elif (creature[which].species == MISSILE)
            dripmissile(x, y, i, which);
        elif (creature[which].species == ORB)
            driporb(x, y, i, which);
        elif (creature[which].species == WHIRLWIND)
            dripwhirlwind(x, y, i, which);
        elif (creature[which].species == CLOUD)
            clouddrip(x, y, which, i);
    } elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
    {   i = whichcreature(x, y, MISSILE, which);
        if (creature[which].species == FRAGMENT)
            fragmissile(x, y, which, i);
        elif (creature[which].species == DRIP)
            dripmissile(x, y, which, i);
        elif (creature[which].species == ORB)
            orbmissile(x, y, which, i);
        elif (creature[which].species == WHIRLWIND)
            missilewhirlwind(x, y, i, which);
        elif (creature[which].species == MISSILE)
            missilemissile(x, y, which, i);
        elif (creature[which].species == CLOUD)
            cloudmissile(x, y, which, i);
    } elif (c == WHIRLWIND)
    {   i = whichcreature(x, y, WHIRLWIND, which);
        if (creature[which].species == DRIP)
            dripwhirlwind(x, y, which, i);
        elif (creature[which].species == MISSILE)
            missilewhirlwind(x, y, which, i);
        elif (creature[which].species == ORB)
            orbwhirlwind(x, y, which, i);
        elif (creature[which].species == WHIRLWIND)
            whirlwindwhirlwind(x, y, which, i);
        elif (creature[which].species == FRAGMENT)
            fragwhirlwind(x, y, which, i);
        elif (creature[which].species == CLOUD)
            cloudwhirlwind(x, y, which, i);
    } elif (c == DOG)
    {   i = whichcreature(x, y, DOG, which);
        if (creature[which].species == FRAGMENT)
            dogfrag(x, y, i, which);
        elif (creature[which].species == PENGUIN)
            dogpenguin(x, y, i, which);
        elif (creature[which].species == ORB)
            dogorb(x, y, i, which);
        elif (creature[i].species == DRIP)
            dogdrip(x, y, i, which);
        elif (creature[i].species == WHIRLWIND)
            dogwhirlwind(x, y, i, which);
        elif (creature[i].species == MISSILE)
            dogmissile(x, y, i, which);
        elif (creature[i].species == DOG)
            dogdog(x, y, i, which);
        elif (creature[i].species == CLOUD)
            clouddog(x, y, which, i);
    } elif (c == PENGUIN)
    {   i = whichcreature(x, y, PENGUIN, which);
        if (creature[which].species == ORB)
            orbpenguin(x, y, which, i);
        elif (creature[which].species == DOG)
            dogpenguin(x, y, which, i);
        elif (creature[which].species == WHIRLWIND)
            penguinwhirlwind(x, y, i, which);
        elif (creature[which].species == MISSILE)
            missilepenguin(x, y, which, i);
        elif (creature[which].species == DRIP)
            drippenguin(x, y, which, i);
        elif (creature[which].species == CLOUD)
            cloudpenguin(x, y, which, i);
        elif (creature[which].species == FRAGMENT)
            fragpenguin(x, y, which, i);
    } elif (c == FRAGMENT)
    {   i = whichcreature(x, y, FRAGMENT, which);
        if (creature[which].species == MISSILE)
            fragmissile(x, y, i, which);
        elif (creature[which].species == DRIP)
            dripfrag(x, y, which, i);
        elif (creature[which].species == ORB)
            fragorb(x, y, i, which);
        elif (creature[which].species == FRAGMENT)
            fragfrag(x, y, which, i);
        elif (creature[which].species == WHIRLWIND)
            fragwhirlwind(x, y, i, which);
        elif (creature[which].species == CLOUD)
            cloudfrag(x, y, which, i);
        elif (creature[which].species == DOG)
            dogfrag(x, y, which, i);
    } elif (c == METAL)
    {   if (creature[which].species == DRIP || creature[which].species == MISSILE || creature[which].species == CLOUD)
            creature[which].alive = FALSE;
        elif (creature[which].species == FRAGMENT)
            reflect(which);
    } elif (c == STONE)
    {   if (creature[which].species == DRIP || creature[which].species == FRAGMENT || creature[which].species == MISSILE || creature[which].species == CLOUD)
        {   effect(FXDEATH_FRAGMENT);
            creature[which].alive = FALSE;
    }   }
    elif (c == WOOD)
    {   if (creature[which].species == FRAGMENT || creature[which].species == MISSILE || creature[which].species == CLOUD || creature[which].species == DRIP)
        {   effect(FXDEATH_FRAGMENT);
            creature[which].alive = FALSE;
    }   }
    elif (c == GOAT)
    {   /* Drips, fragments, missiles, cyclones, dogs, clouds */
        creature[whichcreature(x, y, GOAT, which)].alive = FALSE;
        effect(FXDEATH_GOAT);
        if (creature[which].species != WHIRLWIND)
        {   creature[which].alive = FALSE;
            change(x, y, BONUS);
        }
        if (creature[which].species == MISSILE)
        {   wormscore(creature[which].type, KILLGOAT);
            if (worm[creature[which].type].bias)
            {   worm[creature[which].type].lives += GOATBLOOD;
                stat(creature[which].type, LIFE);
    }   }   }
    elif (c == SLIME)
    {   if (creature[which].species == DRIP || creature[which].species == FRAGMENT || creature[which].species == MISSILE || (creature[which].species == ORB && creature[which].mode != ARMOUR))
        {   creature[which].alive = FALSE;
            change(x, y, EMPTY);
    }   }
    elif (c == DYNAMITE)
    {   if (creature[which].species == ORB)
        {   orbscore(which, DYNAMITEPOINT);
    }   }
    elif (c == SKULL)
    {   if (creature[which].species == ORB)
        {   effect(FXGET_SKULL);
            orbscore(which, SKULLPOINT);
        } elif (creature[which].species == FRAGMENT || creature[which].species == DRIP || creature[which].species == MISSILE || creature[which].species == CLOUD)
        {   effect(FXDEATH_FRAGMENT);
            creature[which].alive = FALSE;
    }   }
    elif (c == TELEPORT)
    {   /* Drips, fragments, missiles, orbs, whirlwinds, dogs, clouds */
        i = whichteleport(x, y);
        if (blocked(i, creature[which].deltax, creature[which].deltay))
            creature[which].alive = FALSE;
        else
        {   effect(FXUSE_TELEPORT);
            creature[which].x = teleport[level][partner(i)].x + creature[which].deltax;
            creature[which].y = teleport[level][partner(i)].y + creature[which].deltay;

            if (creature[which].species == ORB)
            {   orbscore(which, TELPOINT);
                creature[which].x = xwrap(creature[which].x);
                creature[which].y = ywrap(creature[which].y);
            } else
            {   if (!(valid(creature[which].x, creature[which].y)))
                    creature[which].alive = FALSE;
                if (creature[which].species == FRAGMENT)
                    creature[which].last = SILVER;
    }   }   }
    elif (c == ORB)
    {   i = whichcreature(x, y, ORB, which);
        if (creature[which].species == DRIP)
            driporb(x, y, which, i);
        elif (creature[which].species == FRAGMENT)
            fragorb(x, y, which, i);
        elif (creature[which].species == WHIRLWIND)
            orbwhirlwind(x, y, i, which);
        elif (creature[which].species == PENGUIN)
            orbpenguin(x, y, i, which);
        elif (creature[which].species == MISSILE)
            orbmissile(x, y, i, which);
        elif (creature[which].species == DOG)
            dogorb(x, y, which, i);
        elif (creature[which].species == ORB)
            orborb(x, y, which, i);
        elif (creature[which].species == CLOUD)
            cloudorb(x, y, which, i);
    } elif (c == CLOUD)
    {   i = whichcreature(x, y, CLOUD, which);
        if (creature[which].species == DRIP)
            clouddrip(x, y, which, i);
        elif (creature[which].species == FRAGMENT)
            cloudfrag(x, y, which, i);
        elif (creature[which].species == WHIRLWIND)
            cloudwhirlwind(x, y, which, i);
        elif (creature[which].species == PENGUIN)
            cloudpenguin(x, y, which, i);
        elif (creature[which].species == MISSILE)
            cloudmissile(x, y, which, i);
        elif (creature[which].species == DOG)
            clouddog(x, y, which, i);
        elif (creature[which].species == ORB)
            cloudorb(x, y, which, i);
        elif (creature[which].species == CLOUD)
            cloudcloud(x, y, which, i);
    } elif (creature[which].species == ORB)
    {   if (c <= LASTOBJECT)
        {   orbscore(which, object[c].score);
            if (c == AMMO || c == PULSE || c == SLAYER || c == LIGHTNING)
            {   if (creature[which].mode != ARMOUR)
                    creature[which].explode = TRUE;
                else effect(FXUSE_ARMOUR);
            } elif (c == NITRO || c == POWER || c == SLOWER || c == CYCLONE)
            {   effect(FXGET_NITRO);
                creature[which].speed = speedup(creature[which].speed, TRUE);
            } elif (c == HEALER || c == LIFE || c == ICE || c == TREASURE || c == UMBRELLA || c == BONUS)
                orbsplit(which);
            else switch (c)
            {
            case BOMB:
                draw(x, y, ORB);
                bombblast(ORB, which, x, y);
            break;
            case ARMOUR:
                creature[which].armour += MODEADD + (rand() % MODERAND);
                creature[which].mode = ARMOUR;
            break;
            case TONGUE:
                creature[which].tongue += MODEADD + (rand() % MODERAND);
                creature[which].mode = TONGUE;
            break;
            case PROTECTOR:
                for (player = 0; player <= 3; player++)
                    if (worm[player].lives)
                        for (i = 0; i <= PROTECTORS; i++)
                           if (protector[player][i].alive)
                           {   protector[player][i].alive = FALSE;
                               if (protector[player][i].visible)
                                   change(protector[player][i].x, protector[player][i].y, EMPTY);
                           }
            break;
            case MISSILE:
            case CONVERTER:
                effect(FXGET_OBJECT);
                for (i = 0; i <= CREATURES; i++)
                {   if (creature[i].alive && creature[i].species == MISSILE)
                    {   creature[i].alive = FALSE;
                        change(x, y, EMPTY);
                }   }
            break;
            case MULTIPLIER:
                effect(FXGET_OBJECT);
                creature[which].multi *= 2;
                if (creature[which].multi > MULTILIMIT)
                    creature[which].multi = MULTILIMIT;
            break;
            case BIAS:
                effect(FXGET_OBJECT);
                for (player = 0; player <= 3; player++)
                    if (worm[player].lives && worm[player].bias)
                    {   worm[player].bias = 0;
                        stat(player, BIAS);
                    }
            break;
            case AFFIXER:
                effect(FXGET_OBJECT);
                for (player = 0; player <= 3; player++)
                    if (worm[player].lives)
                    {   worm[player].affixer = FALSE;
                        icon(player, AFFIXER);
                    }
            break;
            case REMNANTS:
                effect(FXGET_OBJECT);
                for (player = 0; player <= 3; player++)
                    if (worm[player].lives)
                    {   worm[player].remnants = FALSE;
                        icon(player, REMNANTS);
                    }
            break;
            case SIDESHOT:
                effect(FXGET_POWERUP);
                for (player = 0; player <= 3; player++)
                    if (worm[player].lives)
                    {   worm[player].sideshot = FALSE;
                        icon(player, SIDESHOT);
                    }
            break;
            case MAGNET:
                effect(FXGET_OBJECT);
                for (i = 0; i <= MAGNETS; i++)
                    if (magnet[i].alive)
                        magnet[i].alive = FALSE;
            break;
            case PUSHER:
                effect(FXGET_OBJECT);
                for (i = 0; i <= 3; i++)
                    if (worm[i].lives && worm[i].pusher)
                    {   worm[i].pusher = FALSE;
                        icon(i, PUSHER);
                    }
            break;
            case FREEDOM:
                effect(FXGET_OBJECT);
                for (xx = 0; xx <= FIELDX; xx++)
                    for (yy = 0; yy <= FIELDY; yy++)
                        if (field[xx][yy] >= FIRSTFIRE && field[xx][yy] <= LASTFIRE)
                            change(xx, yy, EMPTY);
            break;
            case SWITCHER:
                effect(FXGET_OBJECT);
                for (xx = 0; xx <= FIELDX; xx++)
                    for (yy = 0; yy <= FIELDY; yy++)
                        if (field[xx][yy] >= FIRSTTAIL && field[xx][yy] <= LASTTAIL)
                            change(xx, yy, WOOD);
            break;
            case GROWER:
                effect(FXGET_GROWER);
                for (xx = 0; xx <= FIELDX; xx++)
                    for (yy = 0; yy <= FIELDY; yy++)
                        if (field[xx][yy] == WOOD)
                            for (xxx = xx - 1; xxx <= xx + 1; xxx++)
                                for (yyy = yy - 1; yyy <= yy + 1; yyy++)                                                                if (valid(xxx, yyy) && field[xxx][yyy] == EMPTY)
                                    field[xxx][yyy] = TEMPWOOD;
                for (xx = 0; xx <= FIELDX; xx++)
                    for (yy = 0; yy <= FIELDY; yy++)
                        if (field[xx][yy] == TEMPWOOD)
                            change(xx, yy, WOOD);
            break;
            case CLOCK:
                secondsperlevel -= rand() % CLOCKRAND;
                if (secondsperlevel < 0)
                    secondsperlevel = 0;
            break;
            case CUTTER:
                for (i = 0; i <= 3; i++)
                    if (worm[i].lives && worm[i].cutter)
                    {   worm[i].cutter = 0;
                        icon(i, CUTTER);
                    }
            break;
            default:
                // assert(0);
            break;
        }   }
        elif (c == EMPTY)
            orbscore(which, EMPTYPOINT);
        elif (c == SILVER)
            orbscore(which, SILVERPOINT);
        elif (c == GOLD)
            orbscore(which, GOLDPOINT);
    }   }
    
    x = creature[which].x; /* We refresh these in case a fragment has been */
    y = creature[which].y; /* reflected. Yes, it is vital. */
    
    if (creature[which].alive && creature[which].visible && (creature[which].deltax || creature[which].deltay))
    {   if (creature[which].species == MISSILE)
            change(x, y, FIRSTMISSILE + creature[which].type);
        elif (creature[which].species == DRIP)
            change(x, y, FIRSTDRIP + creature[which].type);
        elif (creature[which].species == ORB)
        {   field[x][y] = ORB;
            if (creature[which].mode == TONGUE)
                draw(x, y, ORBTONGUE);
            elif (creature[which].mode == ARMOUR)
                draw(x, y, ORBARMOUR);
            else draw(x, y, ORB);
        } else /* fragments, goats, penguins, cyclones, dogs, clouds */
    	change(x, y, creature[which].species);
    }
    
    if (creature[which].alive)
    {   /* decide whether to fire */
        if (creature[which].species == GOAT)
        {   if (!(rand() % 10))
            {   for (i = 0; i <= CREATURES; i++)
                {   if (!creature[i].alive)
                    {   creature[i].deltax = (rand() % 3) - 1;
                        creature[i].deltay = (rand() % 3) - 1;
                        if
                        (   valid(x + creature[i].deltax, y + creature[i].deltay)
                        &&  (creature[i].deltax || creature[i].deltay)
                        )
                        {   c = field[x + creature[i].deltax][y + creature[i].deltay];
                            if
                            (   (c >= FIRSTEMPTY && c <= LASTEMPTY)
                            ||  (c >= FIRSTTAIL && c <= LASTTAIL)
                            )
                            {   effect(FXDO_GOAT);
                                creature[i].alive = TRUE;
                                creature[i].species = FRAGMENT;
                                creature[i].x = x + creature[i].deltax;
                                creature[i].y = y + creature[i].deltay;
                                creature[i].visible = TRUE;
                                creature[i].last = EMPTY;
                                creature[i].speed = (SBYTE) atleast(FRAGSPEED - (level / 2), 1);
                        }   }
                        break;
        }   }   }   }
        elif (creature[which].species == CLOUD)
        {   if (!(rand() % 30))
            {   cloudbullet(which, x, y, -1);
                cloudbullet(which, x, y, 1);
        }   }
        elif (creature[which].species == OCTOPUS)
        {   if (creature[which].dir == -1)
            {   if (!(rand() % 20))
                {   creature[which].dir = 0;
}   }   }   }   }

MODULE void cloudbullet(UBYTE which, SBYTE x, SBYTE y, SBYTE deltay)
{   UBYTE i, c;

    for (i = 0; i <= CREATURES; i++)
    {   if (!creature[i].alive)
        {   creature[i].deltax = 0;
            creature[i].deltay = deltay;
            if (valid(x + creature[i].deltax, y + creature[i].deltay))
            {   c = field[x + creature[i].deltax][y + creature[i].deltay];
                if
                (   (c >= FIRSTEMPTY && c <= LASTEMPTY)
                ||  (c >= FIRSTTAIL && c <= LASTTAIL)
                )
                {   effect(FXDO_CLOUD);
                    creature[i].alive = TRUE;
                    creature[i].species = FRAGMENT;
                    creature[i].x = x;
                    creature[i].y = y + creature[i].deltay;
                    creature[i].visible = TRUE;
                    creature[i].last = EMPTY;
                    creature[i].speed = (SBYTE) atleast(FRAGSPEED - (level / 2), 1);
            }   }
            break;
}   }   }

MODULE void orbsplit(SBYTE which)
{   SBYTE   copy = 0, i;

    effect(FXDO_ORB);
    for (i = 0; i <= CREATURES; i++)
    {   if (!creature[i].alive)
        {   creature[i].x       = creature[which].x;
            creature[i].y       = creature[which].y;
            creature[i].score   = creature[which].score;
            creature[i].armour  = creature[which].armour;
            creature[i].tongue  = creature[which].tongue;
            creature[i].mode    = creature[which].mode;
            creature[i].speed   = creature[which].speed;
            creature[i].explode = FALSE;
            creature[i].multi   = creature[which].multi;
            creature[i].last    = EMPTY;
            creature[i].species = ORB;
            switch (copy)
            {
            case 0:
                if (creature[which].deltax != -1 || creature[which].deltay != -1)
                {   creature[i].deltax = -1;
                    creature[i].deltay = -1;
                    creature[i].alive = TRUE;
                }
            break;
            case 1:
                if (creature[which].deltax != 1 || creature[which].deltay != 1)
                {   creature[i].deltax = 1;
                    creature[i].deltay = 1;
                    creature[i].alive = TRUE;
                }
            break;
            case 2:
                if (creature[which].deltax != 1 || creature[which].deltay != -1)
                {   creature[i].deltax = 1;
                    creature[i].deltay = -1;
                    creature[i].alive = TRUE;
                }
            break;
            case 3:
                if (creature[which].deltax != -1 || creature[which].deltay != 1)
                {   creature[i].deltax = -1;
                    creature[i].deltay = 1;
                    creature[i].alive = TRUE;
                }
            break;
            default:
            break;
            }
            if (++copy >= 4)
                return;
}   }   }

SBYTE partner(SBYTE which)
{   if (which % 2 == 0)
        return((SBYTE) (which + 1));
    else return((SBYTE) (which - 1));
}

/* NAME     putletter -- Put a letter onto the field
SYNOPSIS    void putletter(SBYTE player);
INPUTS      SBYTE player -
            0-3: player on whose behalf the letter is put on for
            -1: any letter
RESULT      none */

MODULE void putletter(SBYTE player)
{   ABOOL done;
    SBYTE i, x, y;
    UBYTE letter;
    SBYTE oldlettery = lettery;

    do
    {   done = findempty(&x, &y, FIRSTEMPTY, LASTEMPTY);
    } while (!done);
    if (player != -1)
    {   for (i = 0; i <= LETTERS; i++)
            if (!(letters[player][i]))
            {   break;
            }
        if (i > LETTERS) /* if no spare letters */
            letter = rand() % (LETTERS + 1);
        else
        {   do
                letter = rand() % (LETTERS + 1);
            while (letters[player][letter]);
    }   }
    else letter = rand() % (LETTERS + 1);
    change(x, y, letter + FIRSTLETTER);

    letterx = x;
    lettery = y;
    lettertype = letter + FIRSTLETTER;
    updatearrow(oldlettery);
    updatearrow(lettery);
}

/* NAME     queue -- adds a keystroke to the key queue
SYNOPSIS    name(SBYTE, SBYTE, SBYTE);
FUNCTION    Adds a keystroke to the in-game key queue.
INPUTS      player - player that pressed the key
            deltax - the deltax of the key
            deltay - the deltay of the key
IMPLEMENTATION
            thewormqueue[] array has WORMQUEUELIMIT as its last index.
            It is implemented as a FIFO stack rather than LIFO so that
            the keystrokes are processed in the correct order (that is,
            the order in which they were pressed). The oldest keystroke
            is always at index [0], the next oldest at [1], and so on
            upwards to the newest keystroke, at [worm[player].pos].
            Keystrokes are removed from the bottom of the array ([0]),
            and the rest of the array is shuffled down to fill the gap,
            so that the contents of [1] go to [0], the contents of [2]
            go to [1], etc. worm[player].pos is adjusted to always point
            to the newest entry, which is the 'end' of the queue.
MODULE      engine.c */

void wormqueue(SBYTE player, SBYTE deltax, SBYTE deltay)
{       if (worm[player].pos < WORMQUEUELIMIT)
	{	worm[player].pos++;
		thewormqueue[player][worm[player].pos].deltax = deltax;
		thewormqueue[player][worm[player].pos].deltay = deltay;
}	}

MODULE void dogqueue(SBYTE which, SBYTE deltax, SBYTE deltay)
{   if (creature[which].pos < DOGQUEUELIMIT)
    {   creature[which].pos++;
        thedogqueue[which][creature[which].pos].deltax = deltax;
        thedogqueue[which][creature[which].pos].deltay = deltay;
    } else
    {   creature[which].alive = FALSE;
        change(creature[which].x, creature[which].y, EMPTY);
}   }

MODULE void reflect(UBYTE which)
{   creature[which].deltax  = -creature[which].deltax;
    creature[which].deltay  = -creature[which].deltay;
    creature[which].x      +=  creature[which].deltax * 2;
    creature[which].y      +=  creature[which].deltay * 2;
}

ABOOL savefields(STRPTR fieldname)
{   SBYTE i, j;
    TEXT  IOBuffer[NAMELENGTH + 1];

    matchteleports();

    if (!ZOpen(fieldname, TRUE))
		return FALSE;

    /* write header */

    strcpy(IOBuffer, "FSET 6.6");
    IOBuffer[9] = levels;
    if (!ZWrite(IOBuffer, 10))
    {   ZClose();
        return FALSE;
    }

    /* write high score table */

    for (i = 0; i <= HISCORES; i++)
    {   IOBuffer[0]                                             =  hiscore[i].player;
        IOBuffer[1]                                             =  hiscore[i].level;

        IOBuffer[2]                                             =   hiscore[i].score / 16777216;
        IOBuffer[3]                                             =  (hiscore[i].score % 16777216) / 65536;
        IOBuffer[4]                                             = ((hiscore[i].score % 16777216) % 65536) / 256;
        IOBuffer[5]                                             = ((hiscore[i].score % 16777216) % 65536) % 256;
        if (!ZWrite(IOBuffer, 6))
        {   ZClose();
            return FALSE;
        }

        for (j = 0; j <= NAMELENGTH; j++)
            IOBuffer[j]                                             = hiscore[i].name[j];
        if (!ZWrite(IOBuffer, NAMELENGTH + 1))
        {   ZClose();
            return FALSE;
        }
        for (j = 0; j <= TIMELENGTH; j++)
            IOBuffer[j]                                             = hiscore[i].time[j];
        if (!ZWrite(IOBuffer, TIMELENGTH + 1))
        {   ZClose();
            return FALSE;
        }
        for (j = 0; j <= DATELENGTH; j++)
            IOBuffer[j]                                             = hiscore[i].date[j];
        if (!ZWrite(IOBuffer, DATELENGTH + 1))
        {   ZClose();
            return FALSE;
    }   }

    /* write level data */

    for (i = 0; i <= levels; i++)
    {   IOBuffer[0]						= startx[i];
        IOBuffer[1]						= starty[i];
        IOBuffer[2]                                             = teleport[i][0].alive;
        IOBuffer[3]                                             = teleport[i][0].x;
        IOBuffer[4]                                             = teleport[i][0].y;
        IOBuffer[5]                                             = teleport[i][1].alive;
        IOBuffer[6]                                             = teleport[i][1].x;
        IOBuffer[7]                                             = teleport[i][1].y;
        if (!ZWrite(IOBuffer, 8))
        {   ZClose();
            return FALSE;
        }

        if (!ZWrite((char *) &board[i][0][0], LEVELSIZE))
        {   ZClose();
            return FALSE;
    }   }

    /* write version string */

    if (!ZWrite(VERSION, strlen(VERSION)))
    {   ZClose();
        return FALSE;
    }

    ZClose();

    if (clearthem)
        clearhiscores();
    modified = FALSE;
    return TRUE;
}

void saylevel(COLOUR colour)
{	TEXT mainstring[15] = "Level ", tempstring[4];

	if (level > 0)
	{	stci_d(&mainstring[6], level);
		strcat((char*) mainstring, " of ");
		stci_d(tempstring, levels);
		strcat((char*) mainstring, (char*) tempstring);
		say(mainstring, colour);
	} else
	{	if (a == FIELDEDIT)
			say("Bonus Level", colour);
		else
		{	if (leveltype == TREASURE)
                            say("Bonus Level: Treasury!", colour);
                        elif (leveltype == DRIP)
                            say("Bonus Level: Drips!", colour);
                        else
                        {   // assert(leveltype == PENGUIN);
                            say("Bonus Level: Penguins!", colour);
}       }       }       }

MODULE SBYTE slowdown(SBYTE speed, ABOOL nitro)
{   speed *= 2;
    if (nitro)
    {   if (speed > VERYSLOW)
            speed = VERYSLOW;
    } elif (speed > SLOW)
        speed = SLOW;
    return(speed);
}

MODULE void slowloop(void)
{   SBYTE i, player, which, x, xx, y, yy;
    UBYTE thissy;

    /* decrement worm strength */

    for (player = 0; player <= 3; player++)
        if (worm[player].lives > 0 && (ice == -1 || ice == player))
        {   if (worm[player].bias > 0)
            {   worm[player].bias--;
                stat(player, BIAS);
            }
            if (worm[player].cutter > 0)
            {   worm[player].cutter--;
                icon(player, CUTTER);
            }                
            if (worm[player].freedom > 0)
            {   worm[player].freedom--;
                icon(player, FREEDOM);
            }
            if (worm[player].ice > 0)
            {   worm[player].ice--;
                icon(player, ICE);
                for (which = 0; which <= 3; which++)
                    if (player != which)
                        worm[which].pos = -1;
                ice = -1;
                icon(player, ICE);
            }
            if (worm[player].mode == ARMOUR)
            {   if (--worm[player].armour == 0)
                {   if (worm[player].tongue > 0)
                        worm[player].mode = TONGUE;
                    else worm[player].mode = NOMODE;
                }
                stat(player, ARMOUR);
            } elif (worm[player].mode == TONGUE)
            {   if (--worm[player].tongue == 0)
                {   if (worm[player].armour > 0)
                        worm[player].mode = ARMOUR;
                    else worm[player].mode = NOMODE;
                }
                stat(player, TONGUE);
        }   }

    /* blank out old causes */

    for (player = 0; player <= 3; player++)
    {   if (worm[player].lives > 0 && r > worm[player].causewait)
        {   drawcause(player, BLACK);
            worm[player].causewait = (ULONG) -1; /* most future time possible */
    }   }

if (ice == -1)
{   if (level)
    {	/* decrement orb strength */
        for (which = 0; which <= CREATURES; which++)
            if (creature[which].alive && creature[which].species == ORB)
                if (creature[which].mode == ARMOUR)
                {   if (--creature[which].armour == 0)
                        if (creature[which].tongue > 0)
                            creature[which].mode = TONGUE;
                        else creature[which].mode = NOMODE;
                } elif (creature[which].mode == TONGUE)
                {   if (--creature[which].tongue == 0)
                        if (creature[which].armour > 0)
                            creature[which].mode = ARMOUR;
                        else creature[which].mode = NOMODE;
                }

        /* create goats */
        if
        (   (!(rand() % freq))
        &&  findempty(&x, &y, FIRSTGOAT, LASTGOAT)
        &&  field[x][y] != GOAT
        )
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   effect(FXBORN_GOAT);
                    creature[i].x = x;
                    creature[i].y = y;
                    creature[i].alive = TRUE;
                    creature[i].species = GOAT;
                    creature[i].last = creature[i].oldlast = field[x][y];
                    creature[i].speed = (SBYTE) atleast(GOATSPEED - (level / 2), 2);
                    creature[i].visible = TRUE;
                    change(x, y, GOAT);
                    break;
        }   }   }

        /* create octopi */
        if
        (   (!(rand() % (freq * 2)))
        &&  findempty(&x, &y, FIRSTGOAT, LASTGOAT)
        &&  field[x][y] != GOAT
        )
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   creature[i].x       = x;
                    creature[i].y       = y;
                    creature[i].alive   = TRUE;
                    creature[i].species = OCTOPUS;
                    creature[i].visible = TRUE;
                    creature[i].speed   = VERYSLOW;
                    creature[i].dir     = -1;
                    creature[i].deltax  = creature[i].deltay = 0;
                    change(x, y, OCTOPUS);
                    break;
        }   }   }

        /* create orbs */
        if
        (   (!(rand() % freq))
         && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY)
        )
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   effect(FXBORN_ORB);
                    creature[i].deltax = (rand() % 2) * 2 - 1;
                    creature[i].deltay = (rand() % 2) * 2 - 1;
                    creature[i].score = 0;
                    creature[i].alive = TRUE;
                    creature[i].armour = 0;
                    creature[i].tongue = 0;
                    creature[i].mode = NOMODE;
                    creature[i].speed = (SBYTE) atleast(ORBSPEED - (level / 2), 2);
                    creature[i].species = ORB;
                    creature[i].explode = FALSE;
                    creature[i].multi = 1;
                    creature[i].x = x;
                    creature[i].y = y;
                    creature[i].last = EMPTY;
                    creature[i].visible = TRUE;
                    change(x, y, ORB);
                    break;
        }   }   }

        /* create dogs */
        if ((!(rand() % freq)) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
        {   for (i = 0; i <= CREATURES; i++)
                if (!(creature[i].alive))
                {   creature[i].x = x;
                    creature[i].y = y;
                    creature[i].last = EMPTY;
                    creature[i].speed = (SBYTE) atleast(DOGSPEED - (level / 2), 2);
                    creature[i].visible = TRUE;
                    creature[i].deltax = 0;
                    creature[i].deltay = 0;
                    creature[i].species = DOG;
                    creature[i].alive = TRUE;
                    creature[i].pos = -1;
                    creature[i].dormant = DORMANT; /* dormant */
                    field[x][y] = DOG;
                    draw(x, y, DOGDORMANT);
                    break;
        }       }

        /* create slime */
        if ((!(rand() % (freq * 2))) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
            change(x, y, SLIME);

        /* grow slime */
        if (!(rand() % (freq * 2)))
        {   for (x = 0; x <= FIELDX; x++)
                for (y = 0; y <= FIELDY; y++)
                    if (field[x][y] == SLIME)
                        for (xx = x - 1; xx <= x + 1; xx++)
                            for (yy = y - 1; yy <= y + 1; yy++)
                                if (valid(xx, yy) && field[xx][yy] == EMPTY && !(rand() % 2))
                                    field[xx][yy] = TEMPSLIME;
            for (x = 0; x <= FIELDX; x++)
                for (y = 0; y <= FIELDY; y++)
                    if (field[x][y] == TEMPSLIME)
                        change(x, y, SLIME);
        }

        /* create timebombs */
        if ((!(rand() % (freq * 8))) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
        {   for (i = 0; i <= CREATURES; i++)
            if (!(creature[i].alive))
            {   creature[i].x = x;
                creature[i].y = y;
                creature[i].last = EMPTY;
                creature[i].speed = (SBYTE) atleast(TIMEBOMBSPEED - (level / 2), 2);
                creature[i].visible = TRUE;
                creature[i].deltax = 0;
                creature[i].deltay = 0;
                creature[i].species = TIMEBOMB;
                creature[i].alive = TRUE;
                creature[i].time = 10;
                field[x][y] = TIMEBOMB;
                draw(x, y, ZERO + 9);
                break;
    }   }   }

    /* create drips */
    if
    (   (level && (!(rand() % freq)))
     || ((!level) && leveltype == DRIP && (!(rand() % FREQ_DRIP)))
    )
    {   x = (rand() % (FIELDX + 1));
        y = (rand() % 3);
        thissy = field[x][y];
        if (thissy >= FIRSTEMPTY && thissy <= LASTEMPTY)
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   effect(FXBORN_DRIP);
                    creature[i].alive      = TRUE;
                    creature[i].last       = EMPTY;
                    creature[i].species    = DRIP;
                    creature[i].type       = rand() % 4;
                    creature[i].x          = x;
                    creature[i].y          = y;
                    creature[i].deltax     = 0;
                    creature[i].deltay     = 1;
                    creature[i].visible    = TRUE;
                    if (level)
                        creature[i].speed  = (SBYTE) atleast(DRIPSPEED - (level / 2), 2);
                    else creature[i].speed = BONUSDRIPSPEED;
                    change(x, y, FIRSTDRIP + creature[i].type);
                    break;
    }   }   }   }

    /* create penguins */
    if
    ((   (  level  && !(rand() % freq))
     ||  ((!level) && leveltype == PENGUIN && (!(rand() % FREQ_PENGUIN)))
     ) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY)
    )
    {   for (i = 0; i <= CREATURES; i++)
            if (!(creature[i].alive))
            {   effect(FXBORN_PENGUIN);
                creature[i].x = x;
                creature[i].y = y;
                creature[i].last = EMPTY;
                if (level)
                    creature[i].speed = (SBYTE) atleast(PENGUINSPEED - (level / 2), 2);
                else creature[i].speed = BONUSPENGUINSPEED;
                creature[i].visible = TRUE;
                creature[i].deltax = 0;
                creature[i].deltay = 0;
                creature[i].species = PENGUIN;
                creature[i].alive = TRUE;
                change(x, y, PENGUIN);
                break;
    }       }

    /* create clouds */
    if (level && (!(rand() % freq)))
    {   x = (rand() % (FIELDX - 1)) + 1;
        y = (rand() % (FIELDY - 1)) + 1;
        /* If you create clouds at an extreme horizontal position
        (0 or FIELDX), will cause a crash because the deltas will be
        wrong. */
        thissy = field[x][y];
        if (thissy >= FIRSTEMPTY && thissy <= LASTEMPTY)
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   creature[i].alive       = TRUE;
                    creature[i].last        = EMPTY;
                    creature[i].species     = CLOUD;
                    creature[i].x           = x;
                    creature[i].y           = y;
                    if (x < FIELDX / 2)
                        creature[i].deltax  = 1;
                    else creature[i].deltax = -1;
                    creature[i].deltay      = 0;
                    creature[i].visible     = TRUE;
                    creature[i].speed       = (SBYTE) atleast(CLOUDSPEED - (level / 2), 3);
                    change(x, y, CLOUD);
                    break;
    }   }   }   }

    /* create objects */
    for (which = 0; which <= LASTOBJECT; which++)
        if (level || leveltype != TREASURE || which == TREASURE)
        {   if (!(rand() % object[which].freq) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
                change(x, y, which);
        } elif (!(rand() % (object[which].freq / 10)) && findempty(&x, &y, FIRSTEMPTY, LASTEMPTY))
            change(x, y, which);

    /* create teleports */
    if (!(rand() % FREQ_TELEPORT)
    && !teleport[level][2].alive
    && findempty(&(teleport[level][2].x), &(teleport[level][2].y), FIRSTEMPTY, LASTEMPTY)
    && findempty(&(teleport[level][3].x), &(teleport[level][3].y), FIRSTEMPTY, LASTEMPTY)
    && (teleport[level][2].x != teleport[level][3].x || teleport[level][2].y != teleport[level][3].y))
    {   teleport[level][2].alive = TRUE;
        teleport[level][3].alive = TRUE;
        change(teleport[level][2].x, teleport[level][2].y, TELEPORT);
        change(teleport[level][3].x, teleport[level][3].y, TELEPORT);
}   }
}

MODULE SBYTE speedup(SBYTE speed, ABOOL nitro)
{	speed /= 2;
	if (speed < FAST)
		speed = FAST;
	return(speed);
}

MODULE ULONG squareblast(SBYTE type, SBYTE player, SBYTE thissy, SBYTE x, SBYTE y, ABOOL cutter)
{   SBYTE which;
    ULONG score = 0;
    SBYTE filler;

    if (type == HEAD && worm[player].bias)
        filler = SILVER;
    else filler = EMPTY;

    if (thissy <= LASTOBJECT)
    {   if (!cutter)
        {   change(x, y, filler);
    }   }
    elif (thissy >= FIRSTTAIL && thissy <= LASTTAIL)
    {   change(x, y, filler);
    } elif (thissy == ORB)
    {   which = whichcreature(x, y, ORB, 255);
        if (creature[which].mode != ARMOUR)
        {   effect(FXDEATH_ORB);
            creature[which].alive = FALSE;
            score = creature[which].score;
            if (type == HEAD && worm[player].bias)
                worm[player].lives += ORBBLOOD;
            change(x, y, BONUS);
        } else effect(FXUSE_ARMOUR);
    } elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
    {   creature[whichcreature(x, y, DRIP, 255)].alive = FALSE;
        change(x, y, filler);
    } elif (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
    {   if (type != HEAD || player != thissy - FIRSTHEAD)
            if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
            {   worm[thissy - FIRSTHEAD].cause = BOMB;
                worm[thissy - FIRSTHEAD].alive = FALSE;
                stat(thissy - FIRSTHEAD, LIFE);
                if (type == HEAD)
                    worm[thissy - FIRSTHEAD].victor = player;
                else
                {   worm[thissy - FIRSTHEAD].victor = -1;
                    score = KILLWORM; // worms will get this bonus from death(), so it is not given for them here.
            }   }
            else effect(FXUSE_ARMOUR);
    } elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
    {   if (type != HEAD || player != thissy - FIRSTMISSILE)
        {   creature[whichcreature(x, y, MISSILE, 255)].alive = FALSE;
            change(x, y, filler);
    }	}
    elif (thissy == PENGUIN)
    {	creature[whichcreature(x, y, PENGUIN, 255)].alive = FALSE;
        change(x, y, filler);
    } elif (thissy == FRAGMENT)
    {	creature[whichcreature(x, y, FRAGMENT, 255)].alive = FALSE;
        change(x, y, filler);
    } elif (thissy == GOAT)
    {   creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
        change(x, y, BONUS);
    }
    return(score);
}

void timeloop(void)
{           TEXT  timedisplay[5] = {"#:##"};
    PERSIST ABOOL outoftime      = FALSE;
            UBYTE i;

	secondsleft = atleast(secondsleft, 0);
	timedisplay[0] = 48 +  (secondsleft / 60);
	timedisplay[2] = 48 + ((secondsleft % 60) / 10);
	timedisplay[3] = 48 + ((secondsleft % 60) % 10);

	if (!level)
    {   say(timedisplay, worm[treasurer].colour);
        if (!secondsleft)
        {   level = reallevel + 1;
            secondsleft = SECONDSPERLEVEL;
            newlevel(treasurer);
    }   }
	elif (!secondsleft)
    {   if (!outoftime)
        {   effect(FXSIREN);
            say("Out of time!", WHITE);
            outoftime = TRUE;
            freq /= 2;
            for (i = 0; i <= CREATURES; i++)
                if (creature[i].alive)
                    creature[i].speed = speedup(creature[i].speed, TRUE);
    }   }
	else
	{	outoftime = FALSE;
		say(timedisplay, WHITE);
}	}

void train(SCANCODE scancode)
{   SBYTE i, x, y;

    switch(scancode)
    {
    case HELP:
        trainer = !trainer;
    break;
    case NUMERICSLASH:
        /* Complete the level. */
        if (trainer)
        {   trainer = FALSE;
            if (worm[1].lives > 0)
            for (i = 0; i <= LETTERS; i++)
            {   letters[1][i] = TRUE;
                drawletter(1, FIRSTLETTER + i, NORMAL);
        }   }
    break;
    case NUMERICASTERISK:
        /* field[][] dump, for debugging purposes. */
        if (trainer)
        {   trainer = FALSE;
            say("Field dump...", PURPLE);
            for (x = 0; x <= FIELDX; x++)
                for (y = 0; y <= FIELDY; y++)
                    draw(x, y, field[x][y]);
            anykey(FALSE);
        }
        break;
    case NUMERICPLUS:
        if (trainer)
        {   trainer = FALSE;
            say("Trainer activated!", PURPLE);
            anykey(FALSE);
            if (worm[1].lives > 0)
            {   worm[1].lives = LIVESLIMIT;
                stat(1, LIFE);
                worm[1].tongue = MODELIMIT;
                worm[1].mode = TONGUE;
                stat(1, TONGUE);
                worm[1].bias = BIASLIMIT;
                stat(1, BIAS);
                worm[1].ammo = AMMOLIMIT;
                stat(1, AMMO);
                worm[1].power = POWERLIMIT;
                stat(1, POWER);
                worm[1].nitro = TRUE;
                stat(1, NITRO);
                worm[1].affixer = TRUE;
                icon(1, AFFIXER);
                worm[1].remnants = TRUE;
                icon(1, REMNANTS);
                worm[1].sideshot = TRUE;
                icon(1, SIDESHOT);
                worm[1].pusher = TRUE;
                icon(1, PUSHER);
                worm[1].cutter = 100;
                icon(1, CUTTER);
                trainer = FALSE;
        }   }
    break;
    default:
    break;
}   }

MODULE void turnworm(SBYTE player, SBYTE deltax, SBYTE deltay)
{
if (worm[player].nitro || !deltax || !deltay)
{	if (worm[player].deltax == deltax && worm[player].deltay == deltay)
	{	worm[player].speed = speedup(worm[player].speed, worm[player].nitro);
		stat(player, NITRO);
	} elif (worm[player].deltax == -deltax && worm[player].deltay == -deltay)
        {       worm[player].speed = slowdown(worm[player].speed, worm[player].nitro);
		stat(player, NITRO);
	} else
	{	worm[player].deltax = deltax;
		worm[player].deltay = deltay;
}	}
}

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

MODULE UBYTE whichcreature(SBYTE x, SBYTE y, UBYTE species, UBYTE exception)
{   UBYTE i;	

    for (i = 0; i <= CREATURES; i++)
        if
		(   creature[i].alive
         && creature[i].x == x
         && creature[i].y == y
         && creature[i].species == species
		 && i != exception
		)
            return i;
    return 255; /* error code */
}
MODULE SBYTE whichteleport(SBYTE x, SBYTE y)
{   SBYTE which;

    for (which = 0; which <= 3; which++)
        if (teleport[level][which].alive && teleport[level][which].x == x && teleport[level][which].y == y)
            return((SBYTE) which);
    return((SBYTE) -1); /* error code */
}

MODULE void wormbullet(SBYTE player)
{   ABOOL   finished,
            flag            = FALSE,
            ok              = FALSE,
            lettered        = FALSE;
    LONG    score;
    SBYTE   distance,
            thissy,
            i, j,
            x, y;

    if (!worm[player].ammo)
    {   stat(player, BONUS);
	if (worm[player].speed == FAST)
	    distance = FASTDISTANCE;
	elif (worm[player].speed == NORMAL)
	   distance = NORMALDISTANCE;
        elif (worm[player].speed == SLOW)
	   distance = SLOWDISTANCE;
        else
        {  // assert(worm[player].speed == VERYSLOW);
           distance = VERYSLOWDISTANCE;
        }

	/* check for metal */

	for (i = 1; i < distance; i++)
	{   x = xwrap(worm[player].x + (i * worm[player].deltax));
	    y = ywrap(worm[player].y + (i * worm[player].deltay));
	    if (field[x][y] == METAL)
		flag = TRUE;
	}

	if (!flag)
        {   // assert(abs(worm[player].deltax) <= 1 && abs(worm[player].deltay) <= 1);
	    x = xwrap(worm[player].x + (worm[player].deltax * distance));
	    y = ywrap(worm[player].y + (worm[player].deltay * distance));
	    thissy = field[x][y];
	    if (thissy == TELEPORT)
	    {   i = whichteleport(x, y);
	        if (!blocked(i, worm[player].deltax, worm[player].deltay))
		    ok = TRUE;
	    }
            if (ok || ((thissy < STONE || thissy > GOAT) && thissy != METAL))
            {   effect(FXDO_JUMP);
	        worm[player].deltax *= distance;
	        worm[player].deltay *= distance;
    }   }   }
    else
    {   effect(FXUSE_AMMO);
        worm[player].ammo--;
        stat(player, AMMO);
        if (worm[player].bias)
            createmissile(player, worm[player].x, worm[player].y);

        if (worm[player].sideshot)
        {   bullet[7].alive      = bullet[8].alive      = TRUE;
            bullet[7].teleported = bullet[8].teleported = 0;
            bullet[7].visible    = bullet[8].visible    = TRUE;
            bullet[7].reflected  = bullet[8].reflected  = FALSE;
            bullet[7].x          = bullet[8].x          = worm[player].x;
            bullet[7].y          = bullet[8].y          = worm[player].y;
            if (!worm[player].deltax && worm[player].deltay)
            {   bullet[7].deltax = -1;
                bullet[8].deltax = 1;
                bullet[7].deltay = bullet[8].deltay = 0;
            } elif (worm[player].deltax && !worm[player].deltay)
            {   bullet[7].deltax = bullet[8].deltax = 0;
                bullet[7].deltay = -1;
                bullet[8].deltay = 1;
            } else /* worm is diagonal */
            {   if (worm[player].deltax == worm[player].deltay)
                {   bullet[7].deltax = 1;
                    bullet[7].deltay = -1;
                } else
                {   bullet[7].deltax = -1;
                    bullet[7].deltay = -1;
                }
                bullet[8].deltax = -bullet[7].deltax;
                bullet[8].deltay = -bullet[7].deltay;
        }   }

        for (i = 0; i <= worm[player].power; i++)
        {   bullet[i].alive      = TRUE;
            bullet[i].teleported = 0;
            bullet[i].visible    = TRUE;
            bullet[i].reflected  = FALSE;
            bullet[i].deltax     = worm[player].deltax;
            bullet[i].deltay     = worm[player].deltay;
            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;
            } elif (worm[player].deltay == 0)
            {   bullet[i].x = worm[player].x;
                bullet[i].y = worm[player].y + distance;
            } else
            {   switch (i)
                {
                case 0:
                    bullet[i].x = worm[player].x + worm[player].deltax;
                    bullet[i].y = worm[player].y + worm[player].deltay;
                break;
                case 1:
                    bullet[i].x = worm[player].x + worm[player].deltax;
                    bullet[i].y = worm[player].y;
                break;
                case 2:
                    bullet[i].x = worm[player].x;
                    bullet[i].y = worm[player].y + worm[player].deltay;
                break;
                case 3:
                    bullet[i].x = worm[player].x + worm[player].deltax * 2;
                    bullet[i].y = worm[player].y;
                break;
                case 4:
                    bullet[i].x = worm[player].x;
                    bullet[i].y = worm[player].y + worm[player].deltay * 2;
                break;
                case 5:
                    bullet[i].x = worm[player].x + worm[player].deltax * 2;
                    bullet[i].y = worm[player].y - worm[player].deltay;
                break;
                case 6:
                    bullet[i].x = worm[player].x - worm[player].deltax;
                    bullet[i].y = worm[player].y + worm[player].deltay * 2;
                break;
                default:
                break;
        }   }   }

        /* Bullets are now set up. */

        score = 0;
        finished = FALSE;
        while (!finished)
        {   finished = TRUE;
            for (i = 0; i <= 8; i++)
            {   if (bullet[i].alive)
                {   finished = FALSE;
                    if (bullet[i].reflected)
                    {   bullet[i].x -= bullet[i].deltax;
                        bullet[i].y -= bullet[i].deltay;
                    } else
                    {   bullet[i].x += bullet[i].deltax;
                        bullet[i].y += bullet[i].deltay;
                    }
                    x = bullet[i].x;
                    y = bullet[i].y;
                    thissy = field[x][y];
                    if (!(valid(x, y)))
                        bullet[i].alive = FALSE;
                    elif (x == worm[player].x && y == worm[player].y)
                    {   /* hit by own bullet */
                        bullet[i].alive = FALSE;
                        if (worm[player].mode != ARMOUR)
                        {   worm[player].cause = FIRSTFIRE + player;
                            worm[player].victor = -1;
                            worm[player].alive = FALSE;
                    }   }
                    elif (thissy >= FIRSTHEAD && thissy <= LASTHEAD)
                    {   if (worm[thissy - FIRSTHEAD].mode != ARMOUR)
                        {   worm[thissy - FIRSTHEAD].cause = FIRSTFIRE + player;
                            worm[thissy - FIRSTHEAD].victor = player;
                            worm[thissy - FIRSTHEAD].alive = FALSE;
                            if (player != thissy - FIRSTHEAD)
                                score += KILLWORM + HITSHOT;
                        } else effect(FXUSE_ARMOUR);
                        bullet[i].alive = FALSE;
                    } elif (thissy >= FIRSTPROTECTOR && thissy <= LASTPROTECTOR)
                    {   if (player != thissy - FIRSTPROTECTOR)
                        {   effect(FXUSE_PROTECTOR);
                            bullet[i].alive = FALSE;
                        } else bullet[i].visible = FALSE;
                    } elif (thissy >= FIRSTMISSILE && thissy <= LASTMISSILE)
                    {   if (player != thissy - FIRSTMISSILE)
                            creature[whichcreature(x, y, MISSILE, 255)].alive = FALSE;
                        else bullet[i].visible = FALSE;
                    } elif (thissy >= FIRSTDRIP && thissy <= LASTDRIP)
                    {   j = whichcreature(x, y, DRIP, 255);
                        creature[j].alive = FALSE;
                    } elif (thissy >= FIRSTLETTER && thissy <= LASTLETTER)
                    {   wormletter(player, thissy);
                        lettered = TRUE;
                    } else
                    {   switch(thissy)
                        {
                        case METAL:
                            if (bullet[i].reflected)
                                bullet[i].alive = FALSE;
                            else
                            {   bullet[i].reflected = TRUE;
                                bullet[i].x -= bullet[i].deltax * 2;
                                bullet[i].y -= bullet[i].deltay * 2;
                            }
                            break;
                        case STONE:
                        case WHIRLWIND:
                            bullet[i].alive = FALSE;
                        break;
                        case ORB:
                            bullet[i].alive = FALSE;
                            j = whichcreature(x, y, ORB, 255);
                            if (creature[j].mode != ARMOUR)
                            {   creature[j].explode = TRUE;
                                score += creature[j].score + HITSHOT;
                                if (worm[player].bias)
                                    worm[player].lives += ORBBLOOD;
                            } else effect(FXUSE_ARMOUR);
                        break;
                        case TELEPORT:
                            j = whichteleport(bullet[i].x, bullet[i].y);
                            if (bullet[i].teleported == 2)
                                bullet[i].alive = FALSE;
                            else
                            {   effect(FXUSE_TELEPORT);
                                bullet[i].visible = FALSE;
                                bullet[i].teleported++;
                                bullet[i].x = teleport[level][partner(j)].x;
                                bullet[i].y = teleport[level][partner(j)].y;
                                score += TELPOINT;
                            }
                        break;
                        case WOOD:
                            if (!worm[player].bias)
                                bullet[i].alive = FALSE;
                        break;
                        case OCTOPUS:
                            creature[whichcreature(x, y, OCTOPUS, 255)].alive = FALSE;
                            change(x, y, BONUS);
                            score += KILLOCTOPUS + HITSHOT;
                            if (worm[player].bias)
                            {   worm[player].lives += OCTOPUSBLOOD;
                            }
                            bullet[i].alive = FALSE;
                        break;
                        case GOAT:
                            effect(FXDEATH_GOAT);
                            creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
                            change(x, y, BONUS);
                            score += KILLGOAT + HITSHOT;
                            if (worm[player].bias)
                            {   worm[player].lives += GOATBLOOD;
                            }
                            bullet[i].alive = FALSE;
                        break;
                        case SKULL:
                            bullet[i].alive = FALSE;
                        break;
                        case FRAGMENT:
                            bullet[i].alive = FALSE;
                            creature[whichcreature(x, y, FRAGMENT, 255)].alive = FALSE;
                            change(x, y, EMPTY);
                        break;
                        case PENGUIN:
                            bullet[i].alive = FALSE;
                            creature[whichcreature(x, y, PENGUIN, 255)].alive = FALSE;
                            change(x, y, EMPTY);
                            score += KILLPENGUIN + HITSHOT;
                            if (worm[player].bias)
                                worm[player].lives += PENGUINBLOOD;
                        break;
                        case DOG:
                            bullet[i].alive = FALSE;
                        break;
                        case CLOUD:
                            creature[whichcreature(x, y, CLOUD, 255)].alive = FALSE;
                            change(x, y, EMPTY);
                        break;
                        case TIMEBOMB:
                            creature[whichcreature(x, y, TIMEBOMB, 255)].alive = FALSE;
                            bombblast(BOMB, 0, x, y);
                            change(x, y, EMPTY);
                        break;
                        default:
                        break;
                    }   }

                    // x and y need this refreshing here
                    x = bullet[i].x;
                    y = bullet[i].y;
                    if (bullet[i].alive && bullet[i].visible)
                    {   draw(x, y, FIRSTFIRE + player);
                        if (worm[player].remnants)
                            field[x][y] = FIRSTFIRE + player;
                        elif (bullet[i].teleported)
                            change(x, y, GOLD);
                        else change(x, y, EMPTY);
        }   }   }   }
        if (lettered)
            putletter(player);
        wormscore(player, score);
        if (worm[player].bias)
            stat(player, LIFE);
        clearkybd();
}   }

MODULE void wormletter(SBYTE player, SBYTE c)
{   switch(c)
    {
    case GREEN_C:
        effect(FX_C);
    break;
    case RED_O:
        effect(FX_O);
    break;
    case BLUE_M:
        effect(FX_M);
    break;
    case YELLOW_P:
       effect(FX_P);
    break;
    case GREEN_L:
        effect(FX_L);
    break;
    case BLUE_T:
        effect(FX_T);
    break;
    default:
        // assert(c == RED_E || c == YELLOW_E);
        effect(FX_E);
    break;
    }
    letters[player][c - FIRSTLETTER] = TRUE;
    drawletter(player, c, NORMAL);
    wormscore(player, LETTERPOINT);
}

/* NAME     wormloop -- controls worms and protectors
SYNOPSIS    wormloop(SBYTE);
FUNCTION    Amiga-worm thinking, processing one keystroke from the
            worm's queue, all the worm's protectors' control, the
            worm's control.
MODULE      engine.c */

MODULE void wormloop(SBYTE player)
{   AUTO    ABOOL enclosed = FALSE, ok;
    AUTO    SBYTE bestx = 0, besty = 0, // to avoid spurious warnings
                  c, d, dirx, diry, i, k, thisprot, x, y,
                  leftx, rightx, topy, bottomy;
    AUTO    SWORD bestgood, good;
    AUTO    UBYTE interior;
    AUTO    ULONG score = 0;
    PERSIST struct
    {   SBYTE deltax[4], deltay[4];
    } deltas[4] =
    { { { 0, -1,  0,  1}, // northwest
        {-1,  0,  1,  0}
      },
      { { 0,  1,  0, -1}, // northeast
        {-1,  0,  1,  0}
      },
      { { 0,  1,  0, -1}, // southeast
        { 1,  0, -1,  0}
      },
      { { 0, -1,  0,  1}, // southwest
        { 1,  0, -1,  0}
    } };
    PERSIST struct
    {   SBYTE leftx, rightx, topy, bottomy;
    } enclose[4] =
    { {-127,   -1, -127,  -1 }, // northwest
      {   1,  127, -127,  -1 }, // northeast
      {   1,  127,    1, 127 }, // southeast
      {-127,   -1,    1, 127 }  // southwest
    };

   /*  Amiga worm control
       Remove a keystroke from the worm queue
       Move worm (and add a keystroke to the dog queue)
       Check for enclosure
       Move protectors
       Collision detection

    AI: Amiga worm control.

    Worm checks ahead, left and right one square. Assigns opinions
    to those three choices and then takes the appropriate one. */

    if (worm[player].control == AMIGA)
    {   if (!(rand() % 50))
            wormqueue(player, (rand() % 3) - 1, (rand() % 3) - 1);
        else
        {   bestgood = -128;

            for (i = 0; i <= 2; i++)
            {   switch(i)
                {
                case 0:
                    dirx = worm[player].deltax;
                    diry = worm[player].deltay;
                break;
                case 1:
                    if (worm[player].deltax == 0) /* if going north or south */
                    {   dirx = -1;                /* then look west */
                        diry = 0;
                    } else                        /* if going east or west */
                    {   dirx = 0;                 /* then look north */
                        diry = -1;
                    }
                break;
                case 2:
                    if (worm[player].deltax == 0) /* if going north or south */
                    {   dirx = 1;                 /* then look east */
                        diry = 0;
                    } else                        /* if going east or west */
                    {   dirx = 0;                 /* then look south */
                        diry = 1;
                    }
                break;
                default:
                    dirx = diry = 0; // to prevent spurious warnings
                break;
                }
                c = field[xwrap(worm[player].x + dirx)][ywrap(worm[player].y + diry)];
                if (c >= FIRSTLETTER && c <= LASTLETTER)
                    good = LETTERPOINT;
                elif (c >= FIRSTHEAD && c <= LASTHEAD)
                    good = -55;
                elif (c <= LASTOBJECT)
                    good = (SWORD) object[c].score;
                elif (c == FIRSTPROTECTOR + player)
                    good = 6;
                elif (c == FIRSTFIRE && c <= LASTFIRE)
		{   if (player == c - FIRSTFIRE)
		        good = -1;
                    else good = -70;
                } elif (c >= FIRSTTAIL && c <= LASTTAIL)
                {   if (worm[player].mode == TONGUE && worm[player].tongue > 10)
                        if (player != c - FIRSTTAIL)
                            good = 10;
                        else good = -10;
                    elif (player == c - FIRSTTAIL)
                        good = -30;
                    else good = -60;
                } else switch(c)
                {
                case SKULL:
                    good = 70;
                break;
                case GOLD:
                    good = GOLDPOINT;
                break;
                case SILVER:
                    good = SILVERPOINT;
                break;
                case EMPTY:
                    good = EMPTYPOINT;
                break;
                case SLIME:
                    if (worm[player].mode == ARMOUR)
                        good = -2;
                    else good = -75;
                break;
                case WOOD:
                    if (worm[player].mode == ARMOUR)
                        good = -2;
                    else good = -70;
                break;
                case STONE:
                    good = -85;
                break;
                case METAL:
                    good = -90;
                break;
                case GOAT:
                    good = -95;
                break;
                case WHIRLWIND:
                    good = -100;
                break;
                case ORB:
                    good = -45;
                break;
                case CLOUD:
                    good = -40;
                break;
                default:
                    good = -50;
                break;
                }
                if (good > bestgood)
                {   bestx = dirx;
                    besty = diry;
                    bestgood = good;
            }   }
            if (bestgood <= -80)
            {   // turn to the right
                if (worm[player].deltax == 0) /* if going north or south */
                {   bestx = 1;                /* then turn east */
                    besty = 0;
                } else                        /* if going east or west */
                {   bestx = 0;                /* then turn south */
                    besty = 1;
            }   }
            if (bestgood <= -60 && (!(rand() % 5)))
                wormqueue(player, 0, 0);
            elif (bestgood < 0 && (!(rand() % 2)))
                wormqueue(player, 0, 0);
            elif (bestx != worm[player].deltax || besty != worm[player].deltay)
                wormqueue(player, bestx, besty);
    }   }

    /* remove a keystroke from the worm queue */

    if (worm[player].pos != -1)
    {   if (thewormqueue[player][0].deltax == 0 && thewormqueue[player][0].deltay == 0)
            wormbullet(player);
        else turnworm(player, thewormqueue[player][0].deltax, thewormqueue[player][0].deltay);
        if (--worm[player].pos != -1)
        {   for (i = 0; i <= worm[player].pos; i++)
            {   thewormqueue[player][i].deltax = thewormqueue[player][i + 1].deltax;
                thewormqueue[player][i].deltay = thewormqueue[player][i + 1].deltay;
    }   }   }

    /* move worm */

    change(worm[player].x, worm[player].y, worm[player].last);
    worm[player].x = xwrap(worm[player].x + worm[player].deltax);
    worm[player].y = ywrap(worm[player].y + worm[player].deltay);
    if (worm[player].freedom)
        worm[player].last = FIRSTFIRE + player;
    else worm[player].last = FIRSTTAIL + player;

    for (i = 0; i <= CREATURES; i++)
    {   if (creature[i].alive && creature[i].species == DOG && creature[i].dormant > DORMANT && creature[i].type == player)
        {   if (!worm[player].rammed)
                dogqueue(i, worm[player].deltax, worm[player].deltay);
            if (creature[i].dormant < CHASING)
            {   creature[i].dormant++;
                draw(creature[i].x, creature[i].y, DOGAWAKENING);
    }   }   }

    /* The deltas are not changed back to the range of -1..1 until after
    the dogs have looked at the queue. This enables them to jump properly. */

    worm[player].rammed = FALSE;
    worm[player].deltax = bsign(worm[player].deltax);
    worm[player].deltay = bsign(worm[player].deltay);

    /*  check for enclosure
        #####
        #...#
        #...# . = interior
        #...# # = tail
        ####! ! = head */

    for (interior = 2; interior <= 7; interior++) // for each size of interior
    {   for (i = 0; i <= 3; i++) // four times, once for each direction
        {   x = worm[player].x;
            y = worm[player].y;
            ok = TRUE;

            for (k = 0; k <= interior; k++)
            {   x += deltas[i].deltax[0];
                y += deltas[i].deltay[0];
                if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player)
                {   ok = FALSE;
                    break;
            }   }
            for (k = 0; k <= interior; k++)
            {   x += deltas[i].deltax[1];
                y += deltas[i].deltay[1];
                if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player)
                {   ok = FALSE;
                    break;
            }   }
            for (k = 0; k <= interior; k++)
            {   x += deltas[i].deltax[2];
                y += deltas[i].deltay[2];
                if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player)
                {   ok = FALSE;
                    break;
            }   }
            // now the last side
            for (k = 0; k <= interior - 1; k++)
            {   x += deltas[i].deltax[3];
                y += deltas[i].deltay[3];
                if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player)
                {   ok = FALSE;
                    break;
            }   }
            if (ok)
            {   effect(FXDO_ENCLOSE);
                enclosed = TRUE;
                if (enclose[i].leftx < -1)
                    enclose[i].leftx = -interior;
                elif (enclose[i].leftx > 1)
                    enclose[i].leftx = interior + 1;
                if (enclose[i].rightx < -1)
                    enclose[i].rightx = -(interior + 1);
                elif (enclose[i].rightx > 1)
                    enclose[i].rightx = interior;
                if (enclose[i].topy < -1)
                    enclose[i].topy = -interior;
                elif (enclose[i].topy > 1)
                    enclose[i].topy = interior + 1;
                if (enclose[i].bottomy < -1)
                    enclose[i].bottomy = -(interior + 1);
                elif (enclose[i].bottomy > 1)
                    enclose[i].bottomy = interior;

                  leftx = worm[player].x + enclose[i].leftx;
                 rightx = worm[player].x + enclose[i].rightx;
                   topy = worm[player].y + enclose[i].topy;
                bottomy = worm[player].y + enclose[i].bottomy;
                // assert(leftx >= 0 && rightx <= FIELDX && topy >= 0 && bottomy <= FIELDY && leftx < rightx && topy < bottomy);

                for (x = leftx; x <= rightx; x++)
                {   for (y = topy; y <= bottomy; y++)
                    {   d = field[x][y];
                        if ((d >= FIRSTEMPTY && d <= LASTEMPTY) || (d >= FIRSTTAIL && d <= LASTTAIL))
                        {   if (worm[player].bias)
                            {   change(x, y, GOLD);
                                wormscore(player, ENCLOSUREPOINT + TURNTOGOLD);
                            } elif (player != d - FIRSTTAIL)
                            {   change(x, y, FIRSTTAIL + player);
                                wormscore(player, ENCLOSUREPOINT);
    }   }   }   }   }   }   }

    // move protectors

    for (i = 0; i <= PROTECTORS; i++)
    {   if (protector[player][i].alive)
        {   if (protector[player][i].visible)
            {   change(protector[player][i].x, protector[player][i].y, protector[player][i].last);
            } else protector[player][i].visible = TRUE;
            protector[player][i].last = EMPTY;
            if (i == NOSE)
            {   protector[player][i].relx = worm[player].deltax * NOSEDISTANCE;
                protector[player][i].rely = worm[player].deltay * NOSEDISTANCE;
                if (!worm[player].affixer)
                {   if (worm[player].position == -1)
                        worm[player].posidir = 1;
                    elif (worm[player].position == 1)
                        worm[player].posidir = -1;
                    worm[player].position += worm[player].posidir;
                    if (worm[player].deltax == 0)
                        protector[player][i].relx = worm[player].position;
                    elif (worm[player].deltay == 0)
                        protector[player][i].rely = worm[player].position;
                    elif (worm[player].position == -1)
                        protector[player][i].relx = worm[player].deltax * (NOSEDISTANCE - 1);
                    elif (worm[player].position == 1)
                        protector[player][i].rely = worm[player].deltay * (NOSEDISTANCE - 1);
            }   }
            elif (!worm[player].affixer)
            {   if (protector[player][i].relx == 1 && protector[player][i].rely == -1)
                {   protector[player][i].deltax = 0;
                    protector[player][i].deltay = 1;
                } elif (protector[player][i].relx == 1 && protector[player][i].rely == 1)
                {   protector[player][i].deltax = -1;
                    protector[player][i].deltay = 0;
                } elif (protector[player][i].relx == -1 && protector[player][i].rely == 1)
                {   protector[player][i].deltax = 0;
                    protector[player][i].deltay = -1;
                } elif (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 = worm[player].x + protector[player][i].relx;
            protector[player][i].y = worm[player].y + protector[player][i].rely;
            if (!valid(protector[player][i].x, protector[player][i].y))
            {   protector[player][i].visible = FALSE;
    }   }   }

    // head collision detection
    wormcol(player, worm[player].x, worm[player].y, enclosed);
    // draw head
    field[worm[player].x][worm[player].y] = FIRSTHEAD + player;
    drawhead(player, worm[player].x, worm[player].y);

    updatearrow(worm[player].arrowy);
    worm[player].arrowy = worm[player].y;
    updatearrow(worm[player].arrowy);

    // protector collision detection
    for (thisprot = 0; thisprot <= PROTECTORS; thisprot++)
    {   if (protector[player][thisprot].alive && protector[player][thisprot].visible)
        {   protcol(player, protector[player][thisprot].x, protector[player][thisprot].y, thisprot);
            // draw protector
            if (protector[player][thisprot].alive && protector[player][thisprot].visible) // in case protector has just been killed, etc.
            {   change(protector[player][thisprot].x, protector[player][thisprot].y, FIRSTPROTECTOR + player);
    }   }   }

    if (worm[player].cutter)
    {   // straight ahead
        x = xwrap(worm[player].x + worm[player].deltax);
        y = ywrap(worm[player].y + worm[player].deltay);
        score += squareblast(HEAD, player, field[x][y], x, y, TRUE);
        // left
        if (!worm[player].deltax || !worm[player].deltay)
        {   // if orthagonal
            x = xwrap(worm[player].x + worm[player].deltay);
            y = ywrap(worm[player].y - worm[player].deltax);
        } else // diagonal
        {   if (worm[player].deltax == worm[player].deltay)
            {   x = xwrap(worm[player].x + worm[player].deltax);
                y = ywrap(worm[player].y - worm[player].deltay);
            } else
            {   x = xwrap(worm[player].x - worm[player].deltax);
                y = ywrap(worm[player].y + worm[player].deltay);
        }   }
        score += squareblast(HEAD, player, field[x][y], x, y, TRUE);
        // right
        if (!worm[player].deltax || !worm[player].deltay)
        {   // if orthagonal
            x = xwrap(worm[player].x - worm[player].deltay);
            y = ywrap(worm[player].y + worm[player].deltax);
        } else // diagonal
        {   if (worm[player].deltax == worm[player].deltay)
            {   x = xwrap(worm[player].x - worm[player].deltax);
                y = ywrap(worm[player].y + worm[player].deltay);
            } else
            {   x = xwrap(worm[player].x + worm[player].deltax);
                y = ywrap(worm[player].y - worm[player].deltay);
        }   }
        score += squareblast(HEAD, player, field[x][y], x, y, TRUE);
        // ahead left
        if (!worm[player].deltax || !worm[player].deltay)
        {   // if orthagonal
            if (worm[player].deltax) // if east or west
            {   x = xwrap(worm[player].x + worm[player].deltax);
                y = ywrap(worm[player].y - worm[player].deltax);
            } else // north or south
            {   x = xwrap(worm[player].x + worm[player].deltay);
                y = ywrap(worm[player].y + worm[player].deltay);
        }   }
        else // diagonal
        {   if (worm[player].deltax == worm[player].deltay)
            {   x = xwrap(worm[player].x + worm[player].deltax);
                y = worm[player].y;
            } else
            {   x = worm[player].x;
                y = ywrap(worm[player].y + worm[player].deltay);
        }   }
        score += squareblast(HEAD, player, field[x][y], x, y, TRUE);
        // ahead right
        if (!worm[player].deltax || !worm[player].deltay)
        {   // if orthagonal
            if (worm[player].deltax) // if east or west
            {   x = xwrap(worm[player].x + worm[player].deltax);;
                y = ywrap(worm[player].y + worm[player].deltax);
            } else // north or south
            {   x = xwrap(worm[player].x - worm[player].deltay);
                y = ywrap(worm[player].y + worm[player].deltay);
        }   }
        else // diagonal
        {   if (worm[player].deltax == worm[player].deltay)
            {   x = worm[player].x;
                y = ywrap(worm[player].y + worm[player].deltay);
            } else
            {   x = xwrap(worm[player].x + worm[player].deltax);
                y = worm[player].y;
        }   }
        score += squareblast(HEAD, player, field[x][y], x, y, TRUE);
        wormscore(player, score);
}   }

MODULE void protcol(SBYTE player, SBYTE x, SBYTE y, SBYTE thisprot)
{   UBYTE c = field[x][y];
    SBYTE i;
    ULONG score = 0;

    if (c >= FIRSTHEAD && c <= LASTHEAD)
        protworm(x, y, player, c - FIRSTHEAD);
    elif (c >= FIRSTTAIL && c <= LASTTAIL)
    {   if (player == c - FIRSTTAIL || worm[player].mode == TONGUE)
            protector[player][thisprot].visible = FALSE;
    } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR)
        protprot(x, y, player, c - FIRSTPROTECTOR);
    elif (c == STONE || c == WOOD || c == METAL || c == TIMEBOMB || c == TELEPORT)
        protector[player][thisprot].visible = FALSE;
    elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
    {   i = whichcreature(x, y, MISSILE, 255);
        protmissile(x, y, player, i);
    } elif (c == WHIRLWIND)
    {   i = whichcreature(x, y, WHIRLWIND, 255);
        protwhirlwind(x, y, player, i);
    } elif (c == DOG)
    {   i = whichcreature(x, y, DOG, 255);
        protdog(x, y, player, i);
    } elif (c >= FIRSTDRIP && c <= LASTDRIP)
    {   i = whichcreature(x, y, DRIP, 255);
        protdrip(x, y, player, i);
    } elif (c == PENGUIN)
    {   i = whichcreature(x, y, PENGUIN, 255);
        protpenguin(x, y, player, i);
    } elif (c == ORB)
    {   i = whichcreature(x, y, ORB, 255);
        protorb(x, y, player, i);
    } elif (c == FRAGMENT)
    {   i = whichcreature(x, y, FRAGMENT, 255);
        protfrag(x, y, player, i);
    } elif (c == CLOUD)
    {   i = whichcreature(x, y, CLOUD, 255);
        cloudprot(x, y, i, player);
    } elif (c == FIRSTFIRE + player)
    {   protector[player][thisprot].visible = FALSE;
    } elif (c == OCTOPUS)
    {   effect(FXUSE_PROTECTOR);
        creature[whichcreature(x, y, OCTOPUS, 255)].alive = FALSE;
        protector[player][thisprot].last = BONUS;
        score += KILLOCTOPUS;
        if (worm[player].bias)
        {   worm[player].lives += OCTOPUSBLOOD;
            stat(player, LIFE);
    }   }
    elif (c == GOAT)
    {   effect(FXUSE_PROTECTOR);
        effect(FXDEATH_GOAT);
        creature[whichcreature(x, y, GOAT, 255)].alive = FALSE;
        protector[player][thisprot].last = BONUS;
        score += KILLGOAT;
        if (worm[player].bias)
        {   worm[player].lives += GOATBLOOD;
            stat(player, LIFE);
    }   }
    else bothcol(player, x, y);
    wormscore(player, score);
}

MODULE void bothcol(SBYTE player, SBYTE x, SBYTE y)
{   UBYTE c = field[x][y];
    SBYTE i, j;
    ULONG score = 0;

    if (c >= FIRSTLETTER && c <= LASTLETTER)
    {   wormletter(player, c);
        putletter(player);
    } elif (c <= LASTOBJECT)
    {   score += wormobject(player, x, y);
    } else
    {   switch(c)
        {
        case EMPTY:
            score += EMPTYPOINT;
        break;
        case SILVER:
            score += SILVERPOINT;
        break;
        case GOLD:
            score += GOLDPOINT;
        break;
        case DYNAMITE:
            effect(FXUSE_BOMB);
            banging = TRUE;
            bangdynamite(x, y, player);
            score += worm[player].dynamitescore;
            worm[player].dynamitescore = 0;
        break;
        case SKULL:
            effect(FXGET_SKULL);
            score += SKULLPOINT;
            j = -1; // for safety
            for (i = 0; i <= 3; i++)
            {   if (!worm[i].lives && x == worm[i].x && y == worm[i].y && worm[i].control != NONE)
                    j = i;
            }
            if (j != -1)
            {   worm[player].bias += worm[j].bias;
                if (worm[player].bias > 0)
                {   if (worm[player].armour > BIASLIMIT)
                        worm[player].armour = BIASLIMIT;
                    stat(player, BIAS);
                    worm[j].bias = 0;
                    stat(j, BIAS);
                }
                worm[player].multi *= worm[j].multi;
                if (worm[player].multi > 1)
                {   if (worm[player].multi > MULTILIMIT)
                        worm[player].multi = MULTILIMIT;
                }
                worm[player].power += worm[j].power;
                if (worm[player].power > 1)
                {   if (worm[player].power > POWERLIMIT)
                        worm[player].power = POWERLIMIT;
                    stat(player, POWER);
                    worm[j].power = 0;
                    stat(j, POWER);
                }
                worm[player].ammo += worm[j].ammo;
                if (worm[player].ammo > 0)
                {   if (worm[player].ammo > AMMOLIMIT)
                        worm[player].ammo = AMMOLIMIT;
                    stat(player, AMMO);
                    worm[j].ammo = 0;
                    stat(j, AMMO);
                }
                worm[player].armour += worm[j].armour;
                if (worm[player].armour > 0)
                {   if (worm[player].armour > MODELIMIT)
                        worm[player].armour = MODELIMIT;
                    stat(player, ARMOUR);
                    worm[j].armour = 0;
                    stat(j, ARMOUR);
                }
                worm[player].tongue += worm[j].tongue;
                if (worm[player].tongue > 0)
                {   if (worm[player].armour > MODELIMIT)
                        worm[player].armour = MODELIMIT;
                    stat(player, TONGUE);
                    worm[j].tongue = 0;
                    stat(j, TONGUE);
                }
                if (worm[player].armour > 0 || worm[player].tongue > 0)
                {   if (worm[player].armour >= worm[player].tongue)
                        worm[player].mode = ARMOUR;
                    else worm[player].mode = TONGUE;
                }
                if (worm[j].nitro)
                {   worm[player].nitro = TRUE;
                    stat(player, NITRO);
                    worm[j].nitro = FALSE;
                    worm[j].speed = NORMAL;
                    stat(j, NITRO);
                }
                if (worm[j].affixer)
                {   worm[player].affixer = TRUE;
                    icon(player, AFFIXER);
                    worm[j].affixer = FALSE;
                    icon(j, AFFIXER);
                }
                if (worm[j].remnants)
                {   worm[player].remnants = TRUE;
                    icon(player, REMNANTS);
                    worm[j].remnants = FALSE;
                    icon(j, REMNANTS);
                }
                if (worm[j].sideshot)
                {   worm[player].sideshot = TRUE;
                    icon(player, SIDESHOT);
                    worm[j].sideshot = FALSE;
                    icon(j, SIDESHOT);
                }
                if (worm[j].pusher)
                {   worm[player].pusher = TRUE;
                    icon(player, PUSHER);
                    worm[j].pusher = FALSE;
                    icon(j, PUSHER);
                }
                if (worm[j].cutter)
                {   worm[player].cutter += worm[j].cutter;
                    icon(player, CUTTER);
                    worm[j].cutter = 0;
                    icon(j, CUTTER);
                }
                for (i = 0; i <= LETTERS; i++)
                    if (letters[j][i])
                    {   drawletter(j, FIRSTLETTER + i, BLACK);
                        if (!letters[player][i])
                        {   letters[player][i] = TRUE;
                            drawletter(player, FIRSTLETTER + i, NORMAL);
                    }   }
            } else ; // assert(0);
        break;
        default:
        break;
    }   } 
    wormscore(player, score);
}

AGLOBAL void wormscore(SBYTE player, LONG score)
{   worm[player].score += score * worm[player].multi * players;
    stat(player, BONUS);
}

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

MODULE void ramming(SBYTE player)
{   SBYTE i;

	worm[player].rammed = TRUE;
    worm[player].x = xwrap(worm[player].x - worm[player].deltax);
    worm[player].y = ywrap(worm[player].y - worm[player].deltay);
    for (i = 0; i <= PROTECTORS; i++)
    {   /* no point checking whether the protectors are alive or dead */
        protector[player][i].x -= worm[player].deltax;
        protector[player][i].y -= worm[player].deltay;
}   }

MODULE SWORD atleast(SWORD value, SWORD minimum)
{	if (value < minimum)
		return(minimum);
	else return(value);
}

MODULE void orbscore(SBYTE which, ULONG score)
{   creature[which].score += score * creature[which].multi;
}

MODULE void __inline change(SBYTE x, SBYTE y, UBYTE image)
{   // assert(valid(x, y));
	field[x][y] = image;
    draw(x, y, image);
}

MODULE void createmissile(UBYTE player, SBYTE x, SBYTE y)
{   UBYTE i;

    for (i = 0; i <= CREATURES; i++)
        if (!creature[i].alive)
        {   effect(FXBORN_MISSILE);
            creature[i].alive      = TRUE;
            creature[i].x          = x;
            creature[i].y          = y;
            creature[i].species    = MISSILE;
            creature[i].type       = player;
            creature[i].last       = EMPTY;
            creature[i].visible    = FALSE;
            if (level)
                creature[i].speed  = (SBYTE) atleast(MISSILESPEED - (level / 2), 1);
            else creature[i].speed = BONUSMISSILESPEED;
            break;
}       }

/* WormWars FSET format for fieldset contents and high score table (Amiga
and IBM-PC), as follows:

header
        TEXT[]                          "FSET x.x" (NULL-terminated)
	SBYTE				levels;
high score table
	for (slot = 0; slot <= HISCORES; slot++)
	{	SBYTE			hiscore[slot].player,
					hiscore[slot].level;
		SLONG			hiscore[slot].score;
		TEXT[]			hiscore[slot].name (NULL-terminated)
		TEXT[]			hiscore[slot].time (NULL-terminated)
		TEXT[]			hiscore[slot].date (NULL-terminated)
	}
level data
	for (level = 0; level <= levels; level++)
	{	SBYTE			startx[level],
                                        starty[level];
		ABOOL			teleport[level][0].alive;
		SBYTE			teleport[level][0].x,
                                        teleport[level][0].y;
		ABOOL			teleport[level][1].alive;
		SBYTE			teleport[level][1].x,
                                        teleport[level][1].y;
		for (x = 0; x <= FIELDX; x++)
			for (y = 0; y <= FIELDY; y++)
				SBYTE	board[level][x][y];
	}
version string
        TEXT[]                          "$VER: Worm Wars x.x (dd.mm.yy) $" (NULL-terminated) */

MODULE SBYTE onlyworm(ABOOL alive)
{   SBYTE i,
          theworm = -1, // to prevent spurious warnings
          worms   = 0;

    for (i = 0; i <= 3; i++)
        if (worm[i].control != NONE && ((!alive) || worm[i].lives))
        {   theworm = i;
            worms++;
        }
    if (worms == 1)
        return (SBYTE) theworm;
    else return -1;
}

/* cloud, prot, worm, dog, drip, frag, missile, orb, penguin, whirlwind

Whichever is earlier in that list comes earlier in the function name. For
example, frag-orb and orb-frag collisions both use fragorb(): there is no
such routine as orbfrag(). Also, there are no such routines as dripdrip()
and penguinpenguin(). */

MODULE void dogdog(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, LIFE);
}
MODULE void dogdrip(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void dogfrag(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, BONUS);
}
MODULE void dogmissile(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, BONUS);
}
MODULE void dogorb(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void dogpenguin(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void dogwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void dripfrag(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, BONUS);
}
MODULE void dripmissile(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   /* drip lives, missile dies */
    creature[which2].alive = FALSE;
}
MODULE void driporb(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    if (creature[which2].mode != ARMOUR)
    {   creature[which2].explode = TRUE;
        draw(x, y, EMPTY);
}   }
MODULE void drippenguin(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void dripwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void fragfrag(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   effect(FXDEATH_FRAGMENT);
    creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    draw(x, y, EMPTY);
}
MODULE void fragmissile(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, EMPTY);
}
MODULE void fragorb(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    if (creature[which2].mode != ARMOUR)
    {   creature[which2].explode = TRUE;
        draw(x, y, EMPTY);
    } else effect(FXUSE_ARMOUR);
}
MODULE void fragpenguin(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void fragwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void missilemissile(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, BONUS);
}
MODULE void missilepenguin(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void missilewhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void orborb(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   effect(FXDEATH_ORB);
    creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, BONUS);
}
MODULE void orbmissile(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   if (creature[which1].mode != ARMOUR)
    {   effect(FXDEATH_ORB);
        creature[which1].alive = FALSE;
        creature[which2].alive = FALSE;
        change(x, y, BONUS);
        wormscore(creature[which2].type, creature[which1].score);
        if (worm[creature[which2].type].bias)
        {   worm[creature[which2].type].lives += ORBBLOOD;
            stat(creature[which2].type, LIFE);
    }   }
    else effect(FXUSE_ARMOUR);
}
MODULE void orbpenguin(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
    if (creature[which1].mode == ARMOUR)
        orbscore(which1, KILLPENGUIN);
    else
    {   creature[which1].alive = FALSE;
        change(x, y, BONUS);
}   }
MODULE void orbwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void penguinwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void whirlwindwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, LIFE);
}
MODULE void wormdog(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   if (creature[which2].dormant == DORMANT)
    {   effect(FXBORN_DOG);
        creature[which2].dormant = AWAKENING;
        creature[which2].type = which1;
        worm[which1].last = DOG;
    } else
    {   creature[which2].alive = FALSE;
        if (worm[which1].mode != ARMOUR)
        {   worm[which1].alive = FALSE;
            worm[which1].cause = DOG;
            worm[which1].victor = -1;
}   }   }
MODULE void wormdrip(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
    if (which1 == creature[which2].type)
    {   effect(FXGET_DRIP);
		wormscore(which1, DRIPBONUS);
		if (worm[which1].bias)
		{	worm[which1].lives += DRIPBLOOD;
			stat(which1, LIFE);
	}	}
    else
    {	worm[which1].alive = FALSE;
        worm[which1].cause = FIRSTDRIP + creature[which2].type;
        worm[which1].victor = -1;
}   }
MODULE void wormfrag(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   if (worm[which1].mode != ARMOUR)
    {	worm[which1].cause = FRAGMENT;
	worm[which1].victor = -1;
	worm[which1].alive = FALSE;
        creature[which2].alive = FALSE;
    } else
    {   effect(FXUSE_ARMOUR);
	reflect(which2);
}   }
MODULE void wormmissile(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   if (creature[which2].type == which1)
        creature[which2].visible = FALSE;
    else
    {   creature[which2].alive = FALSE;
	if (worm[which1].mode != ARMOUR)
	{   worm[which1].cause = FIRSTMISSILE + creature[which2].type;
      	    worm[which1].victor = creature[which2].type;
      	    worm[which1].alive = FALSE;
        } else effect(FXUSE_ARMOUR);
}   }
MODULE void wormorb(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   if (worm[which1].mode == ARMOUR)
    {   effect(FXUSE_ARMOUR);
        effect(FXDEATH_ORB);
        wormscore(which1, creature[which2].score);
        creature[which2].alive = FALSE;
        if (worm[which1].bias)
        {   worm[which1].lives += ORBBLOOD;
            stat(which1, LIFE);
    }   }
    else
    {   if (creature[which2].mode == ARMOUR)
        {   effect(FXUSE_ARMOUR);
            orbscore(which2, KILLWORM);
        } else creature[which2].alive = FALSE;
	worm[which1].cause = ORB;
	worm[which1].victor = -1;
	worm[which1].alive = FALSE;
}   }
MODULE void wormpenguin(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   effect(FXDEATH_PENGUIN);
    creature[which2].alive = FALSE;
    if (worm[which1].armour)
    {   wormscore(which1, KILLPENGUIN);
	if (worm[which1].bias)
	{   worm[which1].lives += PENGUINBLOOD;
	    stat(which1, LIFE);
    }   }
    else
    {   worm[which1].alive = FALSE;
        worm[which1].cause = PENGUIN;
        worm[which1].victor = -1;
}   }
MODULE void wormwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   worm[which1].cause = WHIRLWIND;
    worm[which1].victor = -1;
    worm[which1].alive = FALSE;
}
MODULE void wormworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   if (worm[which1].mode != TONGUE && worm[which2].mode != TONGUE)
    {   /* both worms die */
        worm[which1].cause  = FIRSTHEAD + which2;
        worm[which1].alive  = FALSE;
        worm[which1].victor = -1;
        worm[which2].cause  = FIRSTHEAD + which1;
        worm[which2].alive  = FALSE;
        worm[which2].victor = -1;
    } elif (worm[which1].mode == TONGUE && worm[which2].mode != TONGUE)
    {   /* 1st worm lives, 2nd worm dies  */
        worm[which2].cause  = FIRSTHEAD + which1;
        worm[which2].alive  = FALSE;
        worm[which2].victor = which1;
    } elif (worm[which1].mode != TONGUE && worm[which2].mode == TONGUE)
    {   /* 1st worm dies, 2nd worm lives */
        worm[which1].cause  = FIRSTHEAD + which2;
        worm[which1].alive  = FALSE;
        worm[which1].victor = which2;
}   }
MODULE void protdog(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void protdrip(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
    if (which1 == creature[which2].type)
    {   effect(FXGET_DRIP);
        wormscore(which1, DRIPBONUS);
		if (worm[which1].bias)
		{	worm[which1].lives += DRIPBLOOD;
			stat(which1, LIFE);
	}	}
    else effect(FXUSE_PROTECTOR);
}
MODULE void protfrag(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   effect(FXUSE_PROTECTOR);
    reflect(which2);
}
MODULE void protmissile(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   if (which1 != creature[which2].type)
    {   effect(FXUSE_PROTECTOR);
        creature[which2].alive = FALSE;
    } else creature[which2].visible = FALSE;
}
MODULE void protorb(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   effect(FXUSE_PROTECTOR);
    effect(FXDEATH_ORB);
    wormscore(which1, creature[which2].score);
    creature[which2].alive = FALSE;
    if (worm[which1].bias)
    {   worm[which1].lives += ORBBLOOD;
        stat(which1, LIFE);
}   }
MODULE void protpenguin(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   effect(FXUSE_PROTECTOR);
    effect(FXDEATH_PENGUIN);
    wormscore(which1, KILLPENGUIN);
    creature[which2].alive = FALSE;
    if (worm[which1].bias)
    {   worm[which1].lives += PENGUINBLOOD;
        stat(which1, LIFE);
}   }
MODULE void protwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   UBYTE i;

    for (i = 0; i <= PROTECTORS; i++)
         if (protector[which1][i].alive && protector[which1][i].x == x && protector[which1][i].y == y)
              protector[which1][i].alive = FALSE;    
}
MODULE void protworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   SBYTE i, j = -1; // to prevent spurious warnings

    for (i = 0; i <= PROTECTORS; i++)
         if (protector[which1][i].alive && protector[which1][i].x == x && protector[which1][i].y == y)
         {    j = i;
              break;
         }

    if (which1 != which2)
    {   if (worm[which2].mode != ARMOUR)
        {   effect(FXUSE_PROTECTOR);
            worm[which2].cause  = FIRSTPROTECTOR + which1;
            worm[which2].victor = which1;
            worm[which2].alive  = FALSE;
        } else
        {   effect(FXUSE_ARMOUR);
            protector[which1][j].visible = FALSE;
    }   }
    else
    {   /* protector is over worm's own head; caused by ramming */
        protector[which1][j].visible = FALSE;
}   }
MODULE void protprot(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   SBYTE i, p1 = -1, p2 = -1; // to prevent spurious warnings

    /* Find both protectors */

    for (i = 0; i <= PROTECTORS; i++)
    {   if (protector[which1][i].alive && protector[which1][i].x == x && protector[which1][i].y == y)
            p1 = i;
        if (protector[which2][i].alive && protector[which2][i].x == x && protector[which2][i].y == y)
            p2 = i;
    }
    protector[which1][p1].alive = FALSE;
    protector[which2][p2].alive = FALSE;
    change(x, y, EMPTY);
}
MODULE void cloudcloud(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    creature[which2].alive = FALSE;
    change(x, y, LIGHTNING);
}
MODULE void clouddog(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void clouddrip(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void cloudfrag(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void cloudmissile(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void cloudorb(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void cloudpenguin(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which2].alive = FALSE;
}
MODULE void cloudprot(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void cloudwhirlwind(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
}
MODULE void cloudworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   creature[which1].alive = FALSE;
    if (worm[which2].mode != ARMOUR)
    {   worm[which2].alive = FALSE;
        worm[which2].cause = CLOUD;
        worm[which2].victor = -1;
}   }

MODULE ULONG wormobject(UBYTE player, SBYTE x, SBYTE y)
{   AUTO    UBYTE c = field[x][y], d;
    AUTO    ULONG score = object[c].score;
    AUTO    UBYTE i, j;
    AUTO    SBYTE xx, xxx, yy, yyy;
    AUTO    ABOOL done;
    PERSIST UBYTE otherfield[FIELDX + 1][FIELDY + 1];

    for (i = 0; i <= MAGNETS; i++)
        if (magnet[i].alive && x == magnet[i].x && y == magnet[i].y)
             magnet[i].alive = FALSE;

    if (!valid(x, y)) // defensive programming
    {   return 0;

        /* AUTO TEXT temp1[SAYLIMIT + 1], temp2[8];

        strcpy(temp1, "BAD OBJECT AT x: ");
        stci_d(temp2, x);
        strcat(temp1, temp2);
        strcat(temp1, ", y: ");
        stci_d(temp2, y);
        strcat(temp1, temp2);
        strcat(temp1, "!");
        say(temp1, worm[player].colour);
        draw(FIELDX + 1, 0, c); // indicates which object
        Delay(250);
        clearkybd();
        anykey(FALSE); */
    }

    switch(c)
    {
    case BONUS:
        effect(FXGET_OBJECT);
        i = rand() % (LETTERS + 1);
        letters[player][i] = TRUE;
        drawletter(player, FIRSTLETTER + i, NORMAL);
    break;
    case AMMO:
        effect(FXGET_AMMO);
        worm[player].ammo += (rand() % 5) + 2; /* 2-6 bullets */
        stat(player, AMMO);
    break;
    case ARMOUR:
        effect(FXGET_OBJECT);
        worm[player].armour += MODEADD + (rand() % MODERAND);
        worm[player].mode = ARMOUR;
        stat(player, ARMOUR);
    break;
    case TONGUE:
        effect(FXGET_OBJECT);
        worm[player].tongue += MODEADD + (rand() % MODERAND);
        worm[player].mode = TONGUE;
        stat(player, TONGUE);
        worm[player].last = FIRSTTAIL + player;
        break;
    case NITRO:
        effect(FXGET_NITRO);
        worm[player].nitro = TRUE;
        stat(player, NITRO);
    break;
    case BOMB:
        if (worm[player].mode == NOMODE)
            draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
        else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
            bombblast(HEAD, player, worm[player].x, worm[player].y);
    break;
    case POWER:
        effect(FXGET_POWERUP);
        if (worm[player].power < POWERLIMIT)
        {   worm[player].power += 2;
            stat(player, POWER);
        }
    break;
    case SLAYER:
        for (i = 0; i <= CREATURES; i++)
            if (creature[i].alive)
            {   if (creature[i].species == ORB)
                {   effect(FXDEATH_ORB);
                    score += creature[i].score;
                    creature[i].explode = TRUE;
                    if (worm[player].bias)
                        worm[player].lives += ORBBLOOD;
                } elif (creature[i].species == GOAT)
                {   effect(FXDEATH_GOAT);
                    creature[i].alive = FALSE;
                    score += KILLGOAT;
                    if (worm[player].bias)
                        worm[player].lives += GOATBLOOD;
                    change(creature[i].x, creature[i].y, BONUS);
                } elif (creature[i].species == OCTOPUS)
                {   creature[i].alive = FALSE;
                    score += KILLOCTOPUS;
                    if (worm[player].bias)
                        worm[player].lives += OCTOPUSBLOOD;
                    change(creature[i].x, creature[i].y, BONUS);
                } elif (creature[i].species == PENGUIN)
                {   effect(FXDEATH_PENGUIN);
                    creature[i].alive = FALSE;
                    score += KILLPENGUIN;
                    if (worm[player].bias)
                        worm[player].lives += PENGUINBLOOD;
                    change(creature[i].x, creature[i].y, EMPTY);
                } elif (creature[i].species == DRIP || creature[i].species == CLOUD || (creature[i].species == MISSILE && creature[i].type != player))
                {   creature[i].alive = FALSE;
                    change(creature[i].x, creature[i].y, EMPTY);
            }   }
        for (i = 0; i <= 3; i++)
            if (player != i && worm[i].mode != ARMOUR)
            {   worm[i].alive = FALSE;
                worm[i].cause = SLAYER;
                worm[i].victor = player;
            }
        for (x = 0; x <= FIELDX; x++)
            for (y = 0; y <= FIELDY; y++)
                if (field[x][y] == SLIME)
                    change(x, y, EMPTY);
        if (worm[player].bias)
            stat(player, LIFE); // we do it here once for efficiency
    break;
    case PROTECTOR:
        done = FALSE;
        for (i = 0; i <= PROTECTORS; i++)
            if (!protector[player][i].alive && !done)
            {   do
                {   protector[player][i].relx = ((rand() % 2) * 2) - 1;
                    protector[player][i].rely = ((rand() % 2) * 2) - 1;
                    for (j = 0; j <= PROTECTORS; j++)
                        if (i == NOSE || !protector[player][j].alive || protector[player][j].x != xwrap(worm[player].x + protector[player][i].relx) || protector[player][j].y != ywrap(worm[player].y + protector[player][i].rely))
                        {   effect(FXBORN_PROTECTOR);
                            done = TRUE;
                            protector[player][i].alive = TRUE;
                            protector[player][i].visible = FALSE;
                            protector[player][i].last = EMPTY;
                            if (i == NOSE)
                                worm[player].position = -1;
                        }
                } while (!done);
            }
    break;
    case MISSILE:
        createmissile(player, worm[player].x, worm[player].y);
    break;
    case LIFE:
        effect(FXGET_OBJECT);
        worm[player].lives += (rand() % 5) + 2; /* 2-6 lives */
        stat(player, LIFE);
    break;
    case MULTIPLIER:
        effect(FXGET_OBJECT);
        if (worm[player].multi < MULTILIMIT)
            worm[player].multi *= 2;
    break;
    case BIAS:
        effect(FXGET_OBJECT);
        worm[player].bias += MODEADD + (rand() % MODERAND);
        stat(player, BIAS);
    break;
    case ICE:
        effect(FXGET_OBJECT);
        worm[player].ice += ICEADD + (rand() % ICERAND);
        ice = player;
        icon(player, ICE);
    break;
    case GROWER:
        effect(FXGET_GROWER);

        /* grow silver */
        for (x = 0; x <= FIELDX; x++)
            for (y = 0; y <= FIELDY; y++)
                if (field[x][y] == SILVER)
                    for (xx = x - 1; xx <= x + 1; xx++)
                        for (yy = y - 1; yy <= y + 1; yy++)
                            if (valid(xx, yy))
                                if (field[xx][yy] == EMPTY)
                                    field[xx][yy] = TEMPSILVER;
        /* grow gold */
        for (x = 0; x <= FIELDX; x++)
            for (y = 0; y <= FIELDY; y++)
                if (field[x][y] == GOLD)
                    for (xx = x - 1; xx <= x + 1; xx++)
                        for (yy = y - 1; yy <= y + 1; yy++)
                            if (valid(xx, yy))
                                if (field[xx][yy] == EMPTY || field[xx][yy] == TEMPSILVER)
                                    field[xx][yy] = TEMPGOLD;
        /* update field */
        for (x = 0; x <= FIELDX; x++)
            for (y = 0; y <= FIELDY; y++)
                switch (field[x][y])
                {
                case TEMPGOLD:
                    change(x, y, GOLD);
                break;
                case TEMPSILVER:
                    change(x, y, SILVER);
                break;
                default:
                break;
                }

        if (worm[player].bias)
        {   // grow dynamite
            for (xx = x - 1; xx <= x + 1; xx++)
                for (yy = y - 1; yy <= y + 1; yy++)
                    if (valid(xx, yy))
                        if (field[xx][yy] == EMPTY || field[xx][yy] == TEMPSILVER)
                            field[xx][yy] = TEMPTAIL;
            // update field
            for (x = 0; x <= FIELDX; x++)
            {   for (y = 0; y <= FIELDY; y++)
                {   if (field[x][y] == TEMPTAIL)
                    {   change(x, y, DYNAMITE);
        }   }   }   }
    break;
    case TREASURE:
        treasurer = player;
        if (level)
        {   secondsperlevel = 0;
            leveltype = rand() % 3;
            if (leveltype == 0)
            {   say("Bonus Level: Treasury!", worm[treasurer].colour);
                leveltype = TREASURE;
            } elif (leveltype == 1)
            {   say("Bonus Level: Drips!", worm[treasurer].colour);
                leveltype = DRIP;
            } else
            {   // assert(leveltype == 2);
                say("Bonus Level: Penguins!", worm[treasurer].colour);
                leveltype = PENGUIN;
        }   }
        secondsperlevel += TREASUREADD + (rand() % TREASURERAND);
        if (level && leveltype != TREASURE)
            secondsperlevel *= 2;
        if (secondsperlevel > TIMELIMIT)
            secondsperlevel = TIMELIMIT;
        if (level)
        {   stat(player, BONUS);
            reallevel = level;
            level = 0;
            newlevel(player);
        }
    break;
    case AFFIXER:
        effect(FXGET_OBJECT);
        worm[player].affixer = TRUE;
        icon(player, AFFIXER);
    break;
    case SWITCHER:
        effect(FXGET_OBJECT);
        for (x = 0; x <= FIELDX; x++)
        {   for (y = 0; y <= FIELDY; y++)
            {   if (players >= 2 && field[x][y] >= FIRSTTAIL && field[x][y] <= LASTTAIL && field[x][y] != FIRSTTAIL + player)
                {   change(x, y, FIRSTTAIL + player);
                } elif (worm[player].bias)
                {   if (field[x][y] >= FIRSTFIRE && field[x][y] <= LASTFIRE && field[x][y] != FIRSTFIRE + player)
                        change(x, y, FIRSTFIRE + player);
                    elif (field[x][y] == SLIME)
                        change(x, y, DYNAMITE);
        }   }   }
    break;
    case HEALER:
        effect(FXGET_OBJECT);
        if (worm[player].lives < 100)
            worm[player].lives = 100;
        else worm[player].lives = LIVESLIMIT;
        stat(player, LIFE);
    break;
    case UMBRELLA:
        level += (rand() % 2) + 1;
        if (level >= levels)
            level = levels; // fixed?
        for (i = 0; i <= LETTERS; i++)
            letters[player][i] = TRUE;
    break;
    case CLOCK:
        effect(FXGET_OBJECT);
        if (secondsleft)
        {   secondsperlevel += (rand() % CLOCKRAND) + CLOCKADD;
            if (secondsperlevel > TIMELIMIT)
                secondsperlevel = TIMELIMIT;
        }
    break;
    case SLOWER:
        effect(FXGET_OBJECT);
        for (i = 0; i <= CREATURES; i++)
            if (creature[i].alive && creature[i].species != MISSILE)
                creature[i].speed = (SBYTE) atleast(creature[i].speed * 2, VERYFAST);
    break;
    case PULSE:
        explosion(xwrap(worm[player].x - worm[player].deltax),
                  ywrap(worm[player].y - worm[player].deltay),
                                         worm[player].deltax,
                                         worm[player].deltay,
                 PULSE);
    break;
    case REMNANTS:
        effect(FXGET_OBJECT);
        worm[player].remnants = TRUE;
        icon(player, REMNANTS);
    break;
    case SIDESHOT:
        effect(FXGET_POWERUP);
        worm[player].sideshot = TRUE;
        icon(player, SIDESHOT);
    break;
    case MAGNET:
        effect(FXGET_OBJECT);
        i = 0;
        field[x][y] = EMPTY; // so that the magnet itself is destroyed
        for (xx = 0; xx <= FIELDX; xx++)
            for (yy = 0; yy <= FIELDY; yy++)
                if (field[xx][yy] <= LASTOBJECT)
                {   while (magnet[i].alive && i < MAGNETS)
                       i++;
                    if (i > MAGNETS)
                    {   break;
                    } else
                    {   magnet[i].x      = xx;
                        magnet[i].y      = yy;
                        magnet[i].object = field[xx][yy];
                        magnet[i].player = player;
                        magnet[i].alive  = TRUE;
                        i++;
                }   }
    break;
    case CUTTER:
        effect(FXGET_OBJECT);
        worm[player].cutter += (rand() % CUTTERRAND) + CUTTERADD;
         icon(player, CUTTER);
    break;
    case CYCLONE:
        /* create cyclones */
        for (i = 0; i <= CREATURES; i++)
            if (!(creature[i].alive))
            {   effect(FXGET_CYCLONE);
                done = FALSE;
                while (!done)
                {   creature[i].x = rand() % (FIELDX + 1);
                    creature[i].y = (rand() % (FIELDY - 2)) + 3;
                    d = field[creature[i].x][creature[i].y];
                    if ((d >= FIRSTEMPTY && d <= LASTEMPTY) || (d >= FIRSTTAIL && d <= LASTTAIL))
                        done = TRUE;
                    /* Theoretically, this loop could hang... */
                }
                creature[i].last = EMPTY;
                creature[i].speed = VERYFAST;
                creature[i].visible = TRUE;
                creature[i].deltax = 0;
                creature[i].deltay = 0;
                creature[i].species = WHIRLWIND;
                creature[i].alive = TRUE;
                change(creature[i].x, creature[i].y, WHIRLWIND);
                break;
            }
    break;
    case LIGHTNING:
        effect(FXGET_OBJECT);
        for (xx = 0; xx <= FIELDX; xx++)
            for (yy = 0; yy <= FIELDY; yy++)
                otherfield[xx][yy] = EMPTY;
        for (xx = 0; xx <= FIELDX; xx++)
            for (yy = 0; yy <= FIELDY; yy++)
                if (field[xx][yy] == FIRSTTAIL + player)
                    for (xxx = xx - 1; xxx <= xx + 1; xxx++)
                        for (yyy = yy - 1; yyy <= yy + 1; yyy++)
                            if (valid(xxx, yyy))
                            {   d = field[xxx][yyy];
                                if (d == ORB
                                 || d == GOAT
                                 || d == MISSILE
                                 || d == PENGUIN
                                 || d == FRAGMENT
                                 || (d >= FIRSTTAIL && d <= LASTTAIL && d != FIRSTTAIL + player)
                                 || (d >= FIRSTHEAD && d <= LASTHEAD)
                                 || (d >= FIRSTDRIP && d <= LASTDRIP)
                                 || d <= LASTOBJECT)
                                {   otherfield[xxx][yyy] = TEMPLIGHTNING;
                                    draw(xxx, yyy, LIGHTNING);
                            }   }
        for (xx = 0; xx <= FIELDX; xx++)
        {   for (yy = 0; yy <= FIELDY; yy++)
            {   if (otherfield[xx][yy] == TEMPLIGHTNING)
                {   d = field[xx][yy];
                    switch(d)
                    {
                    case ORB:
                        i = whichcreature(xx, yy, ORB, 255);
                        if (creature[i].mode == ARMOUR)
                            draw(xx, yy, ORBARMOUR);
                        else
                        {   creature[i].alive = FALSE;
                            score += creature[i].score;
                            change(xx, yy, BONUS);
                        }
                    break;
                    case GOAT:
                        creature[whichcreature(xx, yy, GOAT, 255)].alive = FALSE;
                        score += KILLGOAT;
                        change(xx, yy, BONUS);
                    break;
                    case MISSILE:
                        i = whichcreature(xx, yy, MISSILE, 255);
                        if (player != creature[i].type)
                        {   creature[i].alive = FALSE;
                            change(xx, yy, EMPTY);
                        } else draw(xx, yy, FIRSTMISSILE + player);
                    break;
                    case PENGUIN:
                        score += KILLPENGUIN;
                    // note no break here
                    case FRAGMENT:
                        creature[whichcreature(xx, yy, d, 255)].alive = FALSE;
                        change(xx, yy, EMPTY);
                    break;
                    default:
                        if (d >= FIRSTDRIP && d <= LASTDRIP)
                        {   creature[whichcreature(xx, yy, DRIP, 255)].alive = FALSE;
                            score += DRIPBONUS;
							if (player == d - FIRSTDRIP && worm[player].bias)
							{	worm[player].lives += DRIPBLOOD;
								stat(player, LIFE);
                        }	}
						elif (d >= FIRSTHEAD && d <= LASTHEAD)
                        {   if (player != d - FIRSTHEAD && worm[d - FIRSTHEAD].mode != ARMOUR)
                            {   worm[d - FIRSTHEAD].alive = FALSE;
                                worm[d - FIRSTHEAD].cause = LIGHTNING;
                                worm[d - FIRSTHEAD].victor = player;
                                change(xx, yy, EMPTY);
                        }   }
                        else /* eg. tail */
                            change(xx, yy, EMPTY);
                    break;
        }   }   }   }
    break;
    case PUSHER:
        effect(FXGET_OBJECT);
        worm[player].pusher = TRUE;
        icon(player, PUSHER);
    break;
    case FREEDOM:
        effect(FXGET_OBJECT);
        worm[player].freedom += FREEDOMADD + (rand() % FREEDOMRAND);
        if (worm[player].freedom > FREEDOMLIMIT)
            worm[player].freedom = FREEDOMLIMIT;
		icon(player, FREEDOM);
    break;
    case CONVERTER:
        effect(FXGET_OBJECT);
        for (i = 0; i <= CREATURES; i++)
            if (creature[i].alive && creature[i].species == FRAGMENT)
            {   xx = creature[i].x;
                yy = creature[i].y;
                creature[i].alive = FALSE;
                change(xx, yy, EMPTY);
                createmissile(player, xx, yy);
            }
	break;
    default:
        // assert(0);
    break;
    }
    return(score);
}

void icon(SBYTE player, UBYTE image)
{   /* Updates one of the boolean icons. The routine checks
    the status directly. */

    AUTO    SBYTE x, y;

    if (!worm[player].statx)
        x = -7;
    else x = FIELDX + 2;
    if (!worm[player].staty)
        y = (FIELDY / 2) - 3;
    else y = (FIELDY / 2) + 3;

    switch(image)
    {
    case AFFIXER:
        if (worm[player].affixer)
            draw(x, y, AFFIXER);
        else draw(x, y, BLACKENED);
    break;
    case PUSHER:
        if (worm[player].pusher)
            draw(x + 1, y, PUSHER);
        else draw(x + 1, y, BLACKENED);
    break;
    case REMNANTS:
        if (worm[player].remnants)
            draw(x + 2, y, REMNANTS);
        else draw(x + 2, y, BLACKENED);
    break;
    case SIDESHOT:
        if (worm[player].sideshot)
            draw(x + 3, y, SIDESHOT);
        else draw(x + 3, y, BLACKENED);
    break;
    case ICE:
		if (worm[player].ice)
            if (worm[player].ice < 3)
			{	if ((r % 4) <= 1)
					draw(x + 4, y, ICE);
				else draw(x + 4, y, BLACKENED);
			}
			else draw(x + 4, y, ICE);
        else draw(x + 4, y, BLACKENED);
    break;
    case CUTTER:
		if (worm[player].cutter)
            if (worm[player].cutter < 10)
			{	if ((r % 4) <= 1)
					draw(x + 5, y, CUTTER);
				else draw(x + 5, y, BLACKENED);
			}
			else draw(x + 5, y, CUTTER);
        else draw(x + 5, y, BLACKENED);
    break;
	case FREEDOM:
		if (worm[player].freedom)
            if (worm[player].freedom < 10)
			{	if ((r % 4) <= 1)
					draw(x + 6, y, FREEDOM);
				else draw(x + 6, y, BLACKENED);
			}
			else draw(x + 6, y, FREEDOM);
        else draw(x + 6, y, BLACKENED);
    break;
    default:
    break;
}   }

MODULE void wormcol(SBYTE player, SBYTE x, SBYTE y, ABOOL enclosed)
{   ABOOL flag;
    UBYTE c = field[x][y], d;
    SBYTE i, xx, yy;
    ULONG score = 0;

    if (c >= FIRSTHEAD && c <= LASTHEAD)
        wormworm(x, y, player, c - FIRSTHEAD);
    elif (c == TIMEBOMB)
    {   /* push timebomb */
        i = whichcreature(x, y, TIMEBOMB, 255);
        if (valid(x + worm[player].deltax, y + worm[player].deltay))
        {   d = field[x + worm[player].deltax][y + worm[player].deltay];
            if (d <= LASTEMPTY)
            {   creature[i].x += worm[player].deltax;
                creature[i].y += worm[player].deltay;
                field[creature[i].x][creature[i].y] = TIMEBOMB;
                draw(creature[i].x, creature[i].y, ZERO + creature[i].time);
            } else
            {   if (worm[player].mode == NOMODE)
                    draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
                else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
                bombblast(HEAD, player, worm[player].x, worm[player].y);
                creature[i].alive = FALSE;
        }   }
        else score += BOMBOVEREDGE;
    } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR)
        protworm(x, y, c - FIRSTPROTECTOR, player);
    elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
    {   i = whichcreature(x, y, MISSILE, 255);
        wormmissile(x, y, player, i);
    } elif (c >= FIRSTDRIP && c <= LASTDRIP)
    {   i = whichcreature(x, y, DRIP, 255);
        wormdrip(x, y, player, i);
    } elif (c == STONE || c == GOAT || c == OCTOPUS || c == METAL || (c >= FIRSTTAIL && c <= LASTTAIL))
    {   flag = TRUE;
        if (worm[player].pusher)
        {   xx = x + worm[player].deltax;
            yy = y + worm[player].deltay;
            if (valid(xx, yy))
            {   d = field[xx][yy];
                if (d <= LASTEMPTY)
                {   flag = FALSE;
                    if (c == GOAT || c == OCTOPUS)
                    {   i = whichcreature(x, y, c, 255);
                        creature[i].x = xx;
                        creature[i].y = yy;
                        creature[i].visible = FALSE;
                    }
                    field[xx][yy] = c;
                    draw(xx, yy, c);
            }   }                 
            else
            {   flag = FALSE;
                score += BOMBOVEREDGE;
        }   }
        if (flag)
        {   if (c >= FIRSTTAIL && c <= LASTTAIL)
            {   if (worm[player].mode == TONGUE)
                {   effect(FXUSE_TONGUE);
                    if (players > 1)
                        if (player == c - FIRSTTAIL)
                        {   score += TURNTOSILVER;
                            worm[player].last = SILVER;
                        } else
                        {   score += TURNTOGOLD;
                            worm[player].last = GOLD;
                }       }
                elif (!enclosed)
                {   worm[player].cause = c;
                    worm[player].alive = FALSE;
                    worm[player].victor = c - FIRSTTAIL;
            }   }
            else
            {   worm[player].cause = c;
                worm[player].victor = -1;
                worm[player].alive = FALSE;
                ramming(player);
    }   }   }
    elif (c == WOOD)
    {   if (worm[player].mode != ARMOUR)
        {   worm[player].cause = WOOD;
            worm[player].alive = FALSE;
            worm[player].victor = -1;
    }   }
    elif (c == SLIME)
    {   if (worm[player].mode != ARMOUR)
        {   worm[player].cause = SLIME;
            worm[player].alive = FALSE;
            worm[player].victor = -1;
    }   }
    elif (c == PENGUIN)
    {   i = whichcreature(x, y, PENGUIN, 255);
        wormpenguin(x, y, player, i);
    } elif (c == WHIRLWIND)
    {   i = whichcreature(x, y, WHIRLWIND, 255);
        wormwhirlwind(x, y, player, i);
    } elif (c == DOG)
    {   i = whichcreature(x, y, DOG, 255);
        wormdog(x, y, player, i);
    } elif (c == ORB)
    {   i = whichcreature(x, y, ORB, 255);
        wormorb(x, y, player, i);
    } elif (c == FRAGMENT)
    {   i = whichcreature(x, y, FRAGMENT, 255);
        wormfrag(x, y, player, i);
    } elif (c == CLOUD)
    {   i = whichcreature(x, y, CLOUD, 255);
        cloudworm(x, y, i, player);
    } elif (c == TELEPORT)
    {   i = whichteleport(x, y);
        if (blocked(i, worm[player].deltax, worm[player].deltay))
        {   worm[player].cause = TELEPORT;
            worm[player].victor = -1;
            worm[player].alive = FALSE;
            ramming(player);
        } else
        {   effect(FXUSE_TELEPORT);
            score += TELPOINT;
            worm[player].x = xwrap(teleport[level][partner(i)].x + worm[player].deltax);
            worm[player].y = ywrap(teleport[level][partner(i)].y + worm[player].deltay);
    }   }
    elif (c >= FIRSTFIRE && c <= LASTFIRE)
    {   if (player != c - FIRSTFIRE && worm[player].mode != ARMOUR)
        {   worm[player].cause = REMNANTS;
            worm[player].victor = c - FIRSTFIRE;
            worm[player].alive = FALSE;
    }   }
    else bothcol(player, x, y);
    wormscore(player, score);
}

AGLOBAL void drawhead(SBYTE player, SBYTE x, SBYTE y)
{   if (worm[player].alive)
    {   if (worm[player].mode == NOMODE)
        {   draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
            worm[player].flashed = FALSE;
        } else
        {   // assert(worm[player].mode == TONGUE || worm[player].mode == ARMOUR);
            if ((worm[player].mode == TONGUE && worm[player].tongue < 10)
             || (worm[player].mode == ARMOUR && worm[player].armour < 10))
            {   if (!worm[player].flashed)
                    draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
                else draw(worm[player].x, worm[player].y, WHITENED);
                worm[player].flashed = !worm[player].flashed;
            } else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
    }   }
    else draw(worm[player].x, worm[player].y, SKULL);
}

AGLOBAL void drawsquare(SBYTE x, SBYTE y)
{	UBYTE which;

	if (field[x][y] == DOG)
	{	which = whichcreature(x, y, DOG, 255);
		if (!creature[which].dormant)
			draw(x, y, DOGDORMANT);
		elif (creature[which].dormant <= CHASING)
			draw(x, y, DOGAWAKENING);
		else draw(x, y, DOG);
	} elif (field[x][y] >= FIRSTHEAD && field[x][y] <= LASTHEAD)
		drawhead(field[x][y] - FIRSTHEAD, x, y);
	else draw(x, y, field[x][y]);
}

MODULE void updatearrow(SBYTE arrowy)
{   SBYTE i, var = -1;

    /* var of:
                  -2         : many there
                  -1         : nothing there
                 0-3         : just that worm,
       FIRSTLETTER-LASTLETTER: just that letter */

    for (i = 0; i <= 3; i++)
        if (worm[i].control != NONE && worm[i].y == arrowy && (worm[i].lives || field[worm[i].x][worm[i].y] == SKULL))
            if (var == -1)
                var = i;
            else var = -2;
    if (lettery == arrowy)
        if (var == -1)
            var = lettertype;
        else var = -2;
    if (var == -2)
        draw(ARROWX, arrowy, ALL);
    elif (var == -1)
        draw(ARROWX, arrowy, BLACKENED);
    elif (var >= FIRSTLETTER && var <= LASTLETTER)
        draw(ARROWX, arrowy, var);
    else
    {   // assert(var >= 0 && var <= 3);
        if (worm[var].lives)
            draw(ARROWX, arrowy, FIRSTARROW + var);
        else draw(ARROWX, arrowy, SKULL);
}   }

MODULE void __inline bangdynamite(SBYTE x, SBYTE y, SBYTE player)
{   SBYTE xx, yy;

    // Infects (turns to bang-dynamite) all surrounding dynamite.

    for (xx = x - 1; xx <= x + 1; xx++)
        for (yy = y - 1; yy <= y + 1; yy++)
            if (valid(xx, yy))
                if (field[xx][yy] == DYNAMITE)
                {   field[xx][yy] = TEMPBANGDYNAMITE;
                    worm[player].dynamitescore += DYNAMITEPOINT;
                    infector[xx][yy] = player;
                }
}

MODULE void octopusfire(UBYTE which)
{   UBYTE i;
    SBYTE x, y;

    for (i = 0; i <= CREATURES; i++)
    {   if (!creature[i].alive)
        {   switch(creature[which].dir)
            {
            case 0:
                creature[i].deltax = 0;
                creature[i].deltay = -1;
            break;
            case 1:
                creature[i].deltax = 1;
                creature[i].deltay = -1;
            break;
            case 2:
                creature[i].deltax = 1;
                creature[i].deltay = 0;
            break;
            case 3:
                creature[i].deltax = 1;
                creature[i].deltay = 1;
            break;
            case 4:
                creature[i].deltax = 0;
                creature[i].deltay = 1;
            break;
            case 5:
                creature[i].deltax = -1;
                creature[i].deltay = 1;
            break;
            case 6:
                creature[i].deltax = -1;
                creature[i].deltay = 0;
            break;
            case 7:
                creature[i].deltax = -1;
                creature[i].deltay = -1;
            break;
            default:
            break;
            }
            x = creature[which].x + creature[i].deltax;
            y = creature[which].y + creature[i].deltay;
            if (valid(x, y) && field[x][y] >= FIRSTEMPTY && field[x][y] <= LASTEMPTY)
            {   effect(FXGET_PULSE);
                creature[i].last = EMPTY;
                creature[i].visible = TRUE;
                creature[i].x = x;
                creature[i].y = y;
                creature[i].speed = octopusspeed;
                creature[i].species = FRAGMENT;
                creature[i].alive = TRUE;
                change(x, y, FRAGMENT);
                goto out;
    }   }   }

out:
    if (++creature[which].dir == 8)
    {   creature[which].dir = -1;
}   }

