/* $Filename: WormWars/Source/engine.c
 * $VER:      WormWars 7.0
 *
 * © Copyright 1993-2002 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"

#define OTTER_UP    0
#define OTTER_DOWN  1
#define OTTER_LEFT  2
#define OTTER_RIGHT 3

#define ARROWX               (FIELDX + 1)
#define BONUSSPEEDUP         8 // 8 times more common on bonus levels
#define NOSE                 2 // if (NOSE > PROTECTORS) nose disabled
#define WORMQUEUELIMIT      15
#define DOGQUEUELIMIT      120
#define TIMELIMIT          599
#define SECONDSPERLEVEL    120 // assert (SECONDSPERLEVEL <= TIMELIMIT);
#define WEIGHT               5

// dog dormancy
#define DORMANT              0
#define AWAKENING            1
#define CHASING             10

// population limits
#define CREATURES           50
#define MAGNETS             20
#define PROTECTORS           2 // <=4!
#define OCTOPI              12 // this limit applies only to predefined octopi

#define FREQ_BIRD          140
#define FREQ_CLOUD          55
#define FREQ_CLOUDFIRE      30
#define FREQ_DOG            55
#define FREQ_DRIP           15
#define FREQ_FISH          105
#define FREQ_GOAT           30
#define FREQ_GOATFIRE       10
#define FREQ_GOATMOVE        5
#define FREQ_OCTOPUS        90
#define FREQ_OCTOPUSFIRE    40
#define FREQ_OCTOPUSSPIN     3
#define FREQ_ORB            35
#define FREQ_PENGUIN        40
#define FREQ_SLIME          80
#define FREQ_SLIMEGROW      75
#define FREQ_TELEPORT      210
#define FREQ_TIMEBOMB      210

#define SPEED_BIRD          10
#define SPEED_CLOUD          9
#define SPEED_DOG            3
#define SPEED_DRIP           4
#define SPEED_FISH          12
#define SPEED_FRAGMENT       3
#define SPEED_GOAT          16
#define SPEED_MAGNET         9
#define SPEED_MISSILE        6
#define SPEED_OCTOPUS       16
#define SPEED_ORB            6
#define SPEED_OTTER         19
#define SPEED_PENGUIN       11
#define SPEED_TIMEBOMB      60
#define SPEED_WHIRLWIND      1

#define HARDNESS_BIRD       50
#define HARDNESS_CLOUD      50
#define HARDNESS_DOG        50
#define HARDNESS_DRIP       50
#define HARDNESS_FISH       50
#define HARDNESS_FRAGMENT   50
#define HARDNESS_GOAT       50
#define HARDNESS_MISSILE    50
#define HARDNESS_OCTOPUS    50
#define HARDNESS_OTTER      95
#define HARDNESS_ORB        50
#define HARDNESS_PENGUIN    10
#define HARDNESS_TIMEBOMB   80
#define HARDNESS_WHIRLWIND  90

#define DISTANCE_FAST        5
#define DISTANCE_BIRD        3
#define DISTANCE_NORMAL      4
#define DISTANCE_NOSE        4
#define DISTANCE_SLOW        3
#define DISTANCE_VERYSLOW    2

#define POINTS_EMPTY         1
#define POINTS_DYNAMITE      5
#define POINTS_TURNSILVER    5
#define POINTS_ENCLOSURE    10
#define POINTS_SILVER       10
#define POINTS_TURNGOLD     10
#define POINTS_GOLD         20
#define POINTS_LETTER      100
#define POINTS_GRAVE       100

#define  ADD_BOMB      5 // in squares radius
#define  ADD_CLOCK    10 // in seconds
#define  ADD_CUTTER   10 // in VERYSLOWs
#define  ADD_GLOW     20 // in VERYSLOWs
#define  ADD_ICE      10 // in VERYSLOWs
#define  ADD_ARMOUR   25 // in VERYSLOWs
#define  ADD_TREASURE 10 // in seconds
#define RAND_BOMB     25
#define RAND_CLOCK    20
#define RAND_CUTTER   20
#define RAND_GLOW     50
#define RAND_ICE       5
#define RAND_ARMOUR   25
#define RAND_TREASURE 10

MODULE  void changefield(void);
MODULE  void death(void);
MODULE  void fastloop(void);
MODULE  void killall(void);
MODULE  void magnetloop(void);
MODULE  void newhiscores(void);
MODULE  void slowloop(void);
MODULE  void ReadGameports(void);
MODULE  void verynewlevel(void);

MODULE  void bangdynamite(SBYTE x, SBYTE y, SBYTE player);
MODULE  void bombblast(SBYTE triggerer, SBYTE player, SBYTE centrex, SBYTE centrey);
MODULE  void bothcol(SBYTE player, SBYTE x, SBYTE y);
MODULE  void bouncegoatoctopus(UBYTE which, SBYTE x, SBYTE y);
MODULE  void __inline change(SBYTE x, SBYTE y, UBYTE image);
MODULE  void checkrectangle(SBYTE direction, SBYTE player, SBYTE horizontalsize, SBYTE verticalsize);
MODULE  void cloudbullet(UBYTE which, SBYTE x, SBYTE y, SBYTE deltay);
MODULE  void creatureloop(SBYTE which);
MODULE  void dogqueue(SBYTE which, SBYTE deltax, SBYTE deltay);
MODULE  void drawcause(SBYTE player, SBYTE state);
MODULE  void getnumber(SBYTE player);
MODULE  void newlevel(UBYTE player);
MODULE  void orbsplit(SBYTE which);
MODULE  void protcol(SBYTE player, SBYTE x, SBYTE y, SBYTE thisprot);
MODULE  void putnumber(void);
MODULE  void ramming(SBYTE player);
MODULE  void reflect(UBYTE which);
MODULE  void turnworm(SBYTE player, SBYTE deltax, SBYTE deltay);
MODULE  void updatearrow(SBYTE arrowy);
MODULE  void wormbullet(SBYTE player);
MODULE  void wormloop(SBYTE player);
MODULE  void wormcol(SBYTE player, SBYTE x, SBYTE y);

MODULE void wormworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2);
MODULE void protworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2);
MODULE void protprot(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2);
MODULE void octopusfire(UBYTE which);
MODULE void squareblast(SBYTE type, SBYTE player, UBYTE c, SBYTE x, SBYTE y, ABOOL cutter);
MODULE void drawmissile(SBYTE x, SBYTE y, UBYTE which);

MODULE SWORD atleast(SWORD value, SWORD minimum);
MODULE ABOOL blocked(UBYTE which, SBYTE deltax, SBYTE deltay);
MODULE ABOOL bounceorb(UBYTE which, SBYTE x, SBYTE y);
MODULE SBYTE bsign(SBYTE value);
MODULE ABOOL findempty(SBYTE* x, SBYTE* y, ABOOL mode);
MODULE SBYTE onlyworm(ABOOL alive);
MODULE SBYTE slowdown(SBYTE speed, ABOOL nitro);
MODULE SBYTE speedup(SBYTE speed, ABOOL nitro);
MODULE UBYTE whichcreature(SBYTE x, SBYTE y, UBYTE species, UBYTE exception);
MODULE SBYTE whichteleport(SBYTE x, SBYTE y);
MODULE ULONG wormobject(UBYTE player, SBYTE x, SBYTE y);

MODULE void wormkillcreature(UBYTE player, UBYTE which);
MODULE void createcreature(UBYTE species, UBYTE which, SBYTE x, SBYTE y, SBYTE deltax, SBYTE deltay, UBYTE player);
MODULE ULONG arand(ULONG number);
MODULE void protcreature(UBYTE player, UBYTE which);
MODULE void wormcreature(UBYTE player, UBYTE which);
MODULE void creaturecreature(UBYTE which1, UBYTE which2);

/* PRIVATE STRUCTURES -------------------------------------------------- */

