/*
 *   What fun!  A new LIFE program by Tomas Rokicki.  Commented rather
 *   erratically; hell, this is all just one big hack.
 *
 *   If you are adding this algorithm to another LIFE program, please
 *   include life68.asm, memcpy.asm, and the section of this main life.c
 *   indicated below by CUT HERE.
 */
#include "structures.h"
long shsize, svsize ;
long smodulo ;
long displayoffset ; /* where do we display?  offset in long words */
short *a ;
short torus ;
struct GfxBase *GfxBase = NULL ;     /* the GfxBase */
struct IntuitionBase *IntuitionBase = NULL ; /* the IntuitionBase */
struct Screen *myscreen = NULL ;
struct Window *mywindow = NULL ;
struct TextAttr myfont = {(STRPTR)"topaz.font", TOPAZ_EIGHTY, 0, 0 };
struct NewScreen mynewscreen = {0, 0, 0, 0, 0, 1, 2, 0,
   CUSTOMSCREEN, &myfont, (UBYTE *)"AmigaLIFE -- Radical Eye Software"} ;
/*
 *   Let's use a borderless window too, so we can get vanilla
 *   keys.  This gives us a nicer way to exit and adjust the
 *   speed of the program.  But we still play with the screen
 *   bitmaps.
 */
static struct NewWindow mynewwindow = { 0, 0, 0, 0, 0, 1,
   VANILLAKEY | RAWKEY,
   SIMPLE_REFRESH | ACTIVATE | NOCAREREFRESH | BORDERLESS | BACKDROP,
   NULL, NULL, NULL, NULL, NULL, -1, -1, -1, -1, CUSTOMSCREEN } ;
/*
 *   This routine gets a raster for temporary storage.
 */
void error(s)
char *s ;
{
   printf("life: %s\n", s) ;
   if (*s == '!')
      cleanup() ;
}
void *mymalloc(size, type)
long type, size ;
{
   void *AllocMem() ;
   void *p ;

   if ((p=AllocMem(size, type | MEMF_CLEAR))==NULL) {
      error("! could not allocate raster data") ;
   }
   return(p) ;
}
/*
 *   This is the start of the assembly code that needs to be retained
 *   if these algorithms are being adapted to another program.  You may
 *   also want to pull in some of the above housekeeping stuff.
 *
 *   CUT HERE
 *
 *   I believe all necessary global variables are here in asm_info.
 */
struct asm_info {
   long hsize, vsize ;
   long memsize ;
   long modulo ;
   long ksize ;
   long *f1, *f2 ;
   long **accel ;
   long *tsums ;
} asm_info ;
/*
 *   Call this to allocate the two LIFE playfields.  Note that the actual
 *   LIFE universe will be (hsize-2) x (vsize-2); the borders are used
 *   for torus wrapping.
 */
void initasm(hsize, vsize)
long hsize, vsize ;
{
   long memsize ;
   long i ;

   asm_info.hsize = hsize ;
   asm_info.vsize = vsize ;
   asm_info.modulo = ((hsize + 31) & ~31) >> 3 ;
   if (hsize > 32764L * 256L || vsize > 65535L)
      error("! field size limited to 262,112 x 65535") ;
   asm_info.ksize = ((hsize + 1023) & ~1023) >> 10 ;
   memsize = asm_info.vsize * asm_info.modulo ;
   asm_info.f1 = mymalloc(memsize, MEMF_CLEAR) ;
   asm_info.f2 = mymalloc(memsize, MEMF_CLEAR) ;
   asm_info.accel = mymalloc(4L * (vsize + 5) * (asm_info.ksize + 2),
                                MEMF_CLEAR) ;
   asm_info.tsums = mymalloc(6L * asm_info.modulo, MEMF_CLEAR) ;
   for (i=0; i<asm_info.ksize + 2; i++) {
      asm_info.accel[i] = (long *)
         (asm_info.accel + asm_info.ksize + 3 + i * (vsize + 3L)) ;
      asm_info.accel[i][vsize + 3L] = -1 ;
   }
}
/*
 *   This deallocates the memory allocated above.
 */
