/*                        ______________________
                         /
                        /   Kaleidoscope           ____________________
 ______________________/            by            /
                                   Greg Searle   /
                          ______________________/

   Creates a pretty, moving pattern on a lowres HAM screen.  Colors change
smoothly as the pattern is drawn, randomly moving through all 4096 colors.
This program uses the famous "bouncing ball" to guide the pattern.  A moving
point follows a straight line until it hits the edge of the screen, where it
"bounces off" somewhat randomly.

   This program multitasks fine.  The program is active only once per screen
refresh (60 times per second), leaving plenty of time for other programs.  The
regular screen gadgets (dragbar, close box, layering gadgets) are hidden under
the pattern.  Just hit the close box at the upper-right to shut down the
program.

*/
#include <exec/types.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <intuition/intuition.h>

struct GfxBase        *GfxBase;
struct IntuitionBase  *IntuitionBase;
struct BitMap         *bmap;
struct RastPort       *rp;
struct ViewPort       *vp;
struct Screen         *scr;
struct Window         *win;

struct NewScreen screen = {
   0, 0, 0, 0, 0,              /* LeftEdge, TopEdge, Width, Height, Depth */
   -1, -1,                    /* DetailPen, BlockPen (default)           */
   0, CUSTOMSCREEN,          /* ViewModes, Type                         */
   NULL, NULL,              /* *Font (default), *DefaultTitle          */
   NULL, NULL              /* *Gadgets, *CustomBitMap                 */
};

#define WFLAGS  WINDOWCLOSE | SIMPLE_REFRESH | ACTIVATE | \
                BORDERLESS | BACKDROP | RMBTRAP
#define WIDCMP  CLOSEWINDOW

struct NewWindow window = {   /* This will only create a close box        */
   0, 0, 13, 10,             /* LeftEdge, TopEdge, Width, Height         */
   -1, -1,                  /* DetailPen, BlockPen (default)            */
   WIDCMP,                 /* IDCMPFlags                               */
   WFLAGS,                /* Flags                                    */
   NULL, NULL,           /* *FirstGadget, *CheckMark                 */
   NULL, NULL, NULL,    /* *Title, *Screen (to be set), *BitMap     */
   0, 0, 0, 0,         /* MinWidth, MinHeight, MaxWidth, MaxHeight */
   CUSTOMSCREEN       /* Type                                     */
};

#define WIDTH     320
#define HEIGHT    200
#define DEPTH     6
#define LENGTH    60
#define LINES     3

short fromCLI = 0;

main (argv, argc)
  char *argv[];
  long argc;
{
   short          error;
   long           x[LINES], dx[LINES], xx, yy, i, j, k, l, cc = 0;
   float          y[LINES], dy[LINES], p[3][LINES], dp[3][LINES];

   struct IntuiMessage *message, *GetMsg();

   fromCLI = argc;
   if (error = OpenLibraries()) GoAway(error);
   if (error = CreateScreen("Screen", WIDTH, HEIGHT, DEPTH, (short)HAM))
       GoAway(error);
   ShowTitle(scr, FALSE);
   SetRGB4 (vp, 0L, 0L, 0L, 0L);
   SetRast (rp, 0L);
   SetDrMd (rp, JAM1);

   rnd ((short) -VBeamPos());    /*  Init random number generator */
   for (l = 0; l < LINES; l++) {
      x[l]  = rnd (WIDTH);
      y[l]  = rnd (HEIGHT);
      dx[l] = rnd(1) * 2 - 1;        /* x changes only by one, */
      do                            /* keeping pattern smooth */
         dy[l] = (float)(rnd(23) - 11) / 6;
      while (dy[l] == 0);
                                  /* Init colors                          */
      for (i = 0; i < 3; i++) {  /* Uses "bouncing ball" technique to    */
         p[i][l] = rnd(15);     /* change colors smoothly, yet randomly */
         do
            dp[i][l] = (float)(rnd(21) - 10) / 3;
         while (dp[i][l] == 0.0);
      }
   }

   /********** MAIN LOOP **********/

   while (!GetMsg(win -> UserPort)) {

      /* Set current color, then increment colors for each line.        */
     /*  R, G, and B are controlled by "bouncing" vectors.             */
    /*   Once they reach their limit, they reverse at a random speed. */

      WaitTOF();      /* Delay - save multitasking */

      if (++cc > 2) cc = 0;
      for (l = 0; l < LINES; l++) {

         SetAPen (rp, cc * 16L + 16L + (long) p[cc][l]);
         if (!cc) {
            for (i = 0; i < 3; i++) {
               if ((p[i][l] += dp[i][l]) > 15  ||  p[i][l] < 0) {
                  if (p[i][l] > 15.0)
                     p[i][l] = 30.0 - p[i][l];
                  else
                     p[i][l] = -p[i][l];
                  do
                     dp[i][l] = ((p[i][l] < 8) ? 1 : -1) * (float)(rnd(10) + 1) / 3;
                  while (dp[i][l] == 0.0);
               }
            }
         }

       /* Draw:  first x,y & mirrors, then y,x & mirrors.                 */
      /*  i & j are y and x converted into x and y, respectively.        */
     /*   This allows use of the full screen, instead of just a square. */
   
         i = (long)(y[l] / (float)HEIGHT * (float)WIDTH);
         j = (long)((float)x[l] / (float)WIDTH  * (float)HEIGHT);
         xx =  WIDTH - x[l];
         yy = HEIGHT - (long)y[l];
         Line (rp,  x[l],  (long)y[l]-LENGTH/2,  x[l],  (long)y[l]+LENGTH/2);
         Line (rp, xx,  (long)y[l]-LENGTH/2, xx,  (long)y[l]+LENGTH/2);
         Line (rp,  x[l], yy-LENGTH/2,  x[l], yy+LENGTH/2);
         Line (rp, xx, yy-LENGTH/2, xx, yy+LENGTH/2);
   
         xx =  WIDTH - i;
         yy = HEIGHT - j;
         for (k = 0; k < 3; k++) {
            SetDrPt (rp, 0x92492492 >> k);
            SetAPen (rp, k * 16L + 16L + (long) p[k][l]);
            Line (rp,  i-LENGTH/2,  j,  i+LENGTH/2,  j);
            Line (rp, xx-LENGTH/2,  j, xx+LENGTH/2,  j);
            Line (rp,  i-LENGTH/2, yy,  i+LENGTH/2, yy);
            Line (rp, xx-LENGTH/2, yy, xx+LENGTH/2, yy);
         }
         SetDrPt (rp, 0xFFFFFFFF);

         /* Increment x,y.  Check if bounced off edge. */

         if ((x[l] += dx[l]) < 0  ||  x[l] > WIDTH) {
            x[l] -= dx[l];
            dx[l] = -dx[l];
         }
         if ((y[l] += dy[l]) < 0  ||  y[l] > (float)HEIGHT) {
            y[l] -= dy[l];
            dy[l] = -dy[l];
            do
               dy[l] = ((dy[l]<0) ? -1:1) * (float)(rnd(23) - 11) / 6;
            while (dy[l] == 0);
         }
      }  /* for l */

   }  /* while */

   GoAway(0);
}  /* main */

