/*  V 2.00
*   Caterpilla (Polyscope Plus?)
*   by Tony {I *HATE* C} Wills                               (BIX == ajwills)
*   {Give me Turbo Pascal anyday!!!}
*
*   Inspired by images from EA 'Polyscope' (no 'look and feel' suites please!)
*   Further inspirartion from watching cuttlefish
*   Released to the public domain Sep. 1989
*/

#include <devices/audio.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <graphics/gfxbase.h>
#include <graphics/gfx.h>
#include <graphics/rastport.h>
#include <graphics/gfxmacros.h>
#include <graphics/view.h>
#include <functions.h>
#include <stdio.h>

/* Audio stuff */
#define NUMSOUNDS 7
int              gotdev = 0;     /* 1 means succeeded to open device */
FILE            *noisefile;      /* digitised sound files */
FILE            *noiselist;      /* list of soundfile names */

int              noisesize[NUMSOUNDS]; /* length of each sound sample*/
int              maxnoise;       /* actual number of sound files in use */
int              noisy=0;        /* flag to say we've initiated a noise */
struct IOAudio  *audIO;                              /*  all         */
UBYTE           *auddat[NUMSOUNDS];                  /*  audio       */
struct MsgPort  *audport;                            /*  hardware    */
UBYTE            allocmap[] = {0x01,0x02,0x04,0x08}; /*  stuff       */

/* Rest stuff */
struct IntuitionBase *IntuitionBase;  /* Linker requires this symbol name to
                                         link to called Intuition routines */
struct GfxBase       *GfxBase;        /* Linker requires this symbol name to
                                         link to called graphics routines */

#define MAXDIV 5                  /* max number tiled windows across/down */
#define MAXWIN MAXDIV*MAXDIV      /*  "    "      "     "     on screen */
struct Window   *MyWindow[MAXWIN];/* the windows, one per worm */
struct Screen   *MyScreen;
struct ViewPort *VP;

/* In the following 'disc' refers to a solid filled circle */
#define MAXRAD 30                 /* Maximum+1 disc radius to draw */
typedef UBYTE * PATTERN;
PATTERN Discs[MAXRAD];            /* disc templates, to save redrawing time*/
LONG    DiscBytes[MAXRAD];        /* bytes per row of disc templates */
PATTERN Circs[MAXRAD];            /* circle templates */
LONG    CircBytes[MAXRAD];        /* bytes per row of circle templates */
LONG    rad;                      /* radius of a disc or circle */
LONG    rgb[32][3];               /* colour palette information */
LONG    rowbytes, lines;          /* bytes/row and height of a template */
int     numcols;                  /* number of colours we're using */
int     symm;                     /* mirror symmetry on/off flag */
int     killsymm;                 /* disable mirror symmetry mode */
int     timeout;                  /* flag indicating user wants to quit */
int     theyhaventcutthepoweroff = 1; /* for(;ever;)? */

struct worms {
  LONG x;
  LONG y;                   /* current position of head disc of worm */
  LONG xinc;
  LONG yinc;                /* step size to move before drawing next disc */
  int  minx, maxx;
  int  miny, maxy;          /* min and max boundaries for drawing */
  int  corner;              /* size of window gadgets */
  int  minr;
  int  maxr;                /* radius limits for growth of our worms discs */
  LONG siz;                 /* current radius of head disc of worm */
  LONG oldsiz;              /* radius we're growing away from */
  int  numsteps;
  int  count;               /* how long to grow for, and current progress */
  int  minsteps;
  int  maxsteps;            /* limits for growth in length of any segment */
  int  diff;                /* amount to grow in radius per step */
  int  bounces;             /* number bounces off walls in current mode */
  int  dmode;               /* what form to draw (hollow/solid etc) */
  int  life;                /* expiration in,close gadget hits,of this worm */
};

/* Hues 0-30; Red +0, Grn + 10, Blu +20 mod 30 */
int HRGB[30] ={ 15,15,15,15,15,15,12, 9, 6, 3, 0, 0, 0, 0, 0,
                 0, 0, 0, 0, 0, 0, 3, 6, 9,12,15,15,15,15,15 };
/* Our palette is made up of RGB triples, the red component of the nth
   hue is the nth value from the above list, the green component is the
   n+10th value (mod 30) and the blue component is the n+20th (mod 30)
   value. */

/* The following isn't used in this version, but is simply a graphic
   rendition of my mouse pointer */
USHORT MyPointer[]=
{
0x0000, 0x0000,   /*      Plane1          Plane2          Plane1 & Plane2 */
0x001C, 0x001C,   /* 0000000000011100 0000000000011100              333   */
0x003E, 0x003E,   /* 0000000000111110 0000000000111110             33333  */
0x03FF, 0x03FF,   /* 0000001111111111 0000001111111111         3333333333 */
0x02FF, 0x03FF,   /* 0000001011111111 0000001111111111         3133333333 */
0x03FF, 0x07FF,   /* 0000001111111111 0000011111111111        13333333333 */
0x00FF, 0x08FF,   /* 0000000011111111 0000100011111111       1   33333333 */
0x003E, 0x103E,   /* 0000000000111110 0001000000111110      1      33333  */
0x001C, 0x201C,   /* 0000000000011100 0010000000011100     1        333   */
0x4000, 0x0008,   /* 0100000000000000 0000000000001000    2          1    */
0x0000, 0x0008,   /* 0000000000000000 0000000000001000               1    */
0x0000, 0x0070,   /* 0000000000000000 0000000001110000            111     */
0x0000, 0x0000,
0x0000, 0x0000,
};
/*Can't remember what the following rubbish is for, it's commented out,
  probably something I used in conjunction with the above, I'll probably
  remember one day! */