void cleanasm() {
   long memsize ;
   long i ;

   memsize = asm_info.vsize * asm_info.modulo ;
   if (asm_info.f1) {
      FreeMem(asm_info.f1, memsize) ;
      asm_info.f1 = 0 ;
   }
   if (asm_info.f2) {
      FreeMem(asm_info.f2, memsize) ;
      asm_info.f2 = 0 ;
   }
   if (asm_info.accel) {
      FreeMem(asm_info.accel, 4L * (asm_info.vsize + 5) *
                                                      (asm_info.ksize + 2)) ;
      asm_info.accel = 0 ;
   }
   if (asm_info.tsums) {
      FreeMem(asm_info.tsums, 6L * asm_info.modulo) ;
      asm_info.tsums = 0 ;
   }
}
/*
 *   Now we handle wrapping/clipping.  You must call this routine after
 *   setting up the initial pattern to run, or after making any changes
 *   to the array that might affect the border (such as after a user edit.)
 *   Note that it is somewhat expensive and should not be called lightly.
 *
 *   This routine is idempotent.
 *
 *   If there is no torus wrap, we simply clear out the extreme edges.
 *   No changes need to be made to the modified array.
 *
 *   If there is torus wrap, things are just slightly more difficult.
 *   We need to copy the second row to the bottom, and the penultimate
 *   row to the top.  We also need to copy the second column to the last
 *   and the penultimate to the first.
 *
 *   After doing this, we also have to update our modified array.  We
 *   handle the top and bottom in the same way as we did for the actual
 *   bit array, except instead of copying, we `or'.    Horizontally we have
 *   a more complicated situation.  We
 *   handle this by `or'ing the leftmost column with the rightmost column
 *   and putting this result back into both columns.  If the size is
 *   1 mod 32, then we have a slightly more complex situation; for any
 *   changed first column, we need to set the modified bit for the last
 *   *two* columns (but not the other way around.)  Again, this only needs
 *   to be done for widths of 33, 65, 97, etc.
 *
 *   C for now, could be in assembly later.  Turns out MANX does a fair
 *   job with this (although you should check the assembly code generated
 *   for the loops just to make sure it is fair.)  This code is inner loop.
 *
 *   Call with torus=1 for torus wrapping, and 0 otherwise.
 */
asmwrapclip(torus)
int torus ;
{
   register long *s, *d, *d2 ;
   register long m = asm_info.modulo ;
   register long b1, b2 ;
   register long i ;
   register long b3 ;
   long v = asm_info.vsize ;

   b1 = 0x80000000 ;
   b2 = 1L << (31 - ((asm_info.hsize - 1) & 31)) ;
   s = asm_info.f2 ;
   if (!torus) {
/*
 *   These two should not be necessary; test this.  (Except for at the
 *   beginning.)
 */
      ClearMemQuick(((char *)s) + m * (asm_info.vsize - 1), m) ;
      ClearMemQuick(((char *)s), m) ;
      d = (long *)(((char *)s) + m - 4) ;
      b1 = ~b1 ;
      b2 = ~b2 ;
      for (i=v; i; i--) {
         *s &= b1 ;
         *d &= b2 ;
         s = (long *)(((char *)s) + m) ;
         d = (long *)(((char *)d) + m) ;
      }
   } else {
      CopyMemQuick(((char *)s) + m, ((char *)s) + m * (asm_info.vsize - 1), m) ;
      CopyMemQuick(((char *)s) + m * (asm_info.vsize - 2),
                   ((char *)s), m) ;
      d = (long *)(((char *)s) + m - 4) ;
      b1 = b2 << 1 ;
      if (b1 == 0) {
         b1 = 1 ;
         d2 = d - 1 ;
      } else {
         d2 = d ;
      }
      b3 = - b2 ;
      for (i=v; i; i--) {
         if (*s & 0x40000000)
            *d |= b2 ;
         else
            *d &= ~b2 ;
         if (*d2 & b1)
            *s |= 0x80000000 ;
         else
            *s &= 0x7fffffff ;
         *d &= b3 ;
         s = (long *)(((char *)s) + m) ;
         d = (long *)(((char *)d) + m) ;
         d2 = (long *)(((char *)d2) + m) ;
      }
      for (i=0; i<asm_info.ksize; i++) {
         s = asm_info.accel[i] ;
         s[1] |= s[v-2] ;
         s[v-2] |= s[1] ;
      }
      s = asm_info.accel[0] ;
      d = asm_info.accel[asm_info.ksize-1] ;
      b1 = 1L << (31 - (((asm_info.hsize - 1) >> 5) & 31)) ;
      b2 = b1 ;
      d2 = 0 ;
      if ((asm_info.hsize & 31) == 1) {
         if (b2 < 0) {
            b2 = 1 ;
            d2 = asm_info.accel[asm_info.ksize-2] ;
         } else {
            b2 += (b2 << 1) ;
         }
      }
      if (d2 == 0) {
         for (m=v; m; m--) {
            if (*s & 0x80000000)
               *d |= b2 ;
            else if (*d & b2)
               *s |= 0x80000000 ;
            s++ ;
            d++ ;
         }
      } else {
         for (m=v; m; m--) {
            if (*s & 0x80000000) {
               *d |= b1 ;
               *d2 |= b2 ;
            }
            if ((*d & b1) || (*d2 & b2))
               *s |= 0x80000000 ;
            s++ ;
            d++ ;
            d2++ ;
         }
      }
   }
}
/*
 *   This routine handles the fact that we split things into 1024 bit wide
 *   swaths.  You call this to actually compute the next generation.
 */