struct
{   SBYTE x, y, deltax, deltay;
    ABOOL alive, moved, teleported, visible, reflected;
} bullet[9];
struct
{   UWORD freq;
    ULONG score;
} object[LASTOBJECT + 1] =
{   {1600,  60}, // AFFIXER
    {  65,  20}, // AMMO
    { 105,  20}, // ARMOUR
    {  70,  50}, // BIAS
    { 130,  30}, // BOMB
    { 110,  10}, // BONUS
    { 900,  60}, // CLOCK
    { 370,  50}, // CONVERTER
    { 285,  80}, // CUTTER
    { 250,  90}, // CYCLONE
    { 310,  20}, // ENCLOSER
    { 280,  30}, // GLOW
    { 210,  50}, // GROWER
    {1900,  90}, // HEALER
    { 950,  60}, // ICE
    { 140,  60}, // LIFE
    { 160,  80}, // LIGHTNING
    { 925,  80}, // MAGNET
    { 215,  40}, // MISSILE
    { 635,  50}, // MULTIPLIER
    { 380,  10}, // NITRO
    { 235,  30}, // POWER
    { 470,  50}, // PROTECTOR
    { 210,  40}, // PULSE
    { 300,  50}, // PUSHER
    { 400,  40}, // REMNANTS
    { 500,  30}, // SIDESHOT
    { 600,  40}, // SLAYER
    { 980,  40}, // SLOWER
    { 730,  70}, // SWITCHER
    {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];

AGLOBAL UBYTE missileframes[4][MISSILEFRAMES + 1] =
{ { FIRSTMISSILE,
    FIRSTMISSILEFRAME,
    FIRSTMISSILEFRAME + 1,
    FIRSTMISSILEFRAME + 2,
    FIRSTMISSILEFRAME + 3,
    FIRSTMISSILEFRAME + 4
  },
  { FIRSTMISSILE + 1,
    FIRSTMISSILEFRAME + 5,
    FIRSTMISSILEFRAME + 6,
    FIRSTMISSILEFRAME + 7,
    FIRSTMISSILEFRAME + 8,
    FIRSTMISSILEFRAME + 9
  },
  { FIRSTMISSILE + 2,
    FIRSTMISSILEFRAME + 10,
    FIRSTMISSILEFRAME + 11,
    FIRSTMISSILEFRAME + 12,
    FIRSTMISSILEFRAME + 13,
    FIRSTMISSILEFRAME + 14,
  },
  { FIRSTMISSILE + 3,
    FIRSTMISSILEFRAME + 15,
    FIRSTMISSILEFRAME + 16,
    FIRSTMISSILEFRAME + 17,
    FIRSTMISSILEFRAME + 18,
    FIRSTMISSILEFRAME + 19
} };

UBYTE eachworm[4][2][9] =
{ { { GREENHEAD_NW,  GREENHEADUP,   GREENHEAD_NE,
      GREENHEADLEFT, ANYTHING,      GREENHEADRIGHT,
      GREENHEAD_SW,  GREENHEADDOWN, GREENHEAD_SE
    },
    { GREENGLOW_NW,  GREENGLOWUP,   GREENGLOW_NE,
      GREENGLOWLEFT, ANYTHING,      GREENGLOWRIGHT,
      GREENGLOW_SW,  GREENGLOWDOWN, GREENGLOW_SE
  } },
  { { REDHEAD_NW,    REDHEADUP,     REDHEAD_NE,
      REDHEADLEFT,   ANYTHING,      REDHEADRIGHT,
      REDHEAD_SW,    REDHEADDOWN,   REDHEAD_SE
    },
    { REDGLOW_NW,    REDGLOWUP,     REDGLOW_NE,
      REDGLOWLEFT,   ANYTHING,      REDGLOWRIGHT,
      REDGLOW_SW,    REDGLOWDOWN,   REDGLOW_SE
  } },
  { { BLUEHEAD_NW,   BLUEHEADUP,    BLUEHEAD_NE,
      BLUEHEADLEFT,  ANYTHING,      BLUEHEADRIGHT,
      BLUEHEAD_SW,   BLUEHEADDOWN,  BLUEHEAD_SE
    },
    { BLUEGLOW_NW,   BLUEGLOWUP,    BLUEGLOW_NE,
      BLUEGLOWLEFT,  ANYTHING,      BLUEGLOWRIGHT,
      BLUEGLOW_SW,   BLUEGLOWDOWN,  BLUEGLOW_SE
  } },
  { { YELLOWHEAD_NW, YELLOWHEADUP,  YELLOWHEAD_NE,
      YELLOWHEADLEFT,ANYTHING,      YELLOWHEADRIGHT,
      YELLOWHEAD_SW, YELLOWHEADDOWN,YELLOWHEAD_SE
    },
    { YELLOWGLOW_NW, YELLOWGLOWUP,  YELLOWGLOW_NE,
      YELLOWGLOWLEFT,ANYTHING,      YELLOWGLOWRIGHT,
      YELLOWGLOW_SW, YELLOWGLOWDOWN,YELLOWGLOW_SE
} } };

UBYTE eachtail[4][2][9][9] = {
{ { { GN_SE_NW,      GN_SE_N,       GN_SE_NE, // going NW (delta -1, -1)
      GN_SE_W,       WHATEVER,      GN_SE_E,  // (so starting from SE)
      GN_SE_SW,      GN_SE_S,       WHATEVER
    },
    { GN_S_NW,       GN_S_N,        GN_S_NE,  // going N (delta 0, -1)
      GN_S_W,        WHATEVER,      GN_S_E,   // (so starting from S)
      GN_S_SW,       WHATEVER,      GN_S_SE
    },
    { GN_SW_NW,      GN_SW_N,       GN_SW_NE, // going NE (delta 0, 1)
      GN_SW_W,       WHATEVER,      GN_SW_E,  // (so starting from SW)
      WHATEVER,      GN_SW_S,       GN_SW_SE
    },
    { GN_E_NW,       GN_E_N,        GN_E_NE,  // going W (delta -1, 0)
      GN_E_W,        WHATEVER,      WHATEVER, // (so starting from E)
      GN_E_SW,       GN_E_S,        GN_E_SE
    },
    { GN_SE_NW,      GN_S_N,        GN_SW_NE, // going nowhere (delta 0, 0)
      GN_E_W,        WHATEVER,      GN_W_E,  
      GN_NE_SW,      GN_N_S,        GN_NW_SE
    },
    { GN_W_NW,       GN_W_N,        GN_W_NE,  // going E (delta 1, 0)
      WHATEVER,      WHATEVER,      GN_W_E,   // (so starting from W)
      GN_W_SW,       GN_W_S,        GN_W_SE
    },
    { GN_NE_NW,      GN_NE_N,       WHATEVER, // going SW (delta -1, 1)
      GN_NE_W,       WHATEVER,      GN_NE_E,  // (so starting from NE)
      GN_NE_SW,      GN_NE_S,       GN_NE_SE
    },
    { GN_N_NW,       WHATEVER,      GN_N_NE,  // going S (delta 0, 1)
      GN_N_W,        WHATEVER,      GN_N_E,   // (so starting from N)
      GN_N_SW,       GN_N_S,        GN_N_SE
    },
    { WHATEVER,      GN_NW_N,       GN_NW_NE, // going SE (delta 1, 1)
      GN_NW_W,       WHATEVER,      GN_NW_E,  // (so starting from NW)
      GN_NW_SW,      GN_NW_S,       GN_NW_SE
  } },
  { { GG_SE_NW,      GG_SE_N,       GG_SE_NE, // going NW (delta -1, -1)
      GG_SE_W,       WHATEVER,      GG_SE_E,  // (so starting from SE)
      GG_SE_SW,      GG_SE_S,       WHATEVER
    },
    { GG_S_NW,       GG_S_N,        GG_S_NE,  // going N (delta 0, -1)
      GG_S_W,        WHATEVER,      GG_S_E,   // (so starting from S)
      GG_S_SW,       WHATEVER,      GG_S_SE
    },
    { GG_SW_NW,      GG_SW_N,       GG_SW_NE, // going NE (delta 0, 1)
      GG_SW_W,       WHATEVER,      GG_SW_E,  // (so starting from SW)
      WHATEVER,      GG_SW_S,       GG_SW_SE
    },                
    { GG_E_NW,       GG_E_N,        GG_E_NE,  // going W (delta -1, 0)
      GG_E_W,        WHATEVER,      WHATEVER, // (so starting from E)
      GG_E_SW,       GG_E_S,        GG_E_SE
    },
    { GG_SE_NW,      GG_S_N,        GG_SW_NE, // going nowhere (delta 0, 0)
      GG_E_W,        WHATEVER,      GG_W_E,  
      GG_NE_SW,      GG_N_S,        GG_NW_SE
    },
    { GG_W_NW,       GG_W_N,        GG_W_NE,  // going E (delta 1, 0)
      WHATEVER,      WHATEVER,      GG_W_E,   // (so starting from W)
      GG_W_SW,       GG_W_S,        GG_W_SE
    },
    { GG_NE_NW,      GG_NE_N,       WHATEVER, // going SW (delta -1, 1)
      GG_NE_W,       WHATEVER,      GG_NE_E,  // (so starting from NE)
      GG_NE_SW,      GG_NE_S,       GG_NE_SE
    },
    { GG_N_NW,       WHATEVER,      GG_N_NE,  // going S (delta 0, 1)
      GG_N_W,        WHATEVER,      GG_N_E,   // (so starting from N)
      GG_N_SW,       GG_N_S,        GG_N_SE
    },
    { WHATEVER,      GG_NW_N,       GG_NW_NE, // going SE (delta 1, 1)
      GG_NW_W,       WHATEVER,      GG_NW_E,  // (so starting from NW)
      GG_NW_SW,      GG_NW_S,       GG_NW_SE
} } },
{ { { RN_SE_NW,      RN_SE_N,       RN_SE_NE, // going NW (delta -1, -1)
      RN_SE_W,       WHATEVER,      RN_SE_E,  // (so starting from SE)
      RN_SE_SW,      RN_SE_S,       WHATEVER
    },                              
    { RN_S_NW,       RN_S_N,        RN_S_NE,  // going N (delta 0, -1)
      RN_S_W,        WHATEVER,      RN_S_E,   // (so starting from S)
      RN_S_SW,       WHATEVER,      RN_S_SE
    },
    { RN_SW_NW,      RN_SW_N,       RN_SW_NE, // going NE (delta 0, 1)
      RN_SW_W,       WHATEVER,      RN_SW_E,  // (so starting from SW)
      WHATEVER,      RN_SW_S,       RN_SW_SE
    },
    { RN_E_NW,       RN_E_N,        RN_E_NE,  // going W (delta -1, 0)
      RN_E_W,        WHATEVER,      WHATEVER, // (so starting from E)
      RN_E_SW,       RN_E_S,        RN_E_SE
    },
    { RN_SE_NW,      RN_S_N,        RN_SW_NE, // going nowhere (delta 0, 0)
      RN_E_W,        WHATEVER,      RN_W_E,  
      RN_NE_SW,      RN_N_S,        RN_NW_SE
    },
    { RN_W_NW,       RN_W_N,        RN_W_NE,  // going E (delta 1, 0)
      WHATEVER,      WHATEVER,      RN_W_E,   // (so starting from W)
      RN_W_SW,       RN_W_S,        RN_W_SE
    },
    { RN_NE_NW,      RN_NE_N,       WHATEVER, // going SW (delta -1, 1)
      RN_NE_W,       WHATEVER,      RN_NE_E,  // (so starting from NE)
      RN_NE_SW,      RN_NE_S,       RN_NE_SE
    },
    { RN_N_NW,       WHATEVER,      RN_N_NE,  // going S (delta 0, 1)
      RN_N_W,        WHATEVER,      RN_N_E,   // (so starting from N)
      RN_N_SW,       RN_N_S,        RN_N_SE
    },
    { WHATEVER,      RN_NW_N,       RN_NW_NE, // going SE (delta 1, 1)
      RN_NW_W,       WHATEVER,      RN_NW_E,  // (so starting from NW)
      RN_NW_SW,      RN_NW_S,       RN_NW_SE
  } },
  { { RG_SE_NW,      RG_SE_N,       RG_SE_NE, // going NW (delta -1, -1)
      RG_SE_W,       WHATEVER,      RG_SE_E,  // (so starting from SE)
      RG_SE_SW,      RG_SE_S,       WHATEVER
    },
    { RG_S_NW,       RG_S_N,        RG_S_NE,  // going N (delta 0, -1)
      RG_S_W,        WHATEVER,      RG_S_E,   // (so starting from S)
      RG_S_SW,       WHATEVER,      RG_S_SE
    },
    { RG_SW_NW,      RG_SW_N,       RG_SW_NE, // going NE (delta 0, 1)
      RG_SW_W,       WHATEVER,      RG_SW_E,  // (so starting from SW)
      WHATEVER,      RG_SW_S,       RG_SW_SE
    },
    { RG_E_NW,       RG_E_N,        RG_E_NE,  // going W (delta -1, 0)
      RG_E_W,        WHATEVER,      WHATEVER, // (so starting from E)
      RG_E_SW,       RG_E_S,        RG_E_SE
    },
    { RG_SE_NW,      RG_S_N,        RG_SW_NE, // going nowhere (delta 0, 0)
      RG_E_W,        WHATEVER,      RG_W_E,  
      RG_NE_SW,      RG_N_S,        RG_NW_SE
    },
    { RG_W_NW,       RG_W_N,        RG_W_NE,  // going E (delta 1, 0)
      WHATEVER,      WHATEVER,      RG_W_E,   // (so starting from W)
      RG_W_SW,       RG_W_S,        RG_W_SE
    },
    { RG_NE_NW,      RG_NE_N,       WHATEVER, // going SW (delta -1, 1)
      RG_NE_W,       WHATEVER,      RG_NE_E,  // (so starting from NE)
      RG_NE_SW,      RG_NE_S,       RG_NE_SE
    },
    { RG_N_NW,       WHATEVER,      RG_N_NE,  // going S (delta 0, 1)
      RG_N_W,        WHATEVER,      RG_N_E,   // (so starting from N)
      RG_N_SW,       RG_N_S,        RG_N_SE
    },
    { WHATEVER,      RG_NW_N,       RG_NW_NE, // going SE (delta 1, 1)
      RG_NW_W,       WHATEVER,      RG_NW_E,  // (so starting from NW)
      RG_NW_SW,      RG_NW_S,       RG_NW_SE
} } },
{ { { BN_NW_SE,      BN_N_SE,       BN_NE_SE, // going NW (delta -1, -1)
      BN_W_SE,       WHATEVER,      BN_E_SE,  // (so starting from SE)
      BN_SW_SE,      BN_S_SE,       WHATEVER
    },                              
    { BN_NW_S,       BN_N_S,        BN_NE_S,  // going N (delta 0, -1)
      BN_W_S,        WHATEVER,      BN_E_S,   // (so starting from S)
      BN_S_SW,       WHATEVER,      BN_S_SE
    },
    { BN_NW_SW,      BN_N_SW,       BN_SW_NE, // going NE (delta 0, 1)
      BN_W_SW,       WHATEVER,      BN_SW_E,  // (so starting from SW)
      WHATEVER,      BN_S_SW,       BN_SW_SE
    },
    { BN_NW_E,       BN_N_E,        BN_E_NE,  // going W (delta -1, 0)
      BN_W_E,        WHATEVER,      WHATEVER, // (so starting from E)
      BN_SW_E,       BN_E_S,        BN_E_SE
    },
    { BN_NW_SE,      BN_N_S,        BN_SW_NE, // going nowhere (delta 0, 0)
      BN_W_E,        WHATEVER,      BN_W_E,  
      BN_NE_SW,      BN_N_S,        BN_NW_SE
    },
    { BN_W_NW,       BN_N_W,        BN_W_NE,  // going E (delta 1, 0)
      WHATEVER,      WHATEVER,      BN_W_E,   // (so starting from W)
      BN_W_SW,       BN_W_S,        BN_W_SE
    },
    { BN_NW_NE,      BN_N_NE,       WHATEVER, // going SW (delta -1, 1)
      BN_W_NE,       WHATEVER,      BN_E_NE,  // (so starting from NE)
      BN_NE_SW,      BN_NE_S,       BN_NE_SE
    },
    { BN_N_NW,       WHATEVER,      BN_N_NE,  // going S (delta 0, 1)
      BN_N_W,        WHATEVER,      BN_N_E,   // (so starting from N)
      BN_N_SW,       BN_N_S,        BN_N_SE
    },
    { WHATEVER,      BN_N_NW,       BN_NW_NE, // going SE (delta 1, 1)
      BN_W_NW,       WHATEVER,      BN_NW_E,  // (so starting from NW)
      BN_NW_SW,      BN_NW_S,       BN_NW_SE
  } },
  { { BG_NW_SE,      BG_N_SE,       BG_NE_SE, // going NW (delta -1, -1)
      BG_W_SE,       WHATEVER,      BG_E_SE,  // (so starting from SE)
      BG_SW_SE,      BG_S_SE,       WHATEVER
    },
    { BG_NW_S,       BG_N_S,        BG_NE_S,  // going N (delta 0, -1)
      BG_W_S,        WHATEVER,      BG_E_S,   // (so starting from S)
      BG_S_SW,       WHATEVER,      BG_S_SE
    },
    { BG_NW_SW,      BG_N_SW,       BG_NE_SW, // going NE (delta 0, 1)
      BG_W_SW,       WHATEVER,      BG_SW_E,  // (so starting from SW)
      WHATEVER,      BG_S_SW,       BG_SW_SE
    },
    { BG_NE_E,       BG_N_E,        BG_NE_E,  // going W (delta -1, 0)
      BG_W_E,        WHATEVER,      WHATEVER, // (so starting from E)
      BG_SW_E,       BG_E_S,        BG_E_SE
    },
    { BG_NW_SE,      BG_N_S,        BG_NE_SW, // going nowhere (delta 0, 0)
      BG_W_E,        WHATEVER,      BG_W_E,  
      BG_NE_SW,      BG_N_S,        BG_NW_SE
    },
    { BG_NW_W,       BG_N_W,        BG_NE_W,  // going E (delta 1, 0)
      WHATEVER,      WHATEVER,      BG_W_E,   // (so starting from W)
      BG_W_SW,       BG_W_S,        BG_W_SE
    },
    { BG_NW_NE,      BG_N_NE,       WHATEVER, // going SW (delta -1, 1)
      BG_NE_W,       WHATEVER,      BG_NE_E,  // (so starting from NE)
      BG_NE_SW,      BG_NE_S,       BG_NE_SE
    },
    { BG_N_NW,       WHATEVER,      BG_N_NE,  // going S (delta 0, 1)
      BG_N_W,        WHATEVER,      BG_N_E,   // (so starting from N)
      BG_N_SW,       BG_N_S,        BG_N_SE
    },
    { WHATEVER,      BG_N_NW,       BG_NW_NE, // going SE (delta 1, 1)
      BG_NW_W,       WHATEVER,      BG_NW_E,  // (so starting from NW)
      BG_NW_SW,      BG_NW_S,       BG_NW_SE
} } },
{ { { YN_NW_SE,      YN_N_SE,       YN_NE_SE, // going NW (delta -1, -1)
      YN_W_SE,       WHATEVER,      YN_E_SE,  // (so starting from SE)
      YN_SW_SE,      YN_S_SE,       WHATEVER
    },                              
    { YN_NW_S,       YN_N_S,        YN_NE_S,  // going N (delta 0, -1)
      YN_W_S,        WHATEVER,      YN_S_E,   // (so starting from S)
      YN_S_SW,       WHATEVER,      YN_S_SE
    },
    { YN_NW_SW,      YN_N_SW,       YN_NE_SW, // going NE (delta 0, 1)
      YN_W_SW,       WHATEVER,      YN_E_SW,  // (so starting from SW)
      WHATEVER,      YN_S_SW,       YN_SW_SE
    },
    { YN_NW_E,       YN_N_E,        YN_NE_E,  // going W (delta -1, 0)
      YN_W_E,        WHATEVER,      WHATEVER, // (so starting from E)
      YN_E_SW,       YN_S_E,        YN_E_SE
    },
    { YN_NW_SE,      YN_N_S,        YN_NE_SW, // going nowhere (delta 0, 0)
      YN_W_E,        WHATEVER,      YN_W_E,  
      YN_NE_SW,      YN_N_S,        YN_NW_SE
    },
    { YN_NW_W,       YN_N_W,        YN_W_NE,  // going E (delta 1, 0)
      WHATEVER,      WHATEVER,      YN_W_E,   // (so starting from W)
      YN_W_SW,       YN_W_S,        YN_W_SE
    },
    { YN_NW_NE,      YN_N_NE,       WHATEVER, // going SW (delta -1, 1)
      YN_W_NE,       WHATEVER,      YN_NE_E,  // (so starting from NE)
      YN_NE_SW,      YN_NE_S,       YN_NE_SE
    },
    { YN_N_NW,       WHATEVER,      YN_N_NE,  // going S (delta 0, 1)
      YN_N_W,        WHATEVER,      YN_N_E,   // (so starting from N)
      YN_N_SW,       YN_N_S,        YN_N_SE
    },
    { WHATEVER,      YN_N_NW,       YN_NW_NE, // going SE (delta 1, 1)
      YN_NW_W,       WHATEVER,      YN_NW_E,  // (so starting from NW)
      YN_NW_SW,      YN_NW_S,       YN_NW_SE
  } },
  { { YG_NW_SE,      YG_N_SE,       YG_NE_SE, // going NW (delta -1, -1)
      YG_W_SE,       WHATEVER,      YG_E_SE,  // (so starting from SE)
      YG_SW_SE,      YG_S_SE,       WHATEVER
    },                              
    { YG_S_NW,       YG_N_S,        YG_S_NE,  // going N (delta 0, -1)
      YG_W_S,        WHATEVER,      YG_E_S,   // (so starting from S)
      YG_S_SW,       WHATEVER,      YG_S_SE
    },
    { YG_NW_SW,      YG_N_SW,       YG_NE_SW, // going NE (delta 0, 1)
      YG_W_SW,       WHATEVER,      YG_E_SW,  // (so starting from SW)
      WHATEVER,      YG_S_SW,       YG_SW_SE
    },
    { YG_E_NW,       YG_E_N,        YG_E_NE,  // going W (delta -1, 0)
      YG_W_E,        WHATEVER,      WHATEVER, // (so starting from E)
      YG_E_SW,       YG_E_S,        YG_E_SE
    },
    { YG_NW_SE,      YG_N_S,        YG_NE_SW, // going nowhere (delta 0, 0)
      YG_W_E,        WHATEVER,      YG_W_E,  
      YG_NE_SW,      YG_N_S,        YG_NW_SE
    },
    { YG_W_NW,       YG_N_W,        YG_W_NE,  // going E (delta 1, 0)
      WHATEVER,      WHATEVER,      YG_W_E,   // (so starting from W)
      YG_W_SW,       YG_W_S,        YG_W_SE
    },
    { YG_NW_NE,      YG_N_NE,       WHATEVER, // going SW (delta -1, 1)
      YG_W_NE,       WHATEVER,      YG_E_NE,  // (so starting from NE)
      YG_NE_SW,      YG_S_NE,       YG_NE_SE
    },
    { YG_N_NW,       WHATEVER,      YG_N_NE,  // going S (delta 0, 1)
      YG_N_W,        WHATEVER,      YG_E_N,   // (so starting from N)
      YG_N_SW,       YG_N_S,        YG_N_SE
    },
    { WHATEVER,      YG_N_NW,       YG_NW_NE, // going SE (delta 1, 1)
      YG_W_NW,       WHATEVER,      YG_E_NW,  // (so starting from NW)
      YG_NW_SW,      YG_S_NW,       YG_NW_SE
} } }
};

/* 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, visible;
    SBYTE x, y, deltax, deltay, pos, time, dir;
    UBYTE dormant, multi, speed, last, oldlast, species, hardness, frame,
          journey, going, then,
          type; // type is worm 0-3 (for drips, missiles, dogs and birds)
    SWORD freq;
    ULONG score;
} creature[CREATURES + 1];

// MODULE VARIABLES (used only within engine.c) ---------------------------

MODULE ABOOL banging  = FALSE,
             first    = TRUE,
             letters[LETTERS + 1],
             enclosed = FALSE,
             trainer;
MODULE UBYTE infector[FIELDX + 1][FIELDY + 1],
             ice,
             leveltype;
MODULE SBYTE letterx,
             lettery,
             number,
             treasurer;

// GLOBAL VARIABLES -------------------------------------------------------

ABOOL anims      = TRUE,
      clearthem  = FALSE,
      modified   = FALSE,
      randomflag = FALSE,
      randomarray[MAXLEVELS + 1],
      thick      = FALSE,
      turbo      = FALSE;
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];

MODULE ABOOL blocked(UBYTE 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 == METAL || c == GOAT || c == OCTOPUS)
        return TRUE;
    else return FALSE;
}

MODULE void bombblast(SBYTE triggerer, SBYTE player, SBYTE centrex, SBYTE centrey)
{   SBYTE counter, downy, downymax, leftx, leftxmax, rightx, rightxmax, strength, uppy, uppymax, x, y;

    effect(FXUSE_BOMB);
    strength = ADD_BOMB + arand(RAND_BOMB);

    leftxmax = centrex - strength;
    if (leftxmax < 0)
        leftxmax = 0;
    rightxmax = centrex + strength;
    if (rightxmax > FIELDX)
        rightxmax = FIELDX;
    uppymax = centrey - strength;
    if (uppymax < 0)
        uppymax = 0;
    downymax = centrey + strength;
    if (downymax > FIELDY)
        downymax = FIELDY;

    leftx = centrex;
    rightx = centrex;
    uppy = centrey;
    downy = centrey;
    for (counter = 1; counter <= strength; counter++)
    {   if (leftx > leftxmax)
        {   leftx--;
            for (y = uppy; y <= downy; y++)
                squareblast(triggerer, player, field[leftx][y], leftx, y, FALSE);
        }
        if (uppy > uppymax)
        {   uppy--;
            for (x = leftx; x <= rightx; x++)
                squareblast(triggerer, player, field[x][uppy], x, uppy, FALSE);
        }
        if (rightx < rightxmax)
        {   rightx++;
            for (y = downy; y >= uppy; y--)
                squareblast(triggerer, player, field[rightx][y], rightx, y, FALSE);
        }
        if (downy < downymax)
        {   downy++;
            for (x = rightx; x >= leftx; x--)
                squareblast(triggerer, player, field[x][downy], x, downy, FALSE);
}   }   }

MODULE void bouncegoatoctopus(UBYTE which, SBYTE x, SBYTE y)
{   UBYTE killed;

    if (field[x][y] == GOAT || field[x][y] == OCTOPUS || field[x][y] == FISH)
    {   killed = whichcreature(x, y, field[x][y], which);
        creature[killed].alive = FALSE;
        change(x, y, BONUS);
}   }

MODULE ABOOL bounceorb(UBYTE which, SBYTE x, SBYTE y)
{   if
    (    field[x][y] == METAL
     ||  field[x][y] == STONE
     ||  field[x][y] == WOOD
     ||  field[x][y] == GOAT
     ||  field[x][y] == OCTOPUS
     ||  field[x][y] == FISH
     || (field[x][y] >= FIRSTTAIL && field[x][y] <= LASTTAIL)
    )
    {   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 = arand(levels - 1) + 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 death(void)
{   SBYTE pain, player;
    UBYTE 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 = PAIN_FRIENDLYTAIL;
                    else pain = PAIN_ENEMYTAIL;
                    slow = TRUE;
                } elif (worm[player].cause >= FIRSTGLOW && worm[player].cause <= LASTGLOW)
                {   pain = PAIN_GLOW;
                    slow = TRUE;
                } elif (worm[player].cause >= FIRSTFIRE && worm[player].cause <= LASTFIRE)
                    pain = PAIN_WORMFIRE;
                elif (worm[player].cause >= FIRSTHEAD && worm[player].cause <= LASTHEAD)
                    pain = PAIN_HEAD;
                elif (worm[player].cause >= FIRSTPROTECTOR && worm[player].cause <= LASTPROTECTOR)
                    pain = PAIN_PROTECTOR;
                elif (worm[player].cause >= FIRSTMISSILE && worm[player].cause <= LASTMISSILE)
                    pain = PAIN_MISSILE;
                elif (worm[player].cause >= FIRSTDRIP && worm[player].cause <= LASTDRIP)
                    pain = PAIN_DRIP;
                else switch (worm[player].cause)
                {
                case GOAT:
                    pain = PAIN_GOAT;
                    slow = TRUE;
                break;
                case OCTOPUS:
                    pain = PAIN_OCTOPUS;
                    slow = TRUE;
                break;
                case METAL:
                    pain = PAIN_METAL;
                    slow = TRUE;
                break;
                case SLIME:
                    pain = PAIN_SLIME;
                    slow = TRUE;
                break;
                case STONE:
                    pain = PAIN_STONE;
                    slow = TRUE;
                break;
                case TELEPORT:
                    pain = PAIN_TELEPORT;
                    slow = TRUE;
                break;
                case WOOD:
                    pain = PAIN_WOOD;
                    slow = TRUE;
                break;
                case BIRD:
                    pain = PAIN_BIRD;
                break;
                case BOMB:
                    pain = PAIN_BOMB;
                break;
                case CLOUD:
                    pain = PAIN_CLOUD;
                break;
                case DOG:
                    pain = PAIN_DOG;
                break;
                case FRAGMENT:
                    pain = PAIN_FRAGMENT;
                break;
                case LIGHTNING:
                    pain = PAIN_LIGHTNING;
                break;
                case ORB:
                    pain = PAIN_ORB;
                break;
                case OTTER:
                    pain = PAIN_OTTER;
                break;
                case PENGUIN:
                    pain = PAIN_PENGUIN;
                break;
                case SLAYER:
                    pain = PAIN_SLAYER;
                break;
                case WHIRLWIND:
                    pain = PAIN_WHIRLWIND;
                break;
                default:
                    // assert(0);
                break;
                }
                if (worm[player].victor >= 0 && worm[player].victor != player)
                {   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, FIRSTPAIN + player);
                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);
                    change(worm[player].x, worm[player].y, FIRSTGRAVE + player);
                    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
        (   -6,
            (player * 10) + 8,
            BLACKENED
        );
    } else
    {   draw
        (   -6,
            (player * 10) + 8,
            worm[player].cause
        );
}   }

/* 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].dimcolour = DARKGREEN;
    worm[1].dimcolour = DARKRED;
    worm[2].dimcolour = DARKBLUE;
    worm[3].dimcolour = DARKYELLOW;
    worm[0].port = 2;
    worm[1].port = 3;
    worm[2].port = 1;
    worm[3].port = 0;
    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)
{   SBYTE i, player, x, y;

    // all octopi spin together, but some may not be spinning, each may
    // be in a different part of its spin.
    if (!arand(FREQ_OCTOPUSSPIN))
    {   for (i = 0; i <= CREATURES; i++)
        {   if (creature[i].alive && creature[i].species == OCTOPUS && creature[i].dir >= 0)
            {   octopusfire(i);
    }   }   }

    // animate
    if (anims)
    {   for (i = 0; i <= CREATURES; i++)
        {   if (creature[i].alive && creature[i].visible)
            {   if (creature[i].species == BIRD)
                {   if (!(r % 3))
                    {   creature[i].frame += creature[i].dir;
                        draw(creature[i].x, creature[i].y, BIRD + creature[i].frame);
                        if (creature[i].frame == 0)
                        {   creature[i].dir = 1;
                        } elif (creature[i].frame == 4)
                        {   creature[i].dir = -1;
                }   }   }
                elif (creature[i].species == MISSILE)
                {   if (!(r % 3))
                    {   drawmissile(creature[i].x, creature[i].y, i);
                        if (++creature[i].frame > MISSILEFRAMES)
                        {   creature[i].frame = 0;
    }   }   }   }   }   }

    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, FIRSTLETTER + number - 1);
    }   }

    // flash icons
    for (player = 0; player <= 3; player++)
    {   icon(player, GLOW);
        icon(player, CUTTER);
}   }

MODULE ABOOL findempty(SBYTE* x, SBYTE* y, ABOOL mode)
{   SBYTE count = 0, xx, yy;
    UBYTE c;

    if (mode)
    {   do
        {   xx = arand(FIELDX);
            yy = arand(FIELDY);
            c  = field[xx][yy];
        } while ((c != SLIME && c != WOOD && c != STONE && c != METAL && (c < FIRSTTAIL || c > LASTTAIL)) && ++count < PATIENCE);
    } else
    {   do
        {   xx = arand(FIELDX);
            yy = arand(FIELDY);
            c  = field[xx][yy];
        } while ((c < FIRSTEMPTY || c > LASTEMPTY) && ++count < PATIENCE);
    }

    if (count < PATIENCE)
    {   *x = xx;
        *y = yy;
        return(TRUE);
    } else
    {   return(FALSE);
}   }

void gameloop(void)
{   SBYTE i, player;

    if (a == PLAYGAME)
    {   ReadGameports();
        fastloop();
        gameinput();
    }
    if (a == PLAYGAME)
    {   ReadGameports();
        for (player = 0; player <= 3; player++)
            if (worm[player].lives && !(r % worm[player].speed))
                wormloop(player);
    }
    if (a == PLAYGAME)
    {   ReadGameports();
        for (i = 0; i <= CREATURES; i++)
        {   if (creature[i].alive && !(r % creature[i].speed) && (!ice || creature[i].species == MISSILE || creature[i].species == FRAGMENT))
            {   creatureloop(i);
    }   }   }
    if (a == PLAYGAME)
    {   ReadGameports();
        if (!(r % SPEED_MAGNET))
        {   magnetloop();
        }
        death();
    }
    if (a == PLAYGAME)
    {   ReadGameports();
        if (!(r % VERYSLOW))
        {   slowloop();
    }   }
    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;
}

SBYTE loadfields(STRPTR fieldname)
{   SBYTE i, j, x, y;
    TEXT  IOBuffer[NAMELENGTH + 1];
    UBYTE ver;
    UBYTE oldboard[OLDFIELDX + 1][OLDFIELDY + 1];

    /* 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 7.0"))
        ver = 70;
    elif (!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;
    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 (ver <= 66)
        {   if (!ZRead((char *) &oldboard[0][0], OLDLEVELSIZE))
            {   ZClose();
                return 10;
                /* incorrect levels, corrupted high scores,
                incorrect startx, teleports, field data */
            }
            for (x = 0; x <= FIELDX; x++)
            {   for (y = 0; y < FIELDY; y++)
                {   board[i][x][y] = oldboard[x][y];
                }
                board[i][x][FIELDY] = EMPTY;
        }   }
        else
        {   if (!ZRead((char *) &board[i][0][0], LEVELSIZE))
            {   ZClose();
                return 10;
                /* incorrect levels, corrupted high scores,
                incorrect startx, teleports, field data */
        }   }

        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 > OCTOPI)
                    {   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;
}   }   }