/*
FFF4            11111111 = -00000001  11111000 = -00001000

0000
0C65
0DC0

0001
*/

/* ============================= main =================================== */
main()
{
  struct NewScreen MyNewScreen;
  struct NewWindow MyNewWindow;
  int    Ht;                      /* Window max height NTSC or PAL */
  int    i,j;                     /* x, y window indices */
  int    div;                     /* number horz and vert window divisions */
  LONG   ztime;                   /* seed for random number generation */

  /* attempt to ramdomize the random number generator */
  time(&ztime);
  /* ok, tell me how to seed the random number generator and I'll do it! */
  for (i=1;i < (ztime & 0x1f);i++) (void)rand();

  /* open intuition library, so we can use Windows and Screens */
  IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0L);
  if (IntuitionBase == NULL) myexit("failed to open intuition library");

  /* open graphics library, so we can call graphics drawing routines */
  GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0L);
  if (GfxBase == NULL) myexit ("failed to open graphics library");

  /* decide to open NTSC (USA) or PAL (rest of universe) sized screen */
  if ((*GfxBase).DisplayFlags & PAL)
    Ht = 270;
  else
    Ht = 240;

  /* describe screen we wish to open, we want 16 colour interlaced screen */
  MyNewScreen.LeftEdge     = 0/*-20*/;
  MyNewScreen.TopEdge      = 0/*Ht-150*/;
  MyNewScreen.Width        = 360;                /* Lores overscan */
  MyNewScreen.Height       = Ht;                 /* Interlace PAL or NTSC */
  MyNewScreen.Depth        = 5;                  /* 32 colours */
  MyNewScreen.DetailPen    = 1;                  /* DetailPen - text etc */
  MyNewScreen.BlockPen     = 0;                  /* BlockPen - borders etc */
  MyNewScreen.ViewModes    = NULL;               /* Hires Interlaced screen */
  MyNewScreen.Type         = CUSTOMSCREEN;
  MyNewScreen.Font         = NULL;               /* default system font */
  MyNewScreen.DefaultTitle = (UBYTE *)"   Caterpillar 2.0 - by Tony Wills";
  MyNewScreen.Gadgets      = NULL;               /* no special gadgets */
  MyNewScreen.CustomBitMap = NULL;               /* no custom BitMap */

  /* try to open our screen, so we can open our hires graphics window */
  if ((MyScreen = (struct Screen *)OpenScreen(&MyNewScreen)) == NULL)
    myexit("failed to open hires screen");
   
  /* describe the custom hires window we wish to draw our graphics into */
  MyNewWindow.LeftEdge    = 10;
  MyNewWindow.TopEdge     = 10;
  MyNewWindow.Width       = 10;
  MyNewWindow.Height      = 10;
  MyNewWindow.DetailPen   = 0;
  MyNewWindow.BlockPen    = 0;
  MyNewWindow.Title       = (UBYTE *)" ";
                                      /* want closegadget,dragbar,sizegadget
                                         and don't care to refresh display */
  MyNewWindow.Flags       = WINDOWCLOSE|WINDOWDRAG|WINDOWSIZING|NOCAREREFRESH;
                                      /* want to be told when window is
                                         resized or closed */
  MyNewWindow.IDCMPFlags  = NEWSIZE | CLOSEWINDOW;
  MyNewWindow.Type        = CUSTOMSCREEN;
  MyNewWindow.FirstGadget = NULL;       /* no custom gadgets */
  MyNewWindow.CheckMark   = NULL;       /* no custom checkmark */
  MyNewWindow.Screen      = MyScreen;   /* our custom hires 16 colour screen */
  MyNewWindow.BitMap      = NULL;       /* default bitmap */
  MyNewWindow.MinWidth    = 40;         /* It'll cause problems if we allow */
  MyNewWindow.MinHeight   = 40;         /*   too small a window */
  MyNewWindow.MaxWidth    = 320/*360*/;        /* full screen width */
  MyNewWindow.MaxHeight   = Ht;         /* full screen height (NTSC or PAL) */

  /* Circles are drawn 2*radius+1 in diameter! - gives circle a centre plus
     two equal axis, the system routine to drawcircles also does this,
     but it's not mentioned anywhere - may lead to a few bugs one day */

  /* Get some chip memory to store our templates in, we're asking for lots
     of little bits, which generally isn't a nice thing to do
     (fragmentation), but as we're asking for them all one after the other
     it'll probably end up contiguous.  I suppose it's really not too hard
     to just ask for one big block, then point my array at it ... one day */
  for (rad=1; rad<MAXRAD; rad++ ) {
    rowbytes = (((2*rad +1) -1)/8 +1);
    if ( (rowbytes % 2) == 1) rowbytes++;
    lines = 2*rad +1;
    DiscBytes[rad] = rowbytes;
    CircBytes[rad] = rowbytes;
    if ( (Discs[rad]=(UBYTE *)AllocMem((lines*rowbytes),MEMF_CHIP|MEMF_CLEAR))==NULL)
      myexit("failed to allocate template CHIP mem");
    if ( (Circs[rad]=(UBYTE *)AllocMem((lines*rowbytes),MEMF_CHIP|MEMF_CLEAR))==NULL)
      myexit("failed to allocate template CHIP mem");
  }

  MakeTemplates();
      
  VP = &MyScreen->ViewPort;

  /*Knock the system about by kicking the screen to the left to give us
    overscan, didya notice the screen jump left after its opended?? */