void doasmgen() {
   register long *r = asm_info.f1 ;
   register char **rr ;
   long *r2, *r3, *m ;
   long i, k ;
   long *f1, *f2 ;
   int j ;

   asm_info.f1 = asm_info.f2 ;
   asm_info.f2 = r ;
   r = asm_info.accel[asm_info.ksize] ;
   f1 = asm_info.f1 ;
   f2 = asm_info.f2 ;
   r2 = asm_info.accel[asm_info.ksize+1] ;
   r3 = r ;
   j = 128 ;
   for (i=0; i<asm_info.ksize; i++) {
      if (i + 1 == asm_info.ksize)
         j = ((asm_info.modulo - 1) & 127) + 1 ;
      m = asm_info.accel[i] ;
      m[0] = 0 ;
      m[asm_info.vsize-1] = 0 ;
      dogen(f1, f2, (int)j, (unsigned int)(asm_info.vsize),
         m, asm_info.tsums, r, r2, asm_info.modulo) ;
      r = r2 ;
      r2 = r3 ;
      r3 = r ;
      r = m ;
      if (i > 0) {
         k = (long)m - (long)(asm_info.accel[i-1]) ;
         for (rr = (char **)r2; *rr; rr++)
            (*rr)[k] |= 128 ;
      }
      f1 += 32 ;
      f2 += 32 ;
   }
   asmwrapclip(torus) ;
}
/*
 *   This routine takes care of setting a bit, even updating the
 *   modified arrays.  You are responsible for calling this array
 *   for each set bit in the displayed picture.  If you do not want
 *   to do that, simply store the picture in both fields (f1/f2) in
 *   asm_info, and then set all of the accel[i][j], 0<=i<asm_info.ksize,
 *   0<=j<asm_info.vsize to -1.  This will cause the first generation
 *   to take a lot longer.  The example code is in main(), below.
 *
 *   This routine does no bounds checking; you are responsible for
 *   making sure that
 *
 *      1 <= x < asm_info.hsize-1
 *      1 <= y < asm_info.vsize-1
 *
 *   That's right, you cannot set `0' or `asm_info.hsize-1' horizontally
 *   or the corresponding ones vertically; these are our one-pixel border
 *   region which needs to be preserved.  The checking is not done because
 *   there is some code below that needs to call set with values out of
 *   range by 1 in order to update the torus stuff properly.
 *
 *   After setting all the necessary bits, call asmwrapclip().  If you
 *   don't want to do that and you are not setting that many bits, see
 *   the example code in main() where we set a `random' pixel for how
 *   to fix up the border for the case of torus wrapping.
 *
 *   Note that any modifications must be made to both fields, and that
 *   the accel[] arrays must be updated accordingly.  This code does
 *   that.
 */