AGLOBAL void newfield(void)
{   SBYTE x, y;

	teleport[level][0].alive = FALSE;
	teleport[level][1].alive = FALSE;
        startx[level] = CENTREX;
        starty[level] = CENTREY;

	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       = 0;
    reallevel = 0;
    level     = 1;
    a         = PLAYGAME;
    clearscreen();
    newlevel(arand(3));
    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(UBYTE 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++)
            {   if (worm[i].multi > 1)
                {   worm[i].multi   /= 2;
                }
                worm[i].remnants =
                worm[i].encloser =
                worm[i].pusher   = FALSE;

            }
            killall();
            changefield();
            for (x = 0; x <= FIELDX; x++)
            {   for (y = 0; y <= FIELDY; y++)
                {   if (board[level][x][y] == OCTOPUS)
                    {   octopi++;
                        if (octopi <= OCTOPI)
                        {   for (i = 0; i <= CREATURES; i++)
                            {   if (!creature[i].alive)
                                {   createcreature(OCTOPUS, i, x, y, 0, 0, 255);
            }   }   }   }   }   }
            for (i = 0; i <= 3; i++)
            {   worm[i].speed = NORMAL;
                if (worm[i].lives)
                    stat(i, NITRO);
                worm[i].moved = FALSE;
                worm[i].numbers = 0;
                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;
                number = 1;
                putnumber();
                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].alive      = TRUE;
                        worm[i].power      = 0;
                        worm[i].bias       = 0;
                        worm[i].multi      = 1;
                        worm[i].victor     = -1;
                        worm[i].ammo       = 0;
                        worm[i].glow       = 0;
                        worm[i].remnants   =
                        worm[i].affixer    =
                        worm[i].sideshot   =
                        worm[i].pusher     =
                        worm[i].flashed    =
                        worm[i].encloser   =
                        worm[i].rammed     =
                        worm[i].nitro      = FALSE;
                        worm[i].causewait  = (ULONG) -1;
                        worm[i].last       = FIRSTTAIL + i;
                        worm[i].pos        = -1;
                        worm[i].cutter     = 0;
                        worm[i].olddeltax  =
                        worm[i].olddeltay  = 0;
                        for (j = 0; j <= PROTECTORS; j++)
                        {   protector[i][j].alive = FALSE;
                        } for (j = 0; j <= LASTOBJECT; j++)
                        {   stat(i, j);
                            turbo = FALSE;
            }   }   }   }
            for (i = 0; i <= 3; i++)
            {   icon(i, REMNANTS);
                icon(i, AFFIXER);
                icon(i, SIDESHOT);
                icon(i, PUSHER);
                icon(i, GLOW);
                icon(i, CUTTER);
                icon(i, ENCLOSER);
    }   }   }
    clearkybd();
    resettime();
}