Line (rp, x1, y1, x2, y2)   /* Just draws a line and checks for edges */
  struct RastPort *rp;
  long x1, y1, x2, y2;
{

   if (x1 < 0) x1 = 0;
      else if (x1 > WIDTH-1) x1 = WIDTH-1;
   if (y1 < 0) y1 = 0;
      else if (y1 > HEIGHT-1) y1 = HEIGHT-1;

   if (x2 < 0) x2 = 0;
      else if (x2 > WIDTH-1) x2 = WIDTH-1;
   if (y2 < 0) y2 = 0;
      else if (y2 > HEIGHT-1) y2 = HEIGHT-1;

   Move (rp, x1, y1);
   Draw (rp, x2, y2);
}

short OpenLibraries()
{
   void *OpenLibrary();

   if (!(IntuitionBase = (struct IntuitionBase *)
      OpenLibrary ("intuition.library", 1L))) return 1;

   if (!(GfxBase = (struct GfxBase *)
      OpenLibrary ("graphics.library", 1L))) return 2;

   return 0;
}  /* OpenLibraries */

short CreateScreen (title, width, height, depth, modes)
  char title[];
  short width, height, depth, modes;
{
   struct Screen *OpenScreen();
   struct Window *OpenWindow();

   screen.Width      = width;
   screen.Height     = height;
   screen.Depth      = depth;
   screen.ViewModes  = modes | ((width  > 320) ? HIRES:0)
                             | ((height > 200) ?  LACE:0);

   if (!(scr = OpenScreen (&screen))) return 3;
   window.Screen = scr;
   if (!(win = OpenWindow (&window))) return 4;

   vp = &(scr -> ViewPort);
   rp = &(scr -> RastPort);
   return 0;
}  /* CreateScreen */

GoAway(error)
  short error;
{
   Report (error);
   if (IntuitionBase) CloseLibrary (IntuitionBase);
   if (GfxBase)       CloseLibrary (GfxBase);
   if (win)           CloseWindow (win);
   if (scr)           CloseScreen (scr);
   exit (0L);
}  /* GoAway */

Report (error)
  short error;
{
   if (!error) return;
   if (!fromCLI)
      DisplayBeep(NULL);
   else
      switch (error) {
         case 1:  printf("Can't open Intuition.\n");  break;
         case 2:  printf("Can't open Graphics.\n");   break;
         case 3:  printf("Can't open Screen.\n");     break;
         case 4:  printf("Can't open Window.\n");     break;
         default: printf("Undefined error.\n");       break;
      }  /* switch */
}  /* Report */

#asm

*\
*  :ts=8
* Yet Another random number generator.  By Leo Schwab.
* Based on an idea posted on the USENET (Thanks, Sam Dicker!)
* For the Manx assembler.
*
* Calling convention:
*  short rnd (range);
*  short range;
*
* 8606.30
*/

      public    _rnd

_rnd      lea   rndseed,a0   ;Get address of seed
      move.w   4(sp),d1   ;Get range argument
      tst.w   d1
      ble.s   setseed      ;Go reset seed


      move.l   (a0),d0      ;Get seed
      ADD.L   D0,D0
      BHI.S   over
      EORI.L  #$1D872B41,D0
over
      move.l   d0,(a0)      ;Save new seed
      andi.l   #$ffff,d0   ;Coerce into word
      divu   d1,d0      ;Divide by range
      swap   d0      ; and get remainder (modulus)
      rts

setseed      neg.w   d1      ;Probably don't need this
      move.l   d1,(a0)
      rts

      dseg
rndseed      dc.l   0
      cseg
#endasm