/*  VP->DxOffset = -20;
  MakeScreen(MyScreen);
  RethinkDisplay();*/
/*commented out, so you can get at the close gadget in this version !!*/

/* The following was to try and see if giving the window its own pointer
   would relieve my problem of having the mouse pointer restricted to the
   boundaries of the WB screen, rather than my overscan one.  NB if you do
   uncomment it, I haven't allocated the pointer any chip memory, so you'd
   better run nofastmem if you enable this feature! */
/*  SetPointer(MyWindow, &MyPointer, 11L, 16L, -1L, -8L );*/

  numcols = 32;                       /* max num colours */
  timeout = 0;

  initsnd();                          /* get soundport, and digitised files*/

  while(theyhaventcutthepoweroff) {
  for (div=1;div<=MAXDIV;div++) {
    /* cycle thru playing in 1, 4, 9, 16, and 25 windows */
    if (div==1) {
        /* if only 1 window start small so we can grow */
        MyNewWindow.LeftEdge = MyNewWindow.MaxWidth/4;
        MyNewWindow.TopEdge  = MyNewWindow.MaxHeight/4;
        MyNewWindow.Width    = MyNewWindow.MaxWidth/2;
        MyNewWindow.Height   = MyNewWindow.MaxHeight/2;
        MyWindow[0] = NULL;
        /* try to open the window */
        MyWindow[0] = (struct Window *)OpenWindow(&MyNewWindow);
    } else {
      /* open multiple tiled windows */
      for (j=0;j<div;j++)
        for (i=0;i<div;i++) {
          MyNewWindow.LeftEdge = MyNewWindow.MaxWidth/div*i;
          MyNewWindow.TopEdge  = MyNewWindow.MaxHeight/div*j;
          MyNewWindow.Width    = MyNewWindow.MaxWidth/div;
          MyNewWindow.Height   = MyNewWindow.MaxHeight/div;
          MyWindow[i+j*div] = NULL;
          /* try to open the window */
          MyWindow[i+j*div] = (struct Window *)OpenWindow(&MyNewWindow);
        };
    };

    /* make sure we got at least one window !!! */
    if (MyWindow[0] == NULL)
      myexit("failed to open a window[0]");

    WindowToFront(MyWindow[0]);  /* not really necessary on this version */

    Bounce(div);                 /* bounce worms in div*div windows */

    for (i=0;i<MAXWIN;i++)       /* close that set of windows */
      if (MyWindow[i]!=NULL) {
        CloseWindow(MyWindow[i]);
        MyWindow[i]=NULL;
      }
  }
  }

  /* what how did we get here?, someone hasn't actually worked out how to
     exit have they? - well, better do what they ask */

  /* Cleanup, and go home */
  freesnd();
  for (rad=1; rad<MAXRAD; rad++ ) {
    FreeMem(Discs[rad], ((2*rad+1)*DiscBytes[rad]) );
    FreeMem(Circs[rad], ((2*rad+1)*CircBytes[rad]) );
  }

  CloseScreen(MyScreen);
  CloseLibrary(GfxBase);
  CloseLibrary(IntuitionBase);
  exit(TRUE);
}