/* 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, distance, player, c, i;
    SBYTE x, y, xx, yy, xxx, yyy, frontx, fronty, rearx, reary,
          deltax, deltay;

    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); */
    }

    /* decide whether and where to move */

    switch(creature[which].species)
    {
    case BIRD:
        if (creature[which].type == 255)
        {   bestdistance = 255;
            for (player = 0; player <= 3; player++)
            {   if (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 <= DISTANCE_BIRD && distance < bestdistance)
                    {   effect(FXBORN_BIRD);
                        bestdistance = distance;
                        creature[which].type = player;
        }   }   }   }
        if (creature[which].type != 255) // if swooping
        {   if (worm[creature[which].type].lives && !worm[creature[which].type].bias)
            {   creature[which].deltax = bsign(worm[creature[which].type].x - x);
                creature[which].deltay = bsign(worm[creature[which].type].y - y);
            } else
            {   creature[which].type = 255; // return to dormancy
                creature[which].deltax = creature[which].deltay = 0;
        }   }
    break;
    case CLOUD:
        if (creature[which].x == 0 || creature[which].x == FIELDX)
            creature[which].deltax = -creature[which].deltax;
    break;
    case OTTER:
        if (secondsleft)
        {   return;
        }
        if (creature[which].journey == OTTER_RIGHT)
        {   switch(creature[which].going)
            {
            case OTTER_DOWN:
                if (creature[which].y == FIELDY)
                {   if (creature[which].x == FIELDX)
                    {   creature[which].journey = OTTER_LEFT;
                        creature[which].going = OTTER_UP;
                    } else
                    {   creature[which].going = OTTER_RIGHT;
                        creature[which].then  = OTTER_UP;
                }   }
            break;
            case OTTER_RIGHT:
                creature[which].going = creature[which].then;
            break;
            case OTTER_UP:             
                if (creature[which].y == 0)
                {   if (creature[which].x == FIELDX)
                    {   creature[which].journey = OTTER_LEFT;
                        creature[which].going = OTTER_DOWN;
                    } else
                    {   creature[which].going = OTTER_RIGHT;
                        creature[which].then  = OTTER_DOWN;
                }   }
            break;
            default:
            break;
        }   }
        else
        {   // assert(creature[which].journey == OTTER_LEFT);
            switch(creature[which].going)
            {
            case OTTER_DOWN:
                if (creature[which].y == FIELDY)
                {   if (creature[which].x == 0)
                    {   creature[which].journey = OTTER_RIGHT;
                        creature[which].going = OTTER_UP;
                    } else
                    {   creature[which].going = OTTER_LEFT;
                        creature[which].then  = OTTER_UP;
                }   }
            break;
            case OTTER_LEFT:
                creature[which].going = creature[which].then;
            break;
            case OTTER_UP:             
                if (creature[which].y == 0)
                {   if (creature[which].x == 0)
                    {   creature[which].journey = OTTER_RIGHT;
                        creature[which].going = OTTER_DOWN;
                    } else
                    {   creature[which].going = OTTER_LEFT;
                        creature[which].then  = OTTER_DOWN;
                }   }
            break;
            default:
                // assert(0);
            break;
        }   }

        if (creature[which].going == OTTER_RIGHT)
        {   creature[which].deltax = 1;
            creature[which].deltay = 0;
        } elif (creature[which].going == OTTER_LEFT)
        {   creature[which].deltax = -1;
            creature[which].deltay = 0;
        } elif (creature[which].going == OTTER_UP)
        {   creature[which].deltax = 0;
            creature[which].deltay = -1;
        } elif (creature[which].going == OTTER_DOWN)
        {   creature[which].deltax = 0;
            creature[which].deltay = 1;
        }
    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 = arand(2) - 1;
            yy = arand(2) - 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 have a slight upwards drift.
        Higher values of WEIGHT make it less buoyant. */

        creature[which].deltax = arand(2) - 1;
        if (!arand(WEIGHT))
            creature[which].deltay = arand(1) - 1;
        else creature[which].deltay = arand(2) - 1;
    break;
    case MISSILE:
        bestdistance = 255;
        for (player = 0; player <= 3; player++)
        {   if (creature[which].type != player && worm[player].lives)
            {   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 != DRIP
                         && creature[i].species != MISSILE
                        )
                         || creature[i].type != creature[which].type
                    )
                    {   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;
            change(x, y, EMPTY);
        }
    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))
        {   bouncegoatoctopus(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))
                {   bouncegoatoctopus(which, rearx, fronty);
                    xx = creature[which].deltax;
            }   }
            elif (!bounceorb(which, rearx, fronty))
            {   bouncegoatoctopus(which, rearx, fronty);
                yy = creature[which].deltay;
            }
            creature[which].deltax = xx;
            creature[which].deltay = yy;
        }
    break;
    case FISH:
	do
        {   xx = arand(2) - 1;
            yy = arand(2) - 1;
	} while (!valid(x + xx, y + yy));
        c = field[x + xx][y + yy];
        if (c == SLIME || c == WOOD || c == STONE || c == METAL || (c >= FIRSTTAIL && c <= LASTTAIL))
        {   creature[which].deltax = xx;
            creature[which].deltay = yy;
            creature[which].last = creature[which].oldlast;
            if (c >= FIRSTTAIL && c <= LASTTAIL)
            {   creature[which].oldlast = c - FIRSTTAIL + FIRSTGLOW;
            } else creature[which].oldlast = c;    
        } else
        {   creature[which].deltax =
            creature[which].deltay = 0;
        }
    break;
    case GOAT:
        /* decide whether to move */
        if (!arand(FREQ_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 =
        creature[which].deltay = 0;
        if (!happy)
        {   xx = arand(2) - 1;
            yy = arand(2) - 1;
            if (valid(x + xx, y + yy) && (xx || yy))
            {   c = field[x + xx][y + yy];
                if (c == SLIME || c == WOOD || c == STONE || c == METAL || (c >= FIRSTTAIL && c <= LASTTAIL))
                {   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);
            if (creature[which].species == OTTER)
            {   if ((worm[0].lives && worm[0].bias)
                 || (worm[1].lives && worm[1].bias)
                 || (worm[2].lives && worm[2].bias)
                 || (worm[3].lives && worm[3].bias))
                 {   creature[which].last = DYNAMITE;
                 } else creature[which].last = STONE;
            } else
            {   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 == DOG
             || creature[which].species == DRIP
             || creature[which].species == FRAGMENT
             || creature[which].species == WHIRLWIND
            )
	    {	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. */

    if
    (    creature[which].alive
     &&  creature[which].species != FISH
     &&  creature[which].species != GOAT
     &&  creature[which].species != PENGUIN
     &&  creature[which].species != OCTOPUS
     &&  creature[which].species != TIMEBOMB
     && (creature[which].deltax || creature[which].deltay)
    )
    {   c = field[x][y];

        if (c >= FIRSTHEAD && c <= LASTHEAD)
        {   wormcreature(c - FIRSTHEAD, which);
        } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR)
        {   protcreature(c - FIRSTPROTECTOR, which);
        } elif (c >= FIRSTTAIL && c <= LASTTAIL)
        {   ;
        } elif (c >= FIRSTLETTER && c <= LASTLETTER)
        {   creature[which].alive = FALSE;
        } elif (c == TIMEBOMB)
        {   if (creature[which].species != OTTER)
            {   creature[whichcreature(x, y, TIMEBOMB, which)].alive = FALSE;
            }
            bombblast(BOMB, 0, x, y);
            change(x, y, EMPTY);
        } elif (c >= FIRSTDRIP && c <= LASTDRIP)
        {   i = whichcreature(x, y, DRIP, which);
            creaturecreature(i, which);
        } elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
        {   i = whichcreature(x, y, MISSILE, which);
            creaturecreature(which, i);
        } elif
        (   c == BIRD
         || c == DOG
         || c == CLOUD
         || c == FISH
         || c == FRAGMENT
         || c == GOAT
         || c == OCTOPUS
         || c == ORB
         || c == OTTER
         || c == PENGUIN
         || c == WHIRLWIND
        )
        {   i = whichcreature(x, y, c, which);
            creaturecreature(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 || c == WOOD)
        {   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 == DYNAMITE)
        {   ;
        } elif (c >= FIRSTGRAVE && c <= LASTGRAVE)
        {   if (creature[which].species == ORB)
            {   effect(FXGET_SKULL);
            } 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, otters */
            i = whichteleport(x, y);
            if (creature[which].species != OTTER && 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)
                {   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 (creature[which].species == ORB)
        {   if (c <= LASTOBJECT)
            {   if
                (   c == AMMO
                 || c == CYCLONE
                 || c == LIGHTNING
                 || c == NITRO
                 || c == POWER
                 || c == PULSE
                 || c == SLAYER
                 || c == SLOWER
                 || c == ENCLOSER
                )
                {   effect(FXGET_NITRO);
                    creature[which].speed = speedup(creature[which].speed, TRUE);
                } elif (c == HEALER || c == LIFE || c == ICE || c == TREASURE || c == UMBRELLA || c == BONUS || c == ARMOUR)
                {   orbsplit(which);
                } else switch (c)
                {
                case BOMB:
                    draw(x, y, ORB);
                    bombblast(ORB, which, x, y);
                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 GLOW:
                    effect(FXGET_OBJECT);
                    for (xx = 0; xx <= FIELDX; xx++)
                        for (yy = 0; yy <= FIELDY; yy++)
                            if (field[xx][yy] >= FIRSTGLOW && field[xx][yy] <= LASTGLOW)
                                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 -= arand(RAND_CLOCK);
                    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;
    }   }   }   }
    
    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)
        {   drawmissile(x, y, which);
        } elif (creature[which].species == DRIP)
        {   change(x, y, FIRSTDRIP + creature[which].type);
        } else /* fragments, goats, penguins, cyclones, dogs, clouds, orbs, octopi, fish, otters */
        {   change(x, y, creature[which].species);
    }   }
    
    if (creature[which].alive)
    {   /* decide whether to fire */
        if (creature[which].species == GOAT)
        {   if (!arand(FREQ_GOATFIRE))
            {   for (i = 0; i <= CREATURES; i++)
                {   if (!creature[i].alive)
                    {   deltax = arand(2) - 1;
                        deltay = arand(2) - 1;
                        if (valid(x + deltax, y + deltay) && (deltax || deltay))
                        {   c = field[x + deltax][y + deltay];
                            if
                            (   (c >= FIRSTEMPTY && c <= LASTEMPTY)
                             || (c >= FIRSTTAIL && c <= LASTTAIL)
                            )
                            {   effect(FXDO_GOAT);
                                createcreature(FRAGMENT, i, x + deltax, y + deltay, deltax, deltay, 255);
                        }   }
                        break;
        }   }   }   }
        elif (creature[which].species == CLOUD)
        {   if (!arand(FREQ_CLOUDFIRE))
            {   cloudbullet(which, x, y, -1);
                cloudbullet(which, x, y, 1);
        }   }
        elif (creature[which].species == OCTOPUS)
        {   if (creature[which].dir == -1)
            {   if (!arand(FREQ_OCTOPUSFIRE))
                {   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)
        {   if (valid(x, y + deltay))
            {   c = field[x][y + deltay];
                if
                (   (c >= FIRSTEMPTY && c <= LASTEMPTY)
                ||  (c >= FIRSTTAIL && c <= LASTTAIL)
                )
                {   effect(FXDO_CLOUD);
                    createcreature(FRAGMENT, i, x, y + deltay, 0, deltay, 255);
            }   }
            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].speed   = creature[which].speed;
            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));
}

MODULE void putnumber(void)
{   SBYTE oldlettery = lettery,
          x, y;
    ABOOL done;

    do
    {   done = findempty(&x, &y, FALSE);
    } while (!done);
    letterx = x;
    lettery = y;
    change(x, y, FIRSTLETTER + number - 1);
    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 7.0");
    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);
        } elif (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 c;
    ABOOL ok;

    if (ice)
    {   ice--;
    }

    /* decrement worm strength */

    for (player = 0; player <= 3; player++)
    {   if (worm[player].lives)
        {   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].glow > 0)
            {   worm[player].glow--;
                icon(player, GLOW);
            }
            if (worm[player].armour > 0)
            {   worm[player].armour--;
                stat(player, ARMOUR);
    }   }   }

    /* 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)
{   if (level)
    {   /* create goats */
        if
        (   (!arand(FREQ_GOAT))
        &&  findempty(&x, &y, TRUE)
        )
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   createcreature(GOAT, i, x, y, 0, 0, 255);
                    break;
        }   }   }
        /* create octopi */
        if
        (   (!arand(FREQ_OCTOPUS))
        &&  findempty(&x, &y, TRUE)
        )
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   createcreature(OCTOPUS, i, x, y, 0, 0, 255);
                    break;
        }   }   }
        /* create fish */
        if
        (   (!arand(FREQ_FISH))
        &&  findempty(&x, &y, TRUE)
        )
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   createcreature(FISH, i, x, y, 0, 0, 255);
                    break;
        }   }   }
        /* create orbs */
        if (!arand(FREQ_ORB) && findempty(&x, &y, FALSE))
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   createcreature(ORB, i, x, y, (arand(1) * 2) - 1, (arand(1) * 2) - 1, 255);
                    break;
        }   }   }
        /* create dogs */
        if (!arand(FREQ_DOG) && findempty(&x, &y, FALSE))
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!(creature[i].alive))
                {   createcreature(DOG, i, x, y, 0, 0, 255);
                    break;
        }   }   }
        /* create slime */
        if (!arand(FREQ_SLIME) && findempty(&x, &y, FALSE))
            change(x, y, SLIME);
        /* grow slime */
        if (!arand(FREQ_SLIMEGROW))
        {   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 && !arand(1))
                                    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 (!arand(FREQ_TIMEBOMB) && findempty(&x, &y, FALSE))
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!(creature[i].alive))
                {   createcreature(TIMEBOMB, i, x, y, 0, 0, 255);
                    break;
    }   }   }   }

    /* create drips */
    if
    (   ( level && !arand(FREQ_DRIP))
     || (!level && leveltype == DRIP && !arand(FREQ_DRIP / BONUSSPEEDUP))
    )
    {   x = arand(FIELDX);
        y = arand(2);
        c = field[x][y];
        if (c >= FIRSTEMPTY && c <= LASTEMPTY)
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   createcreature(DRIP, i, x, y, 0, 1, (UBYTE) ((r / 16) % 4));
                    break;
    }   }   }   }
    // create penguins
    if
    (   (   ( level && !arand(FREQ_PENGUIN))
         || (!level && leveltype == PENGUIN && !arand(FREQ_PENGUIN / BONUSSPEEDUP))
        )
     && findempty(&x, &y, FALSE)
    )
    {   for (i = 0; i <= CREATURES; i++)
        {   if (!(creature[i].alive))
            {   createcreature(PENGUIN, i, x, y, 0, 0, 255);
                break;
    }   }   }
    // create birds
    if
    (  (level && !arand(FREQ_BIRD) && findempty(&x, &y, FALSE))
    )
    {   // check whether this location is far enough away from the worms
        ok = TRUE;
        for (i = 0; i <= 3; i++)
        {   if
            (   worm[i].lives
             && abs(worm[i].x - x) < DISTANCE_BIRD * 2
             && abs(worm[i].y - y) < DISTANCE_BIRD * 2
            )
            {   ok = FALSE;
                break;
        }   }
        if (ok)
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!(creature[i].alive))
                {   createcreature(BIRD, i, x, y, 0, 0, 255);
                    break;
    }   }   }   }
    /* create clouds */
    if
    (   (   ( level && !arand(FREQ_CLOUD))
         || (!level && leveltype == CLOUD && !arand(FREQ_CLOUD / BONUSSPEEDUP))
        )
     && findempty(&x, &y, FALSE)
    )
    {   for (i = 0; i <= CREATURES; i++)
        {   if (!creature[i].alive)
            {   if (x < FIELDX / 2)
                {   createcreature(CLOUD, i, x, y, -1, 0, 255);
                } else
                {   createcreature(CLOUD, i, x, y,  1, 0, 255);
                }
                break;
    }   }   }

    /* create objects */
    for (which = 0; which <= LASTOBJECT; which++)
        if (level || leveltype != TREASURE || which == TREASURE)
        {   if (!arand(object[which].freq) && findempty(&x, &y, FALSE))
                change(x, y, which);
        } elif (!arand(object[which].freq / 10) && findempty(&x, &y, FALSE))
            change(x, y, which);

    /* create teleports */
    if (!arand(FREQ_TELEPORT)
    && !teleport[level][2].alive
    && findempty(&(teleport[level][2].x), &(teleport[level][2].y), FALSE)
    && findempty(&(teleport[level][3].x), &(teleport[level][3].y), FALSE)
    && (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 void squareblast(SBYTE type, SBYTE player, UBYTE c, SBYTE x, SBYTE y, ABOOL cutter)
{   SBYTE which;
    UBYTE filler;

    if (type == HEAD && worm[player].bias)
    {   filler = DYNAMITE;
    } else filler = EMPTY;

    if (c <= LASTOBJECT)
    {   if (!cutter)
        {   change(x, y, filler);
    }   }
    elif ((c >= FIRSTTAIL && c <= LASTTAIL) || c == WOOD || c == SLIME)
    {   change(x, y, filler);
    } elif (c >= FIRSTHEAD && c <= LASTHEAD)
    {   if (!cutter && (type != HEAD || player != c - FIRSTHEAD))
        {   if (worm[c - FIRSTHEAD].armour == 0)
            {   worm[c - FIRSTHEAD].cause = BOMB;
                worm[c - FIRSTHEAD].alive = FALSE;
                if (type == HEAD)
                {   worm[c - FIRSTHEAD].victor = player;
                } else
                {   worm[c - FIRSTHEAD].victor = -1;
            }   }
            else
            {   effect(FXUSE_ARMOUR);
    }   }   }
    elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
    {   which = whichcreature(x, y, MISSILE, 255);
        if (!cutter)
        {   if (type == HEAD)
            {   if (player != c - FIRSTMISSILE)
                {   wormkillcreature(player, which);
                    change(x, y, filler);
        }   }   }
        else
        {   creature[which].alive = FALSE;
            change(x, y, filler);
    }	}
    elif (c >= FIRSTDRIP && c <= LASTDRIP)
    {   if (!cutter)
        {   which = whichcreature(x, y, DRIP, 255);
            if (type == HEAD)
            {   wormkillcreature(player, which);
            } else
            {   creature[which].alive = FALSE;
            }
            change(x, y, filler);
    }   }
    elif
    (   c == CLOUD
     || c == DOG
     || c == FISH
     || c == FRAGMENT
     || c == GOAT
     || c == OCTOPUS
     || c == ORB
     || c == PENGUIN
     || c == WHIRLWIND
    )
    {   if (!cutter)
        {   which = whichcreature(x, y, c, 255);
            if (type == HEAD)
            {   wormkillcreature(player, which);
            } else
            {   creature[which].alive = FALSE;
            }
            change(x, y, filler);
}   }   }

void timeloop(void)
{   AUTO    TEXT  timedisplay[5] = {"#:##"};
    AUTO    UBYTE i;
    AUTO    SBYTE y;
    PERSIST ABOOL expired        = FALSE;

    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 (!expired)
        {   effect(FXSIREN);
            say("Time expired!", WHITE);
            expired = TRUE;

            // create otters
            for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   for (y = 0; y <= FIELDY - 1; y++)
                    {   if (field[0][y] >= FIRSTEMPTY && field[0][y] <= LASTEMPTY)
                        {   createcreature(OTTER, i, 0,      y, 0,  1, 255);
                            break;
                    }   }
                    break;
            }   }
            for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   for (y = FIELDY - 1; y >= 0; y--)
                    {   if (field[FIELDX][y] >= FIRSTEMPTY && field[FIELDX][y] <= LASTEMPTY)
                        {   createcreature(OTTER, i, FIELDX, y, 0, -1, 255);
                            break;
                    }   }
                    break;
    }   }   }   }
    else
    {   expired = FALSE;
        say(timedisplay, WHITE);
}   }