set(x, y)
register long x, y ;
{
   register long off, bit ;

   bit = 1L << (31 - (31 & x)) ;
   off = ((y * asm_info.modulo) >> 2) + (x >> 5) ;
   asm_info.f1[off] |= bit ;
   asm_info.f2[off] |= bit ;
   off = (((x + 1) & 31) <= 1) ;
   x = (x - 1) >> 5 ;
   bit = 1L << (31 - (31 & x)) ;
   if (x >= 0)
      asm_info.accel[x >> 5][y] |= bit ;
   if (off) {
      x++ ;
      bit = 1L << (31 - (31 & x)) ;
      x >>= 5 ;
      if (x < asm_info.ksize)
         asm_info.accel[x][y] |= bit ;
   }
}
/*
 *   That's all!  The rest is `example' code.
 *
 *   CUT HERE
 */
/*
 *   This routine is an example of how you might use updaterect (in
 *   memcpy.asm) to update the displayed screen.  It allows the
 *   displayed screen to be much smaller than the LIFE fields.
 */
void updatescreen() {
   register long *s, *d ;
   register long bw, bh ;

   s = asm_info.f2 + displayoffset ;
   d = (long *)a ;
   bw = ((shsize + 15) >> 3) & ~1 ;
   if (bw > asm_info.modulo) {
      bw = asm_info.modulo ;
   } else {
      if (((smodulo & 2) == 0) &&
          ((bw & 2) == 2))
         bw += 2 ;
   }
   bh = svsize ;
   if (bh > asm_info.vsize) {
      bh = asm_info.vsize ;
   }
   updaterect(s, asm_info.modulo, d, smodulo, bw, bh) ;
}
/*
 *   An example initialize routine that opens a screen, etc.
 */
initialize(hsize, vsize)
long hsize, vsize ;
{
   long color ;

   if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary(
      "intuition.library",0L))==NULL ||
       (GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0L))
      ==NULL) {
      error("! no libraries") ;
   }
   if ((myscreen = OpenScreen(&mynewscreen))==NULL) {
      error("! no screen") ;
   }
   mynewwindow.Screen = myscreen ;
   if ((mywindow = OpenWindow(&mynewwindow))==NULL) {
      error("! no window") ;
   }
   a = ((short *)(myscreen->BitMap.Planes[0])) ;
   initasm(hsize, vsize) ;
}
/*
 *   Exit routine.
 */
cleanup() {
   if (mywindow != NULL)
      CloseWindow(mywindow) ;
   if (myscreen != NULL)
      CloseScreen(myscreen) ;
   if (IntuitionBase)
      CloseLibrary(IntuitionBase) ;
   IntuitionBase = NULL ;
   if (GfxBase)
      CloseLibrary(GfxBase) ;
   GfxBase = NULL ;
   cleanasm() ;
   exit(0) ;
}
/*
 *   Does one LIFE generation.  Updates the screen if desired.
 */
int curdis ;
int mcurdis ;
dogeneration() {
   doasmgen() ;
   if (curdis <= 0) {
      updatescreen() ;
      curdis = mcurdis ;
   }
   curdis-- ;
}
/*
 *   Random number generator; probably not a very good one.
 */
long rnd(i)
long i ;
{
   static long seed = 323214521 ;
   long rval ;

   seed = seed * 123213 + 121 ;
   rval = (seed >> 5) & 0x7ffffff ;
   return rval % i ;
}
/*
 *   Main routine.  If called with no arguments, makes 1 bit plane screen.
 *   Otherwise, first argument is used as the number of bit planes.
 */