/*=======================================================================*/
Bounce (div)
int div;
{
  struct worms w[MAXWIN];   /* only want div*div worms, but c doesn't want to know */

  int  notdone;             /* exit flag */
  LONG pause;               /* not used this version, delay between drawing */

  LONG col;                 /* pen colour to draw next disc */

  int  hue;                 /* index into RGB table */
  int  ihue;                /* colour step size between colour bands */
  int  bidx;                /* index into band while changing colour */
  LONG band;                /* start of next band in palette to recolour */
  int  bright;              /* whether we're making bands brighten/darken */

  LONG i,j;                 /* temporary counters */
  int  is,js;               /* sign flags, to save us doing -1**i */
  int  win;                 /* current worm/window index */


  notdone = 1;
  symm = 1;
  killsymm=0;
  pause = 0;

  hue    = 0;
  ihue   = 2;
  bright = 1;
  band   = 2;
  bidx   = 0;
  col    = 1;

  /* initialise enough worms to fill all windows */
  for (win=0;win<div*div/*MAXWIN*/;win++) {
    w[win].minr = 3;
    w[win].minx = 0;
    w[win].maxx = 1000;        /* dummy value to trigger an initial resize */
    w[win].miny = 0;
    w[win].maxy = 1000;
    w[win].maxsteps = 20;
    w[win].minsteps = 5;
    w[win].bounces = 0;
    w[win].dmode = win % 6;
    w[win].life = ((rand() % 5) +1)*div;
    if (win==0) w[win].life*=3;
    if (MyWindow[win]!=NULL)   /* may have failed to open all windows */
      init(&w[win],win);
  }

  SetRGB4(VP,0L,0L,0L,0L);     /* black and grey set pens */
  SetRGB4(VP,1L,5L,5L,5L);
  
  while (notdone) {            /* repeat until want more windows */
    /* cycle colours */
    col++; if (col>=numcols) col = 2;
    for (i=2; i< numcols; i++) {
      j= numcols-1-i+col; if (j>=numcols) j=j-numcols+2;
      SetRGB4(VP,i,rgb[j][0],rgb[j][1],rgb[j][2]);
    }
    /* ihue is increment used to determine range of hues used by worm */
    if ((rand() % 1000) == 0) {ihue = ihue++ % 5; if (ihue==0) ihue++;};

    /* bidx counter is active while we're in the middle of altering the hue
     *  of a band of colour in the worm */
    if (!bidx) {
      /* we change 1 segment of the band each time the worm grows 1 segment */
      /* decide whether it's time to change a band then */
      if ((rand() % 60) == 0 ) {
        band+=5; if (band >= numcols) band = 2;
        hue = (hue + ihue) % 30;
        rgb[band][0] = HRGB[hue];
        rgb[band][1] = HRGB[(hue + 20) % 30];
        rgb[band][2] = HRGB[(hue + 10) % 30];
        bidx = 1;
      }
    } else {
      /* changing the hue of a band of colour, until reach end of band
       * or the limit of our palette size (last band may have less than 5
       * coloured segments if run out of palette registers) */
      if (bidx<5 && (band+bidx)<numcols) {
        /* bands either increase in brighter shades of the hue, or darken */
        if (bright == 1) {
          /* we're dealinng with integers here, and hence truncation,
           * which is why we don't just add a fractional inc each segment */
          rgb[band+bidx][0] = rgb[band][0] + (bidx*(15-rgb[band][0])/15);
          if (rgb[band+bidx][0] > 16) rgb[band+bidx][0]=16;
          rgb[band+bidx][1] = rgb[band][1] + (bidx*(15-rgb[band][1])/15);
          if (rgb[band+bidx][1] > 16) rgb[band+bidx][1]=16;
          rgb[band+bidx][2] = rgb[band][2] + (bidx*(15-rgb[band][2])/15);
          if (rgb[band+bidx][2] > 16) rgb[band+bidx][2]=16;
        } else {
          rgb[band+bidx][0] = rgb[band][0]*(15-bidx)/15;
          rgb[band+bidx][1] = rgb[band][1]*(15-bidx)/15;
          rgb[band+bidx][2] = rgb[band][2]*(15-bidx)/15;
        };
        bidx++;   /* point to next segment in band */
      } else
        bidx = 0;
    }

    /* draw one disc of each worm and move to next position */
    if(symm==0 || killsymm==1 || MyWindow[0]==NULL) {
      /* unsynchronised chaos */
      for (win=0;win<div*div;win++) {
        if (MyWindow[win]!=NULL) {
          drawworm(win,&w[win],col,div,&notdone);
          moveworm(win,&w[win],&pause,div,&bright,&notdone);
        }
      }
    } else {
      /* orderly symmetry between windows, following worm[0] */
      drawworm(0,&w[0],col,div,&notdone);
      moveworm(0,&w[0],&pause,div,&bright,&notdone);
      /* now reflect that move in all the other windows */
      js=1;
      for (j=0;j<div;j++) {
        is=1;
        for(i=0;i<div;i++) {
          win=i+j*div;
          w[win].x=is*w[0].x;
          if (w[win].x < 0) w[win].x= w[0].maxx+w[win].x+w[0].minx;
          w[win].y=js*w[0].y;
          if (w[win].y < 0) w[win].y= w[0].maxy+w[win].y+w[0].miny;
          w[win].siz=w[0].siz;
          w[win].dmode=w[0].dmode;
          is=-is;
        };
        js=-js;
      };
      /* now draw worms, so long as their windows are still there */
      for (win=1;win<div*div;win++)
        if (MyWindow[win]!=NULL)
          drawworm(win,&w[win],col,div,&notdone);
    }
  }
}