void train(SCANCODE scancode)
{   SBYTE x, y;

    switch(scancode)
    {
    case HELP:
        trainer = !trainer;
    break;
    case NUMERICSLASH:
        /* Complete the level. */
        if (trainer)
        {   trainer = FALSE;
            verynewlevel();
        }
    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].armour = ARMOURLIMIT;
                stat(1, ARMOUR);
                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);
                worm[1].encloser = TRUE;
                icon(1, ENCLOSER);
                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;
    SBYTE distance,
          i, j,
          x, y;
    UBYTE c;

    if (!worm[player].ammo)
    {   stat(player, BONUS);
	if (worm[player].speed == FAST)
            distance = DISTANCE_FAST;
	elif (worm[player].speed == NORMAL)
           distance = DISTANCE_NORMAL;
        elif (worm[player].speed == SLOW)
           distance = DISTANCE_SLOW;
        else
        {  // assert(worm[player].speed == VERYSLOW);
           distance = DISTANCE_VERYSLOW;
        }

	/* 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));
            c = field[x][y];
            if (c == TELEPORT)
	    {   i = whichteleport(x, y);
	        if (!blocked(i, worm[player].deltax, worm[player].deltay))
		    ok = TRUE;
	    }
            if (ok || ((c < STONE || c > GOAT) && c != 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].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. */

        finished = FALSE;
        while (!finished)
        {   finished = TRUE;
            for (i = 0; i <= 8; i++)
            {   if (bullet[i].alive)
                {   if (bullet[i].visible)
                    {   if (worm[player].remnants)
                            change(x, y, FIRSTGLOW + player);
                        else change(x, y, EMPTY);
                    }

                    finished = FALSE;
                    bullet[i].visible = TRUE;
                    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;
                    c = 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].armour == 0)
                        {   worm[player].cause = FIRSTFIRE + player;
                            worm[player].victor = -1;
                            worm[player].alive = FALSE;
                    }   }
                    elif (c >= FIRSTHEAD && c <= LASTHEAD)
                    {   if (worm[c - FIRSTHEAD].armour == 0)
                        {   worm[c - FIRSTHEAD].cause = FIRSTFIRE + player;
                            worm[c - FIRSTHEAD].victor = player;
                            worm[c - FIRSTHEAD].alive = FALSE;
                        } else effect(FXUSE_ARMOUR);
                        bullet[i].alive = FALSE;
                    } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR)
                    {   if (player != c - FIRSTPROTECTOR)
                        {   effect(FXUSE_PROTECTOR);
                            bullet[i].alive = FALSE;
                        } else bullet[i].visible = FALSE;
                    } elif (c >= FIRSTLETTER && c <= LASTLETTER)
                    {   getnumber(player);
                        lettered = TRUE;
                    } elif (c >= FIRSTGRAVE && c <= LASTGRAVE)
                    {   bullet[i].alive = FALSE;
                    } elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
                    {   if (player != c - FIRSTMISSILE)
                            creature[whichcreature(x, y, MISSILE, 255)].alive = FALSE;
                        else bullet[i].visible = FALSE;
                    } elif (c >= FIRSTDRIP && c <= LASTDRIP)
                    {   j = whichcreature(x, y, DRIP, 255);
                        creature[j].alive = FALSE;
                    } else
                    {   switch(c)
                        {
                        case BOMB:
                            // sets it off, for your benefit
                            bullet[i].alive = FALSE;
                            bombblast(HEAD, player, x, y);
                        break;
                        case SLIME:
                        case WOOD:
                            // destroys one layer of it
                            bullet[i].alive = FALSE;
                            change(x, y, EMPTY);
                        break;
                        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:
                            bullet[i].alive = FALSE;
                        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;
                            }
                        break;
                        case CLOUD:
                        case DOG:
                        case FISH:
                        case FRAGMENT:
                        case GOAT:
                        case OCTOPUS:
                        case ORB:
                        case PENGUIN:
                        case WHIRLWIND:
                            bullet[i].alive = FALSE;
                            j = whichcreature(x, y, c, 255);
                            wormkillcreature(player, j);
                            change(x, y, EMPTY);
                        break;
                        case TIMEBOMB:
                            bullet[i].alive = FALSE;
                            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 (lettered)
            putnumber();
        clearkybd();
}   }