char buffer[200] ;
long time1[3], time2[3] ;
long ratio(a,b,c)
register long a, b, c ;
{
   while (a > 32768L) {
      a /= 2 ;
      c /= 2 ;
   }
   while (b > 32768L) {
      b /= 2 ;
      c /= 2 ;
   }
   if (c == 0)
      return(0) ;
   else
      return(a * b / c) ;
}
long maxgen = 2000000000 ;
main (argc, argv)
int argc ;
char *argv[] ;
{
   register long x, y ;
   int randoms = 0 ;
   int ir ;
   long dvsize = 0 ;
   struct IntuiMessage *message ;
   int code ;
   long delayval = 0 ;
   long generations ;
   long t ;
   short *wh ;
   int quals ;
   long xs, xm ;
   long ys, ym ;
   int needredisplay = 0 ;
   long vsize = 200, hsize = 320 ;

   printf("LIFE 6.1, Copyright 1993 Radical Eye Software\n") ;
   while (argc > 1 && argv[1][0]=='-') {
      argc-- ;
      argv++ ;
      if (argv[0][1]=='h' || argv[0][1]=='H') {
         if (sscanf(argv[0]+2, "%ld", &hsize)!=1)
            hsize = 640 ;
      } else if (argv[0][1]=='r' || argv[0][1]=='R') {
         if (sscanf(argv[0]+2, "%d", &randoms)!=1)
            randoms = 1 ;
      } else if (argv[0][1]=='d' || argv[0][1]=='D') {
         if (sscanf(argv[0]+2, "%d", &mcurdis)!=1)
            mcurdis = 1 ;
      } else if (argv[0][1]=='m' || argv[0][1]=='M') {
         if (sscanf(argv[0]+2, "%ld", &maxgen)!=1)
            maxgen = 2000000000 ;
      } else if (argv[0][1]=='t' || argv[0][1]=='T') {
         torus = 1 ;
      } else if (argv[0][1]=='v' || argv[0][1]=='V') {
         if (sscanf(argv[0]+2, "%ld", &dvsize)!=1)
            dvsize = 0 ;
      } else if (argv[0][1]=='s' || argv[0][1]=='S') {
         delayval = -1 ;
      }
   }
   if (argc > 1) {
      if (argv[1][0]=='?' && argv[1][1]==0) {
         printf(
   "Usage:  life [-h[n]] [-r[n]] [-p[n]] [-o] [-t] [-v[n]] [-s] [infile]\n") ;
         cleanup() ;
      }
      if (freopen(argv[1], "r", stdin)==NULL) {
         printf("Couldn't open %s\n", argv[1]) ;
         cleanup() ;
      }
   }
   if (hsize > 400) {
      vsize = 400 ;
      mynewscreen.ViewModes |= HIRES ;
   } else {
      vsize = 200 ;
   }
   ir = randoms ;
   if (dvsize >= 10)
      vsize = dvsize ;
   shsize = hsize ;
   if (shsize > 720)
      shsize = 704 ;
   svsize = vsize ;
   if (svsize > 470)
      svsize = 470 ;
   mynewscreen.Depth = 1 ;
   mynewscreen.Width = shsize ;
   mynewscreen.Height = svsize ;
   mynewwindow.Width = shsize ;
   mynewwindow.Height = svsize ;
   if (vsize > 300)
      mynewscreen.ViewModes |= LACE ;
   initialize(hsize, vsize) ;
   WaitBlit() ;
   smodulo = myscreen->BitMap.BytesPerRow ;
   xs = 0 ;
   ys = 0 ;
   xm = (hsize - shsize + 31) >> 5 ;
   ym = vsize - svsize ;
   SetAPen(&myscreen->RastPort, 0L) ;
   RectFill(&myscreen->RastPort, 0L, 0L, shsize-1, svsize-1) ;
   WaitBlit() ;
   wh = (short *)(asm_info.f1) ;
   readin(wh, hsize, vsize) ;
/*
 *   Do this sort of thing if you do not set all of the bits in the array
 *   with the supplied set().  This makes the first generation somewhat
 *   slow, but it will still work . . .
 */
/* {
      int i, j ;
      long *r ;

      for (j=0; j<asm_info.ksize; j++)
         for (i=0, r=asm_info.accel[j]; i<vsize; i++, r++)
            *r = -1 ;
   } */
   asmwrapclip(torus) ;
   updatescreen() ;
   WaitBlit() ;
   generations = 0 ;
   DateStamp(time1) ;
   while (1) {
      while (message = (struct IntuiMessage *)GetMsg(mywindow->UserPort)) {
         code = message->Code ;
         quals = message->Qualifier ;
         if (message->Class == RAWKEY)
            code += 256 ;
         ReplyMsg(message) ;
         quals = ((quals & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT)) != 0) +
                   2 * ((quals & (IEQUALIFIER_CONTROL)) != 0) ;
         switch(code) {
case 27 : case 3 : case 'q' : case 'Q' : case 'x' : case 'X' :
            goto done ;
case '0' : case 'g' : case 'G' :
            curdis = 0 ;
            mcurdis = 0 ;
            delayval = 0 ;
            break ;
case '1' : case '2' : case '3' : case '4' :
case '5' : case '6' : case '7' : case '8' : case '9' :
            delayval = 1L << (code - '0') ;
            curdis = 0 ;
            mcurdis = 0 ;
            break ;
case 256+80: case 256+81: case 256+82: case 256+83: case 256+84:
case 256+85: case 256+86: case 256+87: case 256+88:
            mcurdis = 5L << (code - 256 - 80) ;
            curdis = mcurdis ;
            delayval = 0 ;
            break ;
case 256+89:
            xs = xm / 2 ;
            ys = ym / 2 ;
            goto refresh ;
case 0x14c: /* up */
            ys -= 256 >> quals ;
            if (ys < 0)
               ys = 0 ;
            goto refresh ;
case 0x14d: /* down */
            ys += 256 >> quals ;
            if (ys > ym)
               ys = ym ;
            goto refresh ;
case 0x14e: /* forward */
            xs += 8 >> quals ;
            if (xs > xm)
               xs = xm ;
            goto refresh ;
case 0x14f: /* back */
            xs -= 8 >> quals ;
            if (xs < 0)
               xs = 0 ;
refresh:
            displayoffset = xs + ys * (asm_info.modulo >> 2) ;
            needredisplay = 1 ;
            break ;
case ' ' :
            curdis = 0 ;
            mcurdis = 0 ;
            delayval = -1 ;
            goto doone ;
case 'S' : case 's' :
            curdis = 0 ;
            mcurdis = 0 ;
            delayval = -1 ;
default :
            break ;
         }
      }
      if (needredisplay) {
         updatescreen() ;
         needredisplay = 0 ;
         continue ;
      }
      if (delayval) {
         if (delayval == -1)
            continue ;
         Delay(delayval) ;
      }
doone:
      if (randoms) {
         if (--ir <= 0) {
            x = rnd(hsize-2) + 1 ;
            y = rnd(vsize-2) + 1 ;
            set(x, y) ;
/*
 *   If you don't want to call asmwrapclip() because it is so expensive, you
 *   can do something like this after calling set().  It properly checks for
 *   and sets the border pixels if required.
 */
            if (torus) {
               if (x == 1)
                  set(asm_info.hsize-1, y) ;
               else if (x == asm_info.hsize - 2)
                  set(0L, y) ;
               if (y == 1)
                  set(x, asm_info.vsize-1) ;
               else if (y == asm_info.vsize - 2)
                  set(x, 0L) ;
            }
            ir = randoms ;
         }
      }
      dogeneration() ;
      generations++ ;
      if (generations > maxgen)
         break ;
   }
done:
   DateStamp(time2) ;
   delayval = time2[2]-time1[2] + (time2[1]-time1[1]) * 3000L
              + (time2[0]-time1[0]) * 4320000L ;
   t = (hsize - 2L) * (long)(vsize - 2L) * 50L ;
   t = ((t / delayval) * generations +
        ratio(t % delayval, generations, delayval)) ;
   printf(
 "%ld gen's by %ld pixels in %ld ticks is %ld gen's and %ld cells/sec\n",
           generations, (hsize-2)*(long)(vsize-2), delayval,
           (generations * 50L + delayval / 2) / delayval, t) ;
   cleanup() ;
}