/*=======================================================================*/
/* plot one disc of one worm in one window, check for gadget activity */
drawworm(win,w,col,div,notdone)
int win;
struct worms *w;
LONG  col;
int div;
int *notdone;
{
  struct IntuiMessage *msg;
  int i,j;

  /*draw disc */
  if(w->siz <= 0 || w->siz >= w->maxr)
    /* debugging stuff, hopefully we never fall in here (anymore!!)
     * but left here as a trap 'cause we can't afford to try and plot
     * junk to screen as it'll scramble memory */
    printf("bad circle size\n");
  else switch (w->dmode) {
    /* a few different ways to draw circles on the screen, the present
     * choices could be reduced to plotting disks with coloured borders,
     * the disk and border colours being parameters.  But historically
     * I have used a few further variants, and probably will in the
     * future, so this is left here in a rather verbose form */
    case 0 : { /* coloured disc */
               SetAPen(MyWindow[win]->RPort,col);
               BltPattern( MyWindow[win]->RPort, Discs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, DiscBytes[w->siz] );
               break;
             }
    case 1 : { /* coloured disk with black outline */
               SetAPen(MyWindow[win]->RPort,col);
               BltPattern( MyWindow[win]->RPort, Discs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, DiscBytes[w->siz] );
               SetAPen(MyWindow[win]->RPort,0L);
               BltPattern( MyWindow[win]->RPort, Circs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, CircBytes[w->siz] );
               break;
             }
    case 2 : { /* coloured disk with grey outline */
               SetAPen(MyWindow[win]->RPort,col);
               BltPattern( MyWindow[win]->RPort, Discs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, DiscBytes[w->siz] );
               SetAPen(MyWindow[win]->RPort,1L);
               BltPattern( MyWindow[win]->RPort, Circs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, CircBytes[w->siz] );
               break;
             }
    case 3 : { /* black disc with coloured outline */
               SetAPen(MyWindow[win]->RPort,0L);
               BltPattern( MyWindow[win]->RPort, Discs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, DiscBytes[w->siz] );
               SetAPen(MyWindow[win]->RPort,col);
               BltPattern( MyWindow[win]->RPort, Circs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, CircBytes[w->siz] );
               break;
             }
    case 4 : { /* grey disc with coloured outline */
               SetAPen(MyWindow[win]->RPort,1L);
               BltPattern( MyWindow[win]->RPort, Discs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, DiscBytes[w->siz] );
               SetAPen(MyWindow[win]->RPort,col);
               BltPattern( MyWindow[win]->RPort, Circs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, CircBytes[w->siz] );
               break;
             }
    case 5 : { /* hollow coloured circle */
               SetAPen(MyWindow[win]->RPort,0L);
               BltPattern( MyWindow[win]->RPort, Circs[w->siz],
                           w->x-w->siz, w->y-w->siz,
                           w->x+w->siz, w->y+w->siz, CircBytes[w->siz] );
               SetAPen(MyWindow[win]->RPort,col);
               if (w->siz>(w->minr+1))
                 BltPattern( MyWindow[win]->RPort,
                             Circs[w->siz-1], w->x-(w->siz-1), w->y-(w->siz-1),
                             w->x+(w->siz-1), w->y+(w->siz-1), CircBytes[w->siz-1] );
               else
                 BltPattern( MyWindow[win]->RPort, Circs[w->siz],
                             w->x-w->siz, w->y-w->siz,
                             w->x+w->siz, w->y+w->siz, CircBytes[w->siz] );
               break;
             }
  };

  if (timeout>0) timeout--;

  while ((msg = (struct IntuiMessage *)GetMsg(MyWindow[win]->UserPort)) != 0) {
    switch(msg->Class) {
      case CLOSEWINDOW:
        ReplyMsg(msg);
        CloseWindow(MyWindow[win]);
        MyWindow[win]=NULL;
        if (win==0) {
          killsymm = 1;
          *notdone = 0;
          /* if they're closing windows in rapid succession, then let them out */
          if (timeout>0) theyhaventcutthepoweroff = 0;
          timeout=10;
        }
/* used to wait for all windows to close before going onto next set of
   windows, now just wait for window 0 (top left) to be closed */
/*        *notdone = 0;
        for (i=0;i<div*div;i++)
          if (MyWindow[i]!=NULL) *notdone=1;*/
        break;
      case NEWSIZE:
        ReplyMsg(msg);
        killsymm = 1;
        init(w,win);
        break;
      default:
        ReplyMsg(msg);
    };
  };
}