/* 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)
{   SBYTE bestx = 0, besty = 0, // to avoid spurious warnings
          dirx, diry, i, j, thisprot, x, y, index1, index2;
    SWORD bestgood, good;
    UBYTE c;

   /*  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.

    Things which slow the worm down are doubly feared; this is to avoid
    endless ramming situations. */

    if (worm[player].control == AMIGA)
    {   if (!arand(50))
            wormqueue(player, arand(2) - 1, arand(2) - 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 = POINTS_LETTER;
                elif (c >= FIRSTHEAD && c <= LASTHEAD)
                    good = -(PAIN_HEAD);
                elif (c <= LASTOBJECT)
                    good = (SWORD) object[c].score;
                elif (c == FIRSTPROTECTOR + player)
                    good = POINTS_EMPTY;
                elif (c >= FIRSTGLOW && c <= LASTGLOW)
                {   if (player == c - FIRSTGLOW)
		        good = -1;
                    else good = -(PAIN_GLOW);
                } elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
                {   if (player == c - FIRSTMISSILE)
                        good = 0;
                    else good = -(PAIN_MISSILE);
                } elif (c >= FIRSTDRIP && c <= LASTDRIP)
                {   if (player == c - FIRSTDRIP)
                        good = SCORE_DRIP;
                    else good = -(PAIN_DRIP);
                } elif (c >= FIRSTGRAVE && c <= LASTGRAVE)
                {   good = POINTS_GRAVE;
                } elif (c >= FIRSTTAIL && c <= LASTTAIL)
                {   if (worm[player].armour > 10)
                        if (player != c - FIRSTTAIL)
                            good = POINTS_TURNSILVER;
                        else good = 0;
                    elif (player == c - FIRSTTAIL)
                        good = -(PAIN_FRIENDLYTAIL);
                    else good = -(PAIN_ENEMYTAIL);
                } else switch(c)
                {
                case GOLD:
                    good = POINTS_GOLD;
                break;
                case SILVER:
                    good = POINTS_SILVER;
                break;
                case EMPTY:
                    good = POINTS_EMPTY;
                break;
                case SLIME:
                    if (worm[player].armour > 0)
                        good = 0;
                    else good = -(PAIN_SLIME);
                break;
                case WOOD:
                    if (worm[player].armour > 0)
                        good = 0;
                    else good = -(PAIN_WOOD);
                break;
                case STONE:
                    good = -(PAIN_STONE * 2);
                break;
                case METAL:
                    good = -(PAIN_METAL * 2);
                break;
                case GOAT:
                    good = -(PAIN_GOAT * 2);
                break;
                case CLOUD:
                    good = -(PAIN_CLOUD);
                break;
                case DOG:
                    good = -(PAIN_DOG);
                break;
                case FISH:
                    good = -(PAIN_FISH);
                break;
                case FRAGMENT:
                    good = -(PAIN_FRAGMENT);
                break;
                case OCTOPUS:
                    good = -(PAIN_OCTOPUS * 2);
                break;
                case ORB:
                    good = -(PAIN_ORB);
                break;
                case WHIRLWIND:
                    good = -(PAIN_WHIRLWIND);
                break;
                default:
                    good = 0;
                break;
                }
                if (good > bestgood)
                {   bestx = dirx;
                    besty = diry;
                    bestgood = good;
            }   }
            if (bestgood < -2 && !arand(1))
            {   // turn in any of the 8 directions, or fire
                wormqueue
                (   player,
                    (SBYTE) ((arand(1) * 2) - 1),
                    (SBYTE) ((arand(1) * 2) - 1)
                );
            }
            elif (bestgood < -1 && !arand(1))
                wormqueue(player, 0, 0);
            elif (bestgood < 0 && !arand(5))
                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 (first && worm[2].lives && worm[2].control == JOYSTICK)
        {   first = FALSE;
            /* Yes, this is defensive programming :-(
            Because the first time the blue worm has joystick control, a
            bizarre firebutton event seems to occur. */
        } else
        {   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 */

    if (worm[player].last == FIRSTTAIL + player)
    {   field[worm[player].x][worm[player].y] = FIRSTTAIL + player;
        if (!thick)
        {   index1 =       worm[player].olddeltax + 1 + (      (worm[player].olddeltay + 1) * 3);
            index2 = bsign(worm[player].deltax)   + 1 + ((bsign(worm[player].deltay)   + 1) * 3);
            drawtail(worm[player].x, worm[player].y, eachtail[player][0][index1][index2]);
        } else
        {   draw(worm[player].x, worm[player].y, FIRSTTAIL + player);
    }   }
    elif (worm[player].last == FIRSTGLOW + player)
    {   field[worm[player].x][worm[player].y] = FIRSTGLOW + player;
        if (!thick)
        {   index1 =       worm[player].olddeltax + 1 + (      (worm[player].olddeltay + 1) * 3);
            index2 = bsign(worm[player].deltax)   + 1 + ((bsign(worm[player].deltay)   + 1) * 3);
            drawtail(worm[player].x, worm[player].y, eachtail[player][1][index1][index2]);
        } else
        {   draw(worm[player].x, worm[player].y, FIRSTGLOW + player);
    }   }
    else
    {   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].glow)
    {   worm[player].last = FIRSTGLOW + 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);
    worm[player].olddeltax = worm[player].deltax;
    worm[player].olddeltay = worm[player].deltay;

    /*  check for enclosure
        #####
        #...#
        #...# . = interior
        #...# # = tail
        ####! ! = head */

    enclosed = FALSE;
    for (i = 2; i <= 7; i++) // for each size of interior
    {   for (j = 0; j <= 3; j++) // four times, once for each direction
        {   checkrectangle(j, player, i, i);
            if (worm[player].encloser)
            {   checkrectangle(j, player, i, i + 1);
                checkrectangle(j, player, i + 1, i);
    }   }   }

    // 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 * DISTANCE_NOSE;
                protector[player][i].rely = worm[player].deltay * DISTANCE_NOSE;
                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 * (DISTANCE_NOSE - 1);
                    elif (worm[player].position == 1)
                        protector[player][i].rely = worm[player].deltay * (DISTANCE_NOSE - 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);
    // 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);
        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);
        }   }
        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);
        }   }
        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);
        }   }
        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;
        }   }
        squareblast(HEAD, player, field[x][y], x, y, TRUE);
}   }

MODULE void protcol(SBYTE player, SBYTE x, SBYTE y, SBYTE thisprot)
{   UBYTE c = field[x][y];
    SBYTE i;

    if (c >= FIRSTHEAD && c <= LASTHEAD)
    {   protworm(x, y, player, c - FIRSTHEAD);
    } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR)
    {   protprot(x, y, player, c - FIRSTPROTECTOR);
    } elif (c >= FIRSTTAIL && c <= LASTTAIL)
    {   if (player == c - FIRSTTAIL)
        {   protector[player][thisprot].visible = FALSE;
    }   }
    elif
    (   c == STONE
     || c == WOOD
     || c == METAL
     || c == TIMEBOMB
     || c == TELEPORT
     || c == FIRSTGLOW + player
    )
    {   protector[player][thisprot].visible = FALSE;
    } elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
    {   i = whichcreature(x, y, MISSILE, 255);
        protcreature(player, i);
    } elif (c >= FIRSTDRIP && c <= LASTDRIP)
    {   i = whichcreature(x, y, DRIP, 255);
        protcreature(player, i);
    } elif
    (   c == BIRD
     || c == CLOUD
     || c == DOG
     || c == FRAGMENT
     || c == ORB
     || c == PENGUIN
     || c == WHIRLWIND
    )
    {   i = whichcreature(x, y, c, 255);
        protcreature(player, i);
    } else
    {   bothcol(player, x, y);
}   }

MODULE void bothcol(SBYTE player, SBYTE x, SBYTE y)
{   UBYTE c = field[x][y];

    if (c >= FIRSTLETTER && c <= LASTLETTER)
    {   getnumber(player);
        putnumber();
    } elif (c <= LASTOBJECT)
    {   wormscore(player, wormobject(player, x, y));
    } elif (c >= FIRSTGRAVE && c <= LASTGRAVE)
    {   effect(FXGET_SKULL);
        wormscore(player, POINTS_GRAVE);
        worm[player].bias += worm[c - FIRSTGRAVE].bias;

                if (worm[player].bias > 0)
                {   if (worm[player].bias > BIASLIMIT)
                        worm[player].bias = BIASLIMIT;
                    stat(player, BIAS);
                    worm[c - FIRSTGRAVE].bias = 0;
                    stat(c - FIRSTGRAVE, BIAS);
                }
                worm[player].multi *= worm[c - FIRSTGRAVE].multi;
                if (worm[player].multi > 1)
                {   if (worm[player].multi > MULTILIMIT)
                        worm[player].multi = MULTILIMIT;
                }
                worm[player].power += worm[c - FIRSTGRAVE].power;
                if (worm[player].power > 1)
                {   if (worm[player].power > POWERLIMIT)
                        worm[player].power = POWERLIMIT;
                    stat(player, POWER);
                    worm[c - FIRSTGRAVE].power = 0;
                    stat(c - FIRSTGRAVE, POWER);
                }
                worm[player].ammo += worm[c - FIRSTGRAVE].ammo;
                if (worm[player].ammo > 0)
                {   if (worm[player].ammo > AMMOLIMIT)
                        worm[player].ammo = AMMOLIMIT;
                    stat(player, AMMO);
                    worm[c - FIRSTGRAVE].ammo = 0;
                    stat(c - FIRSTGRAVE, AMMO);
                }
                worm[player].armour += worm[c - FIRSTGRAVE].armour;
                if (worm[player].armour > 0)
                {   if (worm[player].armour > ARMOURLIMIT)
                        worm[player].armour = ARMOURLIMIT;
                    stat(player, ARMOUR);
                    worm[c - FIRSTGRAVE].armour = 0;
                    stat(c - FIRSTGRAVE, ARMOUR);
                }
                if (worm[c - FIRSTGRAVE].nitro)
                {   worm[player].nitro = TRUE;
                    stat(player, NITRO);
                    worm[c - FIRSTGRAVE].nitro = FALSE;
                    worm[c - FIRSTGRAVE].speed = NORMAL;
                    stat(c - FIRSTGRAVE, NITRO);
                }
                if (worm[c - FIRSTGRAVE].affixer)
                {   worm[player].affixer = TRUE;
                    icon(player, AFFIXER);
                    worm[c - FIRSTGRAVE].affixer = FALSE;
                    icon(c - FIRSTGRAVE, AFFIXER);
                }
                if (worm[c - FIRSTGRAVE].remnants)
                {   worm[player].remnants = TRUE;
                    icon(player, REMNANTS);
                    worm[c - FIRSTGRAVE].remnants = FALSE;
                    icon(c - FIRSTGRAVE, REMNANTS);
                }
                if (worm[c - FIRSTGRAVE].sideshot)
                {   worm[player].sideshot = TRUE;
                    icon(player, SIDESHOT);
                    worm[c - FIRSTGRAVE].sideshot = FALSE;
                    icon(c - FIRSTGRAVE, SIDESHOT);
                }
                if (worm[c - FIRSTGRAVE].pusher)
                {   worm[player].pusher = TRUE;
                    icon(player, PUSHER);
                    worm[c - FIRSTGRAVE].pusher = FALSE;
                    icon(c - FIRSTGRAVE, PUSHER);
                }
                if (worm[c - FIRSTGRAVE].encloser)
                {   worm[player].encloser = TRUE;
                    icon(player, ENCLOSER);
                    worm[c - FIRSTGRAVE].encloser = FALSE;
                    icon(c - FIRSTGRAVE, ENCLOSER);
                }
                if (worm[c - FIRSTGRAVE].cutter)
                {   worm[player].cutter += worm[c - FIRSTGRAVE].cutter;
                    icon(player, CUTTER);
                    worm[c - FIRSTGRAVE].cutter = 0;
                    icon(c - FIRSTGRAVE, CUTTER);
                }

    } else
    {   switch(c)
        {
        case EMPTY:
            wormscore(player, POINTS_EMPTY);
        break;
        case SILVER:
            wormscore(player, POINTS_SILVER);
        break;
        case GOLD:
            wormscore(player, POINTS_GOLD);
        break;
        case DYNAMITE:
            effect(FXUSE_BOMB);
            banging = TRUE;
            bangdynamite(x, y, player);
            wormscore(player, worm[player].dynamitescore);
            worm[player].dynamitescore = 0;
        break;
        default:
        break;
}   }   } 

AGLOBAL void wormscore(SBYTE player, ULONG 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 __inline change(SBYTE x, SBYTE y, UBYTE image)
{   // assert(valid(x, y));
	field[x][y] = image;
    draw(x, y, image);
}

/* 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;
}

MODULE void wormkillcreature(UBYTE player, UBYTE which)
{   /* This is defensive programming. Sometimes squareblast() finds an
       orb on the field for which there is no corresponding creature
       (ie. an 'orphan orb'. whichcreature() on such a square will
       return 255. */

    if (which != 255)
    {   wormscore(player, creature[which].score);
        creature[which].alive = FALSE;
        if (worm[player].bias)
        {   worm[player].lives++;
            stat(player, LIFE);
        }
        if (creature[which].species == FRAGMENT)
        {   effect(FXDEATH_FRAGMENT);
        } elif (creature[which].species == DRIP)
        {   effect(FXGET_DRIP);
}   }   }