/*========================================================================*/
/* move one worm in one window one step, watch out for edges and corners */
moveworm(win,w,pause,div,bright,notdone)
int win;
struct worms *w;
LONG *pause;
int   div;
int  *bright;
int  *notdone;
{
  LONG i,j;

  /* move to draw next disc */
  w->x+=w->xinc;
  w->y+=w->yinc;

  /* ramp disc size up or down */
  w->count++;
  w->siz = w->oldsiz + (w->diff*w->count)/w->numsteps;

  /* bounce off walls */
  /* bounce off sizing gadget */
  if ((w->y >= w->maxy-w->corner-w->siz) && (w->x >= w->maxx-w->corner-w->siz)){
    /* just **had** to do something 'cute', so running into sizing
       gadget enlarges window */
    i=j=0;
    if (div==1) {
      if (MyWindow[0]->LeftEdge+MyWindow[0]->Width < MyWindow[0]->MaxWidth-1) {
        if (MyWindow[0]->LeftEdge+MyWindow[0]->Width < MyWindow[0]->MaxWidth-11)
          i=10;
        else
          i=(MyWindow[0]->MaxWidth-MyWindow[0]->Width-MyWindow[0]->LeftEdge-1);
      }
      if (MyWindow[0]->TopEdge+MyWindow[0]->Height < MyWindow[0]->MaxHeight-1) {
        if (MyWindow[0]->TopEdge+MyWindow[0]->Height < MyWindow[0]->MaxHeight-11)
          j=10;
        else
          j=(MyWindow[0]->MaxHeight-MyWindow[0]->Height-MyWindow[0]->TopEdge-1);
      }
    }
    if (i+j!=0) {
      snd(6);
      SizeWindow(MyWindow[0],i,j);
      Delay(4L); /* give intuition time to resize window */
      init(w,win);
    } else
/*      snd(w->dmode)*/;  /* just normal bounce if already max size */

    /* turn worm back from corner */
/*    if (w->xinc>0) w->xinc = -((rand() % w->minr) +w->minr);
    w->x = w->maxx-w->siz;
    if (w->yinc>0) w->yinc = -((rand() % w->minr) +w->minr);
    w->y = w->maxy-w->siz-w->corner;
    (w->bounces)++;
*/
  };

  /* bounce off close gadget */
  if ((w->y <= w->miny+w->corner+w->siz) && (w->x <= w->minx+w->corner+w->siz)) {
    i=j=0;
    if (div==1) {
      if (MyWindow[0]->LeftEdge > 0) {
        if (MyWindow[0]->LeftEdge >= 10)
          i=-10;
        else
          i=-MyWindow[0]->LeftEdge;
      }
      if (MyWindow[0]->TopEdge > 0) {
        if (MyWindow[0]->TopEdge >= 10)
          j=-10;
        else
          j=-MyWindow[0]->TopEdge;
      }
    }

    if (i+j!=0) {
      snd(6);
      MoveWindow(MyWindow[0],i,j);
      SizeWindow(MyWindow[0],-i,-j);
      Delay(4L); /*Give intuition time to move/resize window*/
      init(w,win);
      w->life--;
    } else {
/*      snd(w->dmode); */
      w->life--;
      if (w->life < 1) {
        CloseWindow(MyWindow[win]);
        MyWindow[win]=NULL;
        if (win==0) {
          snd(6);
          killsymm=1;
          *notdone=0;
        }
/*        *notdone = 0;
        for (i=0;i<div*div;i++)
          if (MyWindow[i]!=NULL) *notdone=1; */
        return();
      }
    }

    /* turn worm away from close gadget */
/*    if (w->xinc<0) w->xinc = ((rand() % w->minr) +w->minr);
    w->x = w->minx+w->siz+w->corner;
    if (w->yinc<0) w->yinc = ((rand() % w->minr) +w->minr);
    w->y = w->miny+w->siz;
    (w->bounces)++;
*/
  }

/* following stuff was to leave symmetric holes opposite those left by
   close and size gadget, but now we write over the gadgets so this
   is also deleted */
/*
  if ((w->y >= w->maxy-w->corner-w->siz) && (w->x <= w->minx+w->corner+w->siz)) {
*/    /* turn worm away from bottom left corner */
/*    if (w->xinc<0) w->xinc =  ((rand() % w->minr) +w->minr);
    w->x = w->minx+w->siz+w->corner;
    if (w->yinc>0) w->yinc = -((rand() % w->minr) +w->minr);
    w->y = w->maxy-w->siz-w->corner;
    (w->bounces)++;
    snd(w->dmode);
  }
  if ((w->y <= w->miny+w->corner+w->siz) && (w->x >= w->maxx-w->corner-w->siz)){
*/    /* turn worm away from top right corner*/
/*    if (w->xinc>0) w->xinc = -((rand() % w->minr) +w->minr);
    w->x = w->maxx-w->siz-w->corner;
    if (w->yinc<0) w->yinc =  ((rand() % w->minr) +w->minr);
    w->y = w->miny+w->siz+w->corner;
    (w->bounces)++;
    snd(w->dmode);
  }
*/

  /* bounce off left and right walls */
  if (w->x <= w->minx+w->siz) {
    /* bounce with random variation off left wall */
    if (w->xinc<0) w->xinc=(rand() % w->minr)+w->minr;
    /* NB we double check direction above, as we may be traveling away
     * from the wall and yet hit it! due to the worm growing larger just
     * as we are leaving the wall. If we don't do this, then we may end
     * up oscillating toward and away from the wall, net effect - we hug
     * the wall after some 'bounces'. */
    w->x = w->minx+w->siz;  /* touch wall during bounce */
    (w->bounces)++;
    snd(w->dmode);
  }
  if (w->x >= w->maxx-w->siz) {
    /* bounce with random variation off right wall */
    if (w->xinc>0) w->xinc = -((rand() % w->minr)+w->minr);
    w->x = w->maxx-w->siz;
    (w->bounces)++;
    snd(w->dmode);
  }

  /* bounce off top and bottom walls */
  if (w->y <= w->miny+w->siz) {
    /* bounce off top wall, with random variation */
    if (w->yinc<0) w->yinc = (rand() % w->minr)+w->minr;
    w->y = w->miny+w->siz;
    (w->bounces)++;
    snd(w->dmode);
  }
  if (w->y >= w->maxy-w->siz) {
    /* bounce off bottom wall, with random variation */
    if (w->yinc>0) w->yinc = -((rand() % w->minr) +w->minr);
    w->y = w->maxy-w->siz;
    (w->bounces)++;
    snd(w->dmode);
  }


  /* worm band completed ? */
  if (w->count>=w->numsteps) {
    /* start new band */
    w->oldsiz = w->siz;
    w->diff = (rand() % (w->maxr-w->minr))+w->minr - w->oldsiz;
    w->numsteps = (rand() % (w->maxsteps-w->minsteps)) + w->minsteps;
    w->count = 0;
  }

  /* have we we had enough of this drawing mode yet ? */
  if (w->bounces>24) {
    w->bounces=0;
    w->dmode = rand() % 6; /* new drawing mode */
    *bright = rand() % 2;  /* randomise brighter/darker band colouring */
    *pause = rand() % 2;   /* new drawing speed */
    /* blank window for a change, or if drawing hollow circles */
    if ((rand() % 2)==1 || (w->dmode==5)) {
      BlankWindow(*w,win);
      if (symm==1 && killsymm==0) {
        snd(6);
        for (i=1; i<(div*div); i++) {
          if (MyWindow[i]!=NULL) {
            BlankWindow(*w,(int)i);
            /* %$#&*! c language, took a while to realise that without the
             * (int) cast above, the LONG i gave me the high order half
             * of the longword, not the expected lower order half!!! */
          }
        }
      }
    }
/* used to randomly turn on/off symmetry, but decided symmetric is much
   nicer, so now only turn it off if vandals attach the windows (resize
   windows or close window[0] */
/*    if ((rand()%3)<2)
      symm=1;
    else
      symm=0;*/
  }

  /*  Delay(*pause); /* be kind to multitasking */

}