MODULE void wormworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2)
{   if (worm[which1].armour == 0 && worm[which2].armour == 0)
    {   /* 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].armour > 0 && worm[which2].armour == 0)
    {   /* 1st worm lives, 2nd worm dies  */
        worm[which2].cause  = FIRSTHEAD + which1;
        worm[which2].alive  = FALSE;
        worm[which2].victor = which1;
    } elif (worm[which1].armour == 0 && worm[which2].armour > 0)
    {   /* 1st worm dies, 2nd worm lives */
        worm[which1].cause  = FIRSTHEAD + which2;
        worm[which1].alive  = FALSE;
        worm[which1].victor = which2;
}   }
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].armour == 0)
        {   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 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, generated = 0;
    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:
        getnumber(player);
        change(letterx, lettery, FIRSTLETTER + number - 1);
        updatearrow(lettery);
    break;
    case AMMO:
        effect(FXGET_AMMO);
        worm[player].ammo += arand(4) + 2; /* 2-6 bullets */
        stat(player, AMMO);
    break;
    case ARMOUR:
        effect(FXGET_OBJECT);
        worm[player].armour += ADD_ARMOUR + arand(RAND_ARMOUR);
        stat(player, ARMOUR);
    break;
    case NITRO:
        effect(FXGET_NITRO);
        worm[player].nitro = TRUE;
        stat(player, NITRO);
    break;
    case BOMB:
        if (worm[player].glow == 0)
            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 != MISSILE
                     && creature[i].species != DRIP
                    )
                 || creature[i].type != player
                )
                {   wormkillcreature(player, i);
                    change(creature[i].x, creature[i].y, EMPTY);
        }   }   }
        for (i = 0; i <= 3; i++)
            if (player != i && worm[i].armour == 0)
            {   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);
    break;
    case PROTECTOR:
        // create protector
        done = FALSE;
        for (i = 0; i <= PROTECTORS; i++)
        {   if (!protector[player][i].alive && !done)
            {   do
                {   protector[player][i].relx = (arand(1) * 2) - 1;
                    protector[player][i].rely = (arand(1) * 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)
                        ) // if we can find an area without a preexisting protector
                        {   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:
        for (i = 0; i <= CREATURES; i++)
        {   if (!creature[i].alive)
            {   createcreature(MISSILE, i, worm[player].x, worm[player].y, 0, 0, player);
                break;
        }   }
    break;
    case LIFE:
        effect(FXGET_OBJECT);
        worm[player].lives += arand(4) + 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 += ADD_ARMOUR + arand(RAND_ARMOUR);
        stat(player, BIAS);
    break;
    case ICE:
        effect(FXGET_OBJECT);
        ice += ADD_ICE + arand(RAND_ICE);
    break;
    case GROWER:
        effect(FXGET_GROWER);

        /* grow dynamite */
        for (x = 0; x <= FIELDX; x++)
            for (y = 0; y <= FIELDY; y++)
                if (field[x][y] == 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] = TEMPDYNAMITE;
        /* 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] == TEMPDYNAMITE)
                                    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] == TEMPDYNAMITE || 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;
                case TEMPDYNAMITE:
                    change(x, y, DYNAMITE);
                break;
                default:
                break;
                }
    break;
    case TREASURE:
        treasurer = player;
        if (level)
        {   secondsperlevel = 0;
            leveltype = arand(2);
            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 += ADD_TREASURE + arand(RAND_TREASURE);
        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] >= FIRSTGLOW && field[x][y] <= LASTGLOW && field[x][y] != FIRSTGLOW + player)
                        change(x, y, FIRSTGLOW + 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 += arand(1) + 1;
        if (level >= levels)
            level = levels; // fixed?
        for (i = 0; i <= LETTERS; i++)
            letters[i] = TRUE;
    break;
    case CLOCK:
        effect(FXGET_OBJECT);
        secondsperlevel += ADD_CLOCK + arand(RAND_CLOCK);
        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:
        effect(FXGET_PULSE);
        for (i = 0; i <= CREATURES; i++)
        {   if (!creature[i].alive && generated <= 7)
            {   switch (generated)
                {
                case 0:
                    xx = 0;
                    yy = -1;
                break;
                case 1:
                    xx = 1;
                    yy = -1;
                break;
                case 2:
                    xx = 1;
                    yy = 0;
                break;
                case 3:
                    xx = 1;
                    yy = 1;
                break;
                case 4:
                    xx = 0;
                    yy = 1;
                break;
                case 5:
                    xx = -1;
                    yy = 1;
                break;
                case 6:
                    xx = -1;
                    yy = 0;
                break;
                case 7:
                    xx = -1;
                    yy = -1;
                break;
                default:
                break;
                }

                generated++;
                if (xx != worm[player].deltax || yy != worm[player].deltay)
                {   d = field[x + xx][y + yy];
                    if ((d >= FIRSTEMPTY && d <= LASTEMPTY) || (d >= FIRSTTAIL && d <= LASTTAIL) || d <= LASTOBJECT)
                    {   createcreature(FRAGMENT, i, x + xx, y + yy, xx, yy, 255);
        }   }   }   }
    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 += ADD_CUTTER + arand(RAND_CUTTER);
        icon(player, CUTTER);
    break;
    case CYCLONE:
        /* create whirlwind */
        x = arand(FIELDX);
        y = arand(FIELDY - 5) + 5;
        d = field[x][y];
        if (d >= FIRSTEMPTY && d <= LASTEMPTY)
        {   for (i = 0; i <= CREATURES; i++)
            {   if (!creature[i].alive)
                {   createcreature(WHIRLWIND, i, x, y, 0, 0, 255);
                    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 == FISH
                                 || 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];
                    if (d >= FIRSTMISSILE && d <= LASTMISSILE)
                    {   i = whichcreature(xx, yy, MISSILE, 255);
                        if (player != creature[i].type)
                        {   wormkillcreature(player, i);
                            change(xx, yy, EMPTY);
                        } else drawmissile(xx, yy, i);
                    } elif (d >= FIRSTDRIP && d <= LASTDRIP)
                    {   i = whichcreature(xx, yy, DRIP, 255);
                        if (player != creature[i].type)
                        {   wormkillcreature(player, i);
                            change(xx, yy, EMPTY);
                        } else draw(xx, yy, FIRSTDRIP + player);
                    } else
                    {   switch(d)
                        {
                        case CLOUD:
                        case DOG:
                        case FRAGMENT:
                        case GOAT:
                        case OCTOPUS:
                        case ORB:
                        case PENGUIN:
                        case WHIRLWIND:
                            wormkillcreature(player, whichcreature(xx, yy, d, 255));
                            change(xx, yy, EMPTY);
                        break;
                        default:
                            /* eg. tail */
                            change(xx, yy, EMPTY);
                        break;
        }   }   }   }   }
    break;
    case PUSHER:
        effect(FXGET_OBJECT);
        worm[player].pusher = TRUE;
        icon(player, PUSHER);
    break;
    case GLOW:
        effect(FXGET_OBJECT);
        worm[player].glow += ADD_GLOW + arand(RAND_GLOW);
        if (worm[player].glow > GLOWLIMIT)
            worm[player].glow = GLOWLIMIT;
                icon(player, GLOW);
    break;
    case ENCLOSER:
        effect(FXGET_OBJECT);
        worm[player].encloser = TRUE;
        icon(player, ENCLOSER);
    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);
                createcreature(MISSILE, i, xx, yy, 0, 0, player);
            }
	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. */

    SBYTE x = -4, y = (player * 10) + 7;

    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, y + 1, SIDESHOT);
        else draw(x, y + 1, BLACKENED);
    break;
    case CUTTER:
        if (worm[player].cutter)
        {   if (worm[player].cutter < 10)
            {   if ((r % 4) <= 1)
                {   draw(x + 1, y + 1, CUTTER);
                } else draw(x + 1, y + 1, BLACKENED);
            } else draw(x + 1, y + 1, CUTTER);
        } else draw(x + 1, y + 1, BLACKENED);
    break;
    case ENCLOSER:
        if (worm[player].encloser)
            draw(x + 2, y + 1, ENCLOSER);
        else draw(x + 2, y + 1, BLACKENED);
    break;
    default:
    break;
}   }

MODULE void wormcol(SBYTE player, SBYTE x, SBYTE y)
{   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 // oops, you've fucked up! :-( Timebomb explodes!
            {   if (worm[player].glow == 0)
                    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 += creature[i].score;
    }   }
    elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR)
        protworm(x, y, c - FIRSTPROTECTOR, player);
    elif (c >= FIRSTMISSILE && c <= LASTMISSILE)
    {   i = whichcreature(x, y, c, 255);
        wormcreature(player, i);
    } elif (c >= FIRSTDRIP && c <= LASTDRIP)
    {   i = whichcreature(x, y, DRIP, 255);
        wormcreature(player, i);
    } elif
    (   c == STONE
     || c == METAL
     || c == WOOD
     || c == FISH
     || c == GOAT
     || c == OCTOPUS
     || (c >= FIRSTTAIL && c <= LASTTAIL)
    ) // if you've hit something that is pushable
    {   flag = TRUE; // flag is whether you deserve to die
        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)
                // if you're pushing the square into a square which
                // has an object or is empty/silver/gold
                {   flag = FALSE; // then you don't deserve to die
                    if
                    (   c == FISH
                     || 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 // if pushing off the field edges
            {   flag = FALSE; // then you don't deserve to die
                // kill the creature
                if
                (   c == FISH
                 || c == GOAT
                 || c == OCTOPUS
                )
                {   i = whichcreature(x, y, c, 255);
                    wormkillcreature(player, i);
        }   }   }
        if (flag) // if we deserve to die
        {   if (c >= FIRSTTAIL && c <= LASTTAIL) // if we're pushing/hitting tail
            {   if (worm[player].armour > 0)
                {   effect(FXUSE_ARMOUR);
                    if (players > 1)
                    {   if (player == c - FIRSTTAIL)
                        {   score += POINTS_TURNSILVER;
                            worm[player].last = SILVER;
                        } else
                        {   score += POINTS_TURNGOLD;
                            worm[player].last = GOLD;
                }   }   }
                elif (!enclosed)
                {   worm[player].cause = c;
                    worm[player].alive = FALSE;
                    worm[player].victor = c - FIRSTTAIL;
            }   }
            elif
            (   c == FISH
             || c == GOAT
             || c == OCTOPUS
            )
            {   i = whichcreature(x, y, c, 255);
                wormcreature(player, i);
            } else
            {   worm[player].cause = c;
                worm[player].alive = FALSE;
                worm[player].victor = -1;
                if (c != WOOD)
                {   ramming(player);
    }   }   }   }
    elif (c == SLIME)
    {   if (worm[player].armour == 0)
        {   worm[player].cause = c;
            worm[player].alive = FALSE;
            worm[player].victor = -1;
    }   }
    elif
    (   c == BIRD
     || c == CLOUD
     || c == DOG
     || c == FRAGMENT
     || c == ORB
     || c == PENGUIN
     || c == WHIRLWIND
    )
    {   i = whichcreature(x, y, c, 255);
        wormcreature(player, i);
    } 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);
            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 >= FIRSTGLOW && c <= LASTGLOW)
    {   if (player != c - FIRSTGLOW && worm[player].armour == 0)
        {   worm[player].cause = GLOW;
            worm[player].victor = c - FIRSTGLOW;
            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].glow == 0)
        {   draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]);
            worm[player].flashed = FALSE;
        } else
        {   if (worm[player].glow < 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, FIRSTPAIN + player);
}

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] >= FIRSTGRAVE
                 && field[worm[i].x][worm[i].y] <= LASTGRAVE
        )   )   )
        {   if (var == -1)
                var = i;
            else var = -2;
    }   }
    if (lettery == arrowy)
        if (var == -1)
            var = FIRSTLETTER + number - 1;
        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, FIRSTGRAVE + var);
}   }

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 += POINTS_DYNAMITE;
                    infector[xx][yy] = player;
}   }   }   }   }

MODULE void octopusfire(UBYTE which)
{   UBYTE i;
    SBYTE x, y, deltax, deltay;

    for (i = 0; i <= CREATURES; i++)
    {   if (!creature[i].alive)
        {   switch(creature[which].dir)
            {
            case 0:
                deltax = 0;
                deltay = -1;
            break;
            case 1:
                deltax = 1;
                deltay = -1;
            break;
            case 2:
                deltax = 1;
                deltay = 0;
            break;
            case 3:
                deltax = 1;
                deltay = 1;
            break;
            case 4:
                deltax = 0;
                deltay = 1;
            break;
            case 5:
                deltax = -1;
                deltay = 1;
            break;
            case 6:
                deltax = -1;
                deltay = 0;
            break;
            case 7:
                deltax = -1;
                deltay = -1;
            break;
            default:
            break;
            }
            x = creature[which].x + deltax;
            y = creature[which].y + deltay;
            if (valid(x, y) && field[x][y] >= FIRSTEMPTY && field[x][y] <= LASTEMPTY)
            {   effect(FXGET_PULSE);
                createcreature(FRAGMENT, i, x, y, deltax, deltay, 255);
                goto out;
    }   }   }

out:
    if (++creature[which].dir == 8)
    {   creature[which].dir = -1;
}   }