/*=======================================================================*/
init(w,win)
/*called after opening new window, and on window resize */
  struct worms *w;
  int   win;
{
  LONG i,j;
  int  hue;
  int  oldminx, oldminy, oldmaxx, oldmaxy;

  /* so we can calculate whether they've enlarged or reduced the window */
  oldminx = w->minx;
  oldmaxx = w->maxx;
  oldminy = w->miny;
  oldmaxy = w->maxy;

  /* find window limits */
  w->miny = 0 /*MyWindow[win]->BorderTop*/;
  w->maxy = MyWindow[win]->Height/*-1 - 1/*MyWindow[win]->BorderBottom*/;
  w->minx = 0/*MyWindow[win]->BorderLeft*/;
  w->maxx = MyWindow[win]->Width/*-1 - 1/*MyWindow[win]->BorderRight*/;
  w->corner = 10/*MyWindow[win]->BorderRight-2*/;

  /* set max radius to 1/4 of minimum dimension of current window */
  w->maxr = MAXRAD-1;
  if (w->maxr>(w->maxx-w->minx)/4) w->maxr = (w->maxx-w->minx)/4;
  if (w->maxr>(w->maxy-w->miny)/4) w->maxr = (w->maxy-w->miny)/4;

  if ((w->minx<oldminx) || (w->miny<oldminy) ||
      (w->maxx<oldmaxx) || (w->maxy<oldmaxy)   ) {
    /* reinitialise worm parameters if window smaller than old, or new window */
    w->x = (w->maxx-w->minx)/2 +w->minx;
    w->y = (w->maxy-w->miny)/2 +w->miny;

    w->siz  = (rand() % (w->maxr-w->minr))+w->minr;
    w->oldsiz = w->siz;
    w->diff = (rand() % (w->maxr-w->minr))+w->minr - w->oldsiz;
    w->numsteps = (rand() % (w->maxsteps-w->minsteps)) + w->minsteps;
    w->count = 0;

    w->xinc = (rand() % w->minr) +w->minr;
    w->yinc = (rand() % w->minr) +w->minr;
  
    /* clear the window */
    BlankWindow(*w,win);

    /* initialise to a grey pallette (why not?) */
    for (i=2; i<numcols; i+=5) {
      hue = rand() % 30;
/*      rgb[i][0] = HRGB[hue];
      rgb[i][1] = HRGB[(hue +20) % 30];
      rgb[i][2] = HRGB[(hue +10) % 30];
*/
      rgb[i][0] = 2*i+2;
      rgb[i][1] = 2*i+2;
      rgb[i][2] = 2*i+2;
      SetRGB4(VP,i,rgb[i][0],rgb[i][1],rgb[i][2]);
      for (j=1; j<5 && (i+j)<numcols; j++ ) {
        if ((rand() & 1) == 0) {
          rgb[i+j][0] = rgb[i][0] + (j*(15-rgb[i][0])/15);
          if (rgb[i+j][0] > 16) rgb[i+j][0]=16;
          rgb[i+j][ 1] = rgb[i][1] + (j*(15-rgb[i][1])/15);
          if (rgb[i+j][1] > 16) rgb[i+j][1]=16;
          rgb[i+j][2] = rgb[i][2] + (j*(15-rgb[i][2])/15);
          if (rgb[i+j][2] > 16) rgb[i+j][2]=16;
        } else {
          rgb[i+j][0] = rgb[i][0]*(15-j)/15;
          rgb[i+j][1] = rgb[i][1]*(15-j)/15;
          rgb[i+j][2] = rgb[i][2]*(15-j)/15;
        };
        SetRGB4(VP,(LONG)(i+j),rgb[i+j][0],rgb[i+j][1],rgb[i+j][2]);
      };
    };
  };
}

/*======================================================================*/
/* cover screen with ranbow coloured poka-dots (not really!) */
BlankWindow(w,win)
struct worms w;
int win;
{
    /* clear the screen */
    SetAPen(MyWindow[win]->RPort,0L);
    SetOPen(MyWindow[win]->RPort,0L);
    RectFill(MyWindow[win]->RPort, 0L/*(LONG)(w.minx+w.corner)*/, 0L/*(LONG)w.miny*/, (LONG)w.maxx, (LONG)w.maxy/*(w.maxy-w.corner)*/ );
}

/*=====================================================================*/
/* Stamp out a set of shapes to blit to the screen */
MakeTemplates()
{
int x,y,radius,d;
  /* someones clever routine for quickly drawing circles */
  for (radius=1; radius < MAXRAD; radius ++ ) {
    x=0;
    y=radius;
    d = 3- (radius << 1);
    while (x<y) {
      circlepoints(radius,x,y/*,fill*/);
      if (d<0)
        d = d + (x << 2) + 6;
      else {
        d = d + ((x-y) << 2) + 10;
        y = y-1;
      }
      x = x+1;
    }
    if (x==y) circlepoints(radius,x,y/*,fill*/);
  }
}

circlepoints(radius,x,y)
int radius,x,y;
{
  /* Stamp out hollow circle templates */
    pset( radius, radius + x, radius + y );
    pset( radius, radius + y, radius + x );
    pset( radius, radius + y, radius - x );
    pset( radius, radius + x, radius - y );
    pset( radius, radius - x, radius - y );
    pset( radius, radius - y, radius - x );
    pset( radius, radius - y, radius + x );
    pset( radius, radius - x, radius + y );
  /* Stamp out solid disc templates */  
    line( radius, radius-x, radius+x, radius+y );
    line( radius, radius-y, radius+y, radius+x );
    line( radius, radius-y, radius+y, radius-x );
    line( radius, radius-x, radius+x, radius-y );
}