MODULE void createcreature(UBYTE species,
                           UBYTE which,
                           SBYTE x,
                           SBYTE y,
                           SBYTE deltax,
                           SBYTE deltay,
                           UBYTE player)
{   switch(species)
    {
    case FRAGMENT:
        creature[which].speed    = SPEED_FRAGMENT;
        creature[which].score    = SCORE_FRAGMENT;
        creature[which].hardness = HARDNESS_FRAGMENT;
        creature[which].visible  = TRUE;
        creature[which].last     = EMPTY;
        change(x, y, FRAGMENT);
    break;
    case OCTOPUS:
        creature[which].speed    = SPEED_OCTOPUS;
        creature[which].score    = SCORE_OCTOPUS;
        creature[which].hardness = HARDNESS_OCTOPUS;
        creature[which].visible  = TRUE;
        creature[which].last     = field[x][y];
        creature[which].dir      = -1;
        change(x, y, OCTOPUS);
    break;
    case FISH:
        creature[which].speed    = SPEED_FISH;
        creature[which].score    = SCORE_FISH;
        creature[which].hardness = HARDNESS_FISH;
        creature[which].visible  = TRUE;
        creature[which].last     =
        creature[which].oldlast  = field[x][y];
        change(x, y, FISH);
    break;
    case GOAT:
        effect(FXBORN_GOAT);
        creature[which].speed    = SPEED_GOAT;
        creature[which].score    = SCORE_GOAT;
        creature[which].hardness = HARDNESS_GOAT;
        creature[which].visible  = TRUE;
        creature[which].last     =
        creature[which].oldlast  = field[x][y];
        change(x, y, GOAT);
    break;
    case DOG:
        creature[which].speed    = SPEED_DOG;
        creature[which].score    = SCORE_DOG;
        creature[which].hardness = HARDNESS_DOG;
        creature[which].visible  = TRUE;
        creature[which].last     = EMPTY;
        creature[which].pos      = -1;
        creature[which].dormant  = DORMANT; /* dormant */
        field[x][y] = DOG;
        draw(x, y, DOGDORMANT);
    break;
    case ORB:
        effect(FXBORN_ORB);
        creature[which].speed    = SPEED_ORB;
        creature[which].score    = SCORE_ORB;
        creature[which].hardness = HARDNESS_ORB;
        creature[which].visible  = TRUE;
        creature[which].last     = EMPTY;
        creature[which].multi    = 1;
        change(x, y, ORB);
    break;
    case CLOUD:
        creature[which].speed    = SPEED_CLOUD;
        creature[which].score    = SCORE_CLOUD;
        creature[which].hardness = HARDNESS_CLOUD;
        creature[which].visible  = TRUE;
        creature[which].last     = EMPTY;
        change(x, y, CLOUD);
    break;
    case PENGUIN:
        effect(FXBORN_PENGUIN);
        creature[which].speed    = SPEED_PENGUIN;
        creature[which].score    = SCORE_PENGUIN;
        creature[which].hardness = HARDNESS_PENGUIN;
        creature[which].visible  = TRUE;
        creature[which].last     = EMPTY;
        change(x, y, PENGUIN);
    break;
    case TIMEBOMB:
        creature[which].speed    = SPEED_TIMEBOMB;
        creature[which].score    = SCORE_TIMEBOMB;
        creature[which].hardness = HARDNESS_TIMEBOMB;
        creature[which].last     = EMPTY;
        creature[which].time     = 10;
        creature[which].visible  = TRUE;
        field[x][y] = TIMEBOMB;
        draw(x, y, ZERO + 9);
    break;
    case DRIP:
        effect(FXBORN_DRIP);
        creature[which].speed    = SPEED_DRIP;
        creature[which].score    = SCORE_DRIP;
        creature[which].hardness = HARDNESS_DRIP;
        creature[which].last     = EMPTY;
        creature[which].type     = player;
        creature[which].visible  = TRUE;
        change(x, y, FIRSTDRIP + creature[which].type);
    break;
    case WHIRLWIND:
        effect(FXGET_CYCLONE);
        creature[which].speed    = SPEED_WHIRLWIND;
        creature[which].score    = SCORE_WHIRLWIND;
        creature[which].hardness = HARDNESS_WHIRLWIND;
        creature[which].last     = EMPTY;
        creature[which].visible  = TRUE;
        change(x, y, WHIRLWIND);
    break;
    case MISSILE:
        effect(FXBORN_MISSILE);
        creature[which].score    = SCORE_MISSILE;
        creature[which].speed    = SPEED_MISSILE;
        creature[which].hardness = HARDNESS_MISSILE;
        creature[which].visible  = FALSE;
        creature[which].last     = EMPTY;
        creature[which].type     = player;
        creature[which].frame    = 0;
    break;
    case BIRD:
        creature[which].speed    = SPEED_BIRD;
        creature[which].score    = SCORE_BIRD;
        creature[which].hardness = HARDNESS_BIRD;
        creature[which].visible  = TRUE;
        creature[which].last     = EMPTY;
        creature[which].frame    = 0;
        creature[which].dir      = 1;
        creature[which].type     = 255;
        change(x, y, BIRD);
    break;
    case OTTER:
        creature[which].speed    = SPEED_OTTER;
        creature[which].score    = SCORE_OTTER;
        creature[which].hardness = HARDNESS_OTTER;
        creature[which].visible  = TRUE;
        creature[which].last     = STONE; // should really check whether it should be dynamite instead
        if (x == 0)
        {   creature[which].going    = OTTER_DOWN;
            creature[which].journey  = OTTER_RIGHT;
        } else
        {   creature[which].going    = OTTER_UP;
            creature[which].journey  = OTTER_LEFT;
        }
        field[x][y] = OTTER;
        draw(x, y, OTTER);
    default:
    break;
    }

    creature[which].alive   = TRUE;
    creature[which].x       = x;
    creature[which].y       = y;
    creature[which].deltax  = deltax;
    creature[which].deltay  = deltay;
    creature[which].species = species;
}

MODULE ULONG arand(ULONG number)
{   // Returns a value between 0 and number, inclusive.

    return((ULONG) (rand() % (number + 1)));
}

MODULE void protcreature(UBYTE player, UBYTE which)
{   UBYTE i;

    /* Handles collisions between protectors and creatures. */

    switch(creature[which].species)
    {
    case BIRD:
    case CLOUD:
    case DOG:
    case DRIP:
    case ORB:
    case PENGUIN:
        effect(FXUSE_PROTECTOR);
        wormkillcreature(player, which);
    break;
    case FRAGMENT:
        effect(FXUSE_PROTECTOR);
        reflect(which);
    break;
    case MISSILE:
        if (player != creature[which].type)
        {   effect(FXUSE_PROTECTOR);
            wormkillcreature(player, which);
        } else creature[which].visible = FALSE;
    break;
    case WHIRLWIND:
        for (i = 0; i <= PROTECTORS; i++)
        {   if
            (   protector[player][i].alive
             && protector[player][i].x == creature[which].x
             && protector[player][i].y == creature[which].y
            )
            {   protector[player][i].alive = FALSE;
        }   }
    break;
    default:
    break;
}   }

MODULE void wormcreature(UBYTE player, UBYTE which)
{   /* Handles collisions between worms and creatures. */

    switch(creature[which].species)
    {
    case CLOUD:
        wormkillcreature(player, which);
        if (worm[player].armour == 0)
        {   worm[player].alive = FALSE;
            worm[player].cause = CLOUD;
            worm[player].victor = -1;
        }
    break;
    case DOG:
        if (creature[which].dormant == DORMANT)
        {   effect(FXBORN_DOG);
            creature[which].dormant = AWAKENING;
            creature[which].type    = player;
            worm[player].last       = DOG;
        } else
        {   wormkillcreature(player, which);
            if (worm[player].armour == 0)
            {   worm[player].alive  = FALSE;
                worm[player].cause  = DOG;
                worm[player].victor = -1;
        }   }
    break;
    case DRIP:
        wormkillcreature(player, which);
        if (player != creature[which].type && worm[player].armour == 0)
        {   worm[player].alive  = FALSE;
            worm[player].cause  = FIRSTDRIP + creature[which].type;
            worm[player].victor = -1;
        }
    break;
    case FISH:
    case GOAT:
    case OCTOPUS:
        worm[player].alive  = FALSE;
        worm[player].cause  = creature[which].species;
        worm[player].victor = -1;
    break;
    case FRAGMENT:
        if (worm[player].armour > 0)
        {   effect(FXUSE_ARMOUR);
            reflect(which);
        } else
        {   wormkillcreature(player, which);
            worm[player].cause  = FRAGMENT;
            worm[player].victor = -1;
            worm[player].alive  = FALSE;
        }
    break;
    case MISSILE:
        if (creature[which].type == player)
        {   creature[which].visible = FALSE;
        } else
        {   wormkillcreature(player, which);
            if (worm[player].armour == 0)
            {   worm[player].cause  = FIRSTMISSILE + creature[which].type;
                worm[player].victor = creature[which].type;
                worm[player].alive  = FALSE;
            } else effect(FXUSE_ARMOUR);
        }
    break;
    case BIRD:
    case PENGUIN:
    case ORB:
        wormkillcreature(player, which);
        if (worm[player].armour > 0)
        {   effect(FXUSE_ARMOUR);
        } else
        {   worm[player].cause = creature[which].species;
            worm[player].victor = -1;
            worm[player].alive = FALSE;
        }
    break;
    case WHIRLWIND:
        worm[player].cause  = WHIRLWIND;
        worm[player].victor = -1;
        worm[player].alive  = FALSE;
    break;
    default:
    break;
}   }

MODULE void creaturecreature(UBYTE which1, UBYTE which2)
{   if (creature[which1].hardness > creature[which2].hardness)
    {   creature[which2].alive = FALSE;
        if (creature[which1].species == MISSILE)
        {   wormkillcreature(creature[which1].type, which2);
    }   }
    elif (creature[which1].hardness < creature[which2].hardness)
    {   creature[which1].alive = FALSE;
        if (creature[which2].species == MISSILE)
        {   wormkillcreature(creature[which2].type, which1);
    }   }
    else
    {   creature[which1].alive =
        creature[which2].alive = FALSE;
        change(creature[which1].x, creature[which1].y, BONUS);
}   }

/* 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. */

AGLOBAL 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 void ReadGameports(void)
{   ReadStandardJoystick(0);
    ReadStandardJoystick(1);
    ReadAdapterJoystick(2);
    ReadAdapterJoystick(3);
    ReadGamepads();
}

MODULE void drawmissile(SBYTE x, SBYTE y, UBYTE which)
{   draw(x, y, missileframes[creature[which].type][creature[which].frame]);
}

MODULE void checkrectangle(SBYTE direction, SBYTE player, SBYTE horizontalsize, SBYTE verticalsize)
{   AUTO SBYTE i, x, y, leftx, rightx, topy, bottomy;
    AUTO UBYTE c;
    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
    };

    x = worm[player].x;
    y = worm[player].y; // for speed

    for (i = 0; i <= verticalsize; i++)
    {   x += deltas[direction].deltax[0];
        y += deltas[direction].deltay[0];
        if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player)
        {   return;
    }   }
    for (i = 0; i <= horizontalsize; i++)
    {   x += deltas[direction].deltax[1];
        y += deltas[direction].deltay[1];
        if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player)
        {   return;
    }   }
    for (i = 0; i <= verticalsize; i++)
    {   x += deltas[direction].deltax[2];
        y += deltas[direction].deltay[2];
        if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player)
        {   return;
    }   }
    for (i = 0; i <= horizontalsize - 1; i++)
    {   x += deltas[direction].deltax[3];
        y += deltas[direction].deltay[3];
        if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player)
        {   return;
    }   }

    effect(FXDO_ENCLOSE);
    enclosed = TRUE;
    if (enclose[direction].leftx < -1)
        enclose[direction].leftx = -horizontalsize;
    elif (enclose[direction].leftx > 1)
        enclose[direction].leftx = horizontalsize + 1;
    if (enclose[direction].rightx < -1)
        enclose[direction].rightx = -(horizontalsize + 1);
    elif (enclose[direction].rightx > 1)
        enclose[direction].rightx = horizontalsize;
    if (enclose[direction].topy < -1)
        enclose[direction].topy = -verticalsize;
    elif (enclose[direction].topy > 1)
        enclose[direction].topy = verticalsize + 1;
    if (enclose[direction].bottomy < -1)
        enclose[direction].bottomy = -(verticalsize + 1);
    elif (enclose[direction].bottomy > 1)
        enclose[direction].bottomy = verticalsize;

      leftx = worm[player].x + enclose[direction].leftx;
     rightx = worm[player].x + enclose[direction].rightx;
       topy = worm[player].y + enclose[direction].topy;
    bottomy = worm[player].y + enclose[direction].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++)
        {   c = field[x][y];
            if ((c >= FIRSTEMPTY && c <= LASTEMPTY) || (c >= FIRSTTAIL && c <= LASTTAIL) || (c >= FIRSTGLOW && c <= LASTGLOW))
            {   change(x, y, FIRSTGLOW + player);
                wormscore(player, POINTS_ENCLOSURE + POINTS_TURNGOLD);
}   }   }   }

MODULE void verynewlevel(void)
{   SBYTE advancer,
          player;
    UWORD counter[4] = {0, 0, 0, 0};
    SBYTE x, y;

        for (player = 0; player <= 3; player++)
        {   if (worm[player].lives)
            {   for (x = 0; x <= FIELDX; x++)
                {   for (y = 0; y <= FIELDY; y++)
                    {   if (field[x][y] == FIRSTTAIL + player || field[x][y] == FIRSTGLOW + player)
                        {   counter[player]++;
        }   }   }   }   }
        if (counter[0] >= counter[1]
         && counter[0] >= counter[2]
         && counter[0] >= counter[3])
        {   advancer = 0;
        } elif (counter[1] >= counter[0]
         && counter[1] >= counter[2]
         && counter[1] >= counter[3])
        {   advancer = 1;
        } elif (counter[2] >= counter[0]
         && counter[2] >= counter[1]
         && counter[2] >= counter[3])
        {   advancer = 2;
        } else
        {   advancer = 3;
        }

        if (level++ == 0)
        {   level = reallevel + 1;
            reallevel = 0;
        }
        stopfx();
        if (level > levels)
            effect(FXCELEBRATE);
        newlevel(advancer);
}

MODULE void getnumber(SBYTE player)
{   worm[player].numbers++;
    wormscore(player, POINTS_LETTER);
    number++;

    if (number == 10)
    {   verynewlevel();
    }

    // The calling function is responsible for calling putnumber() if it
    // wants a new number to appear.
}