line(radius,x1,x2,y)
int radius,x1,x2,y;
{
  int byte, byte1, byte2;
  UBYTE mask1, mask2;

  byte1 = y*DiscBytes[radius] + x1/8;
  byte2 = y*DiscBytes[radius] + x2/8;
  for (byte=byte1+1; byte<=byte2-1; byte++)
    Discs[radius][byte] = 0xff;
  mask1 = (1 << (8-(x1 % 8)))-1;
  mask2 = ~((1 << (8-(x2 % 8)))-1);
  if (byte1 == byte2) {
    Discs[radius][byte1] = (mask1 & mask2);
  } else {
   Discs[radius][byte1] = mask1;
   Discs[radius][byte2] = mask2;
 }
}

pset(radius,x,y)
int radius,x,y;
{
  int i,j,byte;
  UBYTE mask;

  byte = y*CircBytes[radius] + x/8;
  mask = 1 << (7-(x % 8));
  Circs[radius][byte] = Circs[radius][byte] | mask;
}

/*=======================================================================*/
/* Open audio device, and load noise files */
/* Loosely based on a snippet of code by J.Bickers */
initsnd()
{
  char noisename[30];
  int  i;

  for (maxnoise=0;maxnoise<NUMSOUNDS;maxnoise++)
    noisesize[maxnoise]=0;
  maxnoise=0;
  if ((audIO = (struct IOAudio *)AllocMem((long)sizeof(struct IOAudio),MEMF_PUBLIC|MEMF_CLEAR))==NULL)
    myexit("failed to get IO struc");
  if ((noiselist=fopen("caterpilla.snd","r"))==NULL)
    printf("file caterpilla.snd not found, so no sound effects\n");
  else {
    while (feof(noiselist)==0) {
      if (fgets(&noisename,30,noiselist)!=NULL) {
        for (i=0;(i<30) && (noisename[i]!='\0');i++) {
          if (noisename[i]=='\n') noisename[i]='\0';
        }
        if ((noisefile=fopen(&noisename,"r"))==NULL)
          myexit("failed to open a noise file");
        if (fseek(noisefile,0L,2)!=0)
          myexit("failed on seek eof noisefile");
        noisesize[maxnoise]=ftell(noisefile);
        if (fseek(noisefile,0L,0)!=0)
          myexit("failed on reset to start of file");

        if ((auddat[maxnoise] =
             (UBYTE *)AllocMem((LONG)noisesize[maxnoise],MEMF_CHIP))==NULL)
          myexit("failed to alloc auddat");
        if (fread(auddat[maxnoise],1,(noisesize[maxnoise]-1),noisefile)==0)
          myexit("read noise failed");
        fclose(noisefile);
        noisefile=NULL;
        maxnoise++;
      }
    };
    fclose(noiselist);
  };

  audIO->ioa_Length = 4;
  audIO->ioa_Data = allocmap;
  audIO->ioa_Request.io_Message.mn_Node.ln_Pri = 127;

  if ((audport = CreatePort("Caterpilla snd",0))==NULL)
    myexit("failed to create port");

  audIO->ioa_Request.io_Message.mn_ReplyPort = audport;

  if (OpenDevice(AUDIONAME,0L,audIO,0L)!=0)
    myexit("Open audio device failed");
  gotdev = 1;         /* So freesnd() knows to close device */

}


snd(smode)
/* make a noise, abort any noise in progress */
UWORD smode;
{
  if (maxnoise!=0) {
        if (!CheckIO(audIO) && noisy==1) {
          AbortIO(audIO);
        };
        audIO->ioa_Request.io_Command = CMD_WRITE;
        audIO->ioa_Request.io_Flags = ADIOF_PERVOL;
        audIO->ioa_Data = auddat[(smode % maxnoise)];
        audIO->ioa_Length = noisesize[(smode % maxnoise)];
        if (smode==6)
          audIO->ioa_Volume = 64;
        else
          audIO->ioa_Volume = 32;
        audIO->ioa_Period = 447;    /* sampling period between our samples*/
        audIO->ioa_Cycles = 1;      /* set to zero to get cont. replay */
        BeginIO(audIO);             /* initiate a noise */
        noisy=1;
  }
}

/* Audio device exit and cleanup stuff */
freesnd()
{
int noisenum;
    if (noisefile!=NULL) fclose(noisefile);
    if (noiselist!=NULL) fclose(noiselist);
    if (gotdev) CloseDevice(audIO);
    if (audport) DeletePort(audport);
    for (noisenum=0;noisenum<maxnoise;noisenum++) {
      if (noisesize[noisenum]!=0)
        if (auddat[noisenum]) FreeMem(auddat[noisenum],(LONG)noisesize[noisenum]);
    }
    if (audIO) FreeMem(audIO,(long)sizeof(*audIO));
}

/*=======================================================================*/

/* Error, report and cleanup routine */
myexit(AbortMsg)
UBYTE * AbortMsg;
{
  int rad, win;

  if (AbortMsg      != NULL) printf("%s\n",AbortMsg);
  freesnd();
  for (rad=1; rad<MAXRAD; rad++ ) {
    if (Discs[rad] != NULL)
      FreeMem(Discs[rad], ((2*rad+1)*DiscBytes[rad]) );
    if (Circs[rad] != NULL)
      FreeMem(Circs[rad], ((2*rad+1)*CircBytes[rad]) );
  }
  for (win=0;win<MAXWIN;win++)
    if (MyWindow[win]!=NULL) CloseWindow(MyWindow[win]);
  if (MyScreen      != NULL) CloseScreen(MyScreen);
  if (GfxBase       != NULL) CloseLibrary(GfxBase);
  if (IntuitionBase != NULL) CloseLibrary(IntuitionBase);
  exit(FALSE);
}

