/*****************************************************************
** Snake.c - KABjorke 30 March 87				**
** © 1987, National Pixel Products				**
** Released to the Public Domain for Non-Commercial Purposes.	**
**	A variant on the old "bouncing lines" program.		**
**	This version uses multiple vertices and Bezier splines.	**
*****************************************************************/

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <intuition/intuition.h>
#include <graphics/gfx.h>
#include <graphics/text.h>

#define MAXVERT 15

#define BGPEN 0
#define FGPEN 1
#define LOPEN 2
#define HIPEN 3

/* Event() return codes */
#define DUNNO -1
#define RESIZED 1

#define TRAIL (Trails.Flags&CHECKED)
#define POLYS (ShoPoly.Flags&CHECKED)

/****************************************************************/
/******* Built-Ins **********************************************/
/****************************************************************/

extern LONG GfxBase;
extern LONG IntuitionBase;

extern struct Window *OpenWindow();
extern struct IntuiMessage *GetMsg();

/****************************************************************/
/******* Local IntuiJunk ****************************************/
/****************************************************************/

struct Window *w;
struct RastPort *rp;
struct IntuiMessage *message;
USHORT class;

struct TextAttr ROMFont8 = {
	"Topaz80",8,0,0		/* Define text font for our screen */
};

struct IntuiText SText[2] = {
	{LOPEN,BGPEN,JAM1,CHECKWIDTH,0,&ROMFont8,"Trails",NULL},
	{LOPEN,BGPEN,JAM1,CHECKWIDTH,0,&ROMFont8,"Poly",  NULL}
};

struct MenuItem ShoPoly = {
	NULL,			/* NextItem */
	0,9,(CHECKWIDTH+50), 9,	/* Corner, Size */
	ITEMTEXT|HIGHCOMP|ISDRAWN|ITEMENABLED|CHECKIT|MENUTOGGLE, /* Flags */
	0L,			/* MutualExclude */
	(APTR)&SText[1], NULL,	/* ItemFill, SelectFill */
	'\0',			/* Command */
	NULL, NULL		/* SubItem, NextSelect */
};

struct MenuItem Trails = {
	&ShoPoly,		/* NextItem */
	0,0,(CHECKWIDTH+50), 9,	/* Corner, Size */
	ITEMTEXT|HIGHCOMP|ISDRAWN|ITEMENABLED|CHECKIT|MENUTOGGLE, /* Flags */
	0L,			/* MutualExclude */
	(APTR)&SText[0], NULL,	/* ItemFill, SelectFill */
	'\0',			/* Command */
	NULL, NULL		/* SubItem, NextSelect */
};

struct Menu Options = {
	NULL,			/* NextMenu */
	0,0,(CHECKWIDTH+54),9,	/* Corner, Size */
	MIDRAWN|MENUENABLED,	/* Flags */
	"Snake",		/* MenuName */
	&Trails			/* FirstItem */
};

/****************************************************************/
/******* Window *************************************************/
/****************************************************************/

struct NewWindow nw = {
	100,50,			/* Starting corner */
	300,100,		/* Width, Height */
	LOPEN,HIPEN,		/* DetailPen, BlockPen */
	CLOSEWINDOW|NEWSIZE,	/* IDCMPFlags (Don't need MENUPICK!) */
	WINDOWDEPTH|ACTIVATE|WINDOWDRAG|WINDOWCLOSE|
		SMART_REFRESH|WINDOWSIZING,	/* Flags */
	NULL,			/* FirstGadget */
	NULL,			/* Pointer to checkmark */
	"Snake",		/* title */
	NULL,			/* Screen */
	NULL,			/* Bitmap */
	50,50,640,400,		/* Size limits */
	WBENCHSCREEN		/* Type */
};

/****************************************************************/
/******* Spline Vars ********************************************/
/****************************************************************/

float coeffs[4][MAXVERT];
float P[2][4], Pv[2][4], xlf, ylf;
SHORT xlim, ylim;
SHORT DispVerts[(2*MAXVERT)];
SHORT OldVerts[16][(2*MAXVERT)];
SHORT control[8], oldcont[16][8];

/****************************************************************/
/****** Function Typing *****************************************/
/****************************************************************/

extern VOID AmiSetup();
extern VOID SplineSetup();
extern VOID CalcSnake();
extern SHORT Event();

/****************************************************************/
/******* Execution Begins Here **********************************/
/****************************************************************/

VOID _main()
{
	register UWORD i;
	register BOOL  k;
	UWORD onward = 0;
	UWORD SnakeColr = FGPEN;
	UWORD PolyColr = HIPEN;
	LONG Iterate = 0L;

	AmiSetup();
	SplineSetup();

	xlim = w->Width - 3;
	ylim = w->Height - 2;
	xlf = (float)xlim;
	ylf = (float)ylim;
	for(i=0;i<4;++i) {
	   P[0][i]  = (float)(rand() % (xlim-2)  + 1);	/* random start */
	   P[1][i]  = (float)(rand() % (ylim-14) + 13);
	   Pv[0][i] = (float)(rand() % 10 + 1);		/* and vector */
	   Pv[1][i] = (float)(rand() % 10 + 1);
	}

	class = NULL;
	do {
	   CalcSnake();
	   if ((!TRAIL) && (onward)) {
	      SetAPen(rp,BGPEN);			/* erase old */
	      onward = Iterate & 15;	
	      Move(rp,OldVerts[onward][0],OldVerts[onward][1]);
	      PolyDraw(rp,MAXVERT,&OldVerts[onward][0]);
	      if (POLYS) {
	         Move(rp,oldcont[onward][0],oldcont[onward][1]);
	         PolyDraw(rp,4,&oldcont[onward][0]);
	      }
	   }

	   SetAPen(rp,SnakeColr);				/* Draw new Curve */
	   Move(rp,DispVerts[0],DispVerts[1]);
	   PolyDraw(rp,MAXVERT,&DispVerts[0]);

	   if (POLYS) {
	      SetAPen(rp,PolyColr);
	      Move(rp,control[0],control[1]);
	      PolyDraw(rp,4,&control[0]);
	   }
	   if((rand() & 127) < 2) {			/* new color */
	      PolyColr = SnakeColr;
	      if (++SnakeColr > 3) SnakeColr = 1;
	   }

	   onward = (10 + Iterate++) & 15;
	   for (i=0; i<(2*MAXVERT); ++i)		/* old curve */
	      OldVerts[onward][i] = DispVerts[i];
	   for (i=0; i<8; ++i)				/* old control points */
	      oldcont[onward][i] = control[i];

	   for(i=0;i<4;++i) {			/* update control points */
	      P[0][i] += Pv[0][i];		/* new control points */
	      P[1][i] += Pv[1][i];
	      if(P[0][i] < 2.0)	{		/* clipping */
		 P[0][i] = 2.0;	Pv[0][i] = -Pv[0][i];
	      } else if(P[0][i] > xlf) {
		 P[0][i] = xlf;	Pv[0][i] = -Pv[0][i];
	      }
	      if(P[1][i] < 10.0)	{
		 P[1][i] = 10.0; Pv[1][i] = -Pv[1][i];
	      } else if(P[1][i] > ylf) {
		 P[1][i] = ylf;	 Pv[1][i] = -Pv[1][i];
	      }
	      if(((rand() >> 5) & 127) < 2) {	/* change x speed */
		 k = (Pv[0][i] < 0.0);
		 Pv[0][i] = (rand() >> 5) & 7;
		 if (k) Pv[0][i] = -Pv[0][i];
	      }
	      if(((rand() >> 5) & 255) < 50) {	/* change y speed */
		 k = (Pv[1][i] < 0.0);
		 Pv[1][i] = (rand() >> 5) & 7;
		 if (k) Pv[1][i] = -Pv[1][i];
	      }
	   }
	   onward = 1;
	   if(w->UserPort->mp_SigBit) {
	      message = GetMsg(w->UserPort);
	      if(message != NULL) {
	         class = message->Class;
	         ReplyMsg(message);
	      }
	   }
	} while(Event());

	ClearMenuStrip(w);
	CloseWindow(w);
	CloseLibrary(IntuitionBase);
	CloseLibrary(GfxBase);
}

/****************************************************************/
/****** Analyse IDCMP events ************************************/
/****************************************************************/

SHORT Event()
{
	switch(class) {
	   case CLOSEWINDOW:
	      return(NULL);
	      break;
	   case NEWSIZE:
	      class = NULL;
	      xlim = w->Width - 3;
	      ylim = w->Height -2;
	      xlf = (float)xlim;
	      ylf = (float)ylim;
	      return(RESIZED);
	      break;
	}
	return(DUNNO);
}

/****************************************************************/
/****** Set up display & libraries ******************************/
/****************************************************************/

VOID AmiSetup()
{
	if ((GfxBase = OpenLibrary("graphics.library",LIBRARY_VERSION))
		== NULL) {
	   Exit(20);
	}
	if ((IntuitionBase = OpenLibrary("intuition.library",LIBRARY_VERSION))
		== NULL) {
	   CloseLibrary(GfxBase);
	   Exit(20);
	}
	if ((w = OpenWindow(&nw)) == NULL) {
	   CloseLibrary(IntuitionBase);
	   CloseLibrary(GfxBase);
	   Exit(20);
	}
	rp = w->RPort;
	SetAPen(rp,HIPEN);
	SetDrMd(rp,JAM1);
	SetMenuStrip(w,&Options);
}

/****************************************************************/
/******* Precalculate Spline Coefficients ***********************/
/****************************************************************/

VOID SplineSetup()
{
	float t;
	UWORD i;
	for (i=0; i<MAXVERT; ++i) {
	  t = (float)i / (float)(MAXVERT-1);
	  coeffs[3][i]  = 1.0 - t;
	  coeffs[2][i]  = coeffs[3][i] * coeffs[3][i];
	  coeffs[0][i]  = t * (coeffs[1][i] = t*t);
	  coeffs[1][i] *= (3.0 * coeffs[3][i]);
	  coeffs[3][i] *= coeffs[2][i];
	  coeffs[2][i] *= (3.0 * t);
	}
}


/****************************************************************/
/****** Recalculate snake vertices ******************************/
/****************************************************************/

VOID CalcSnake()
{
	register i, j;
	float xt, yt;
	short ndx;
	ndx = 0;
	for (i=0; i<MAXVERT; ++i) {
	  xt = yt = 0.0;
	  for (j=0; j<4; ++j) {
	    xt += P[0][j] * coeffs[j][i];
	    yt += P[1][j] * coeffs[j][i];
	  }
#ifdef DEBUG
	  /* clip is unnessesary because of bounding-poly adherence */
	  if (xt<2.0)	xt = 2.0;
	  if (xt>xlim)	xt = xlim;
	  if (yt<12.0)	yt = 12.0;
	  if (yt>ylim)	yt = ylim;
#endif DEBUG
	  DispVerts[ndx++] = (SHORT)xt;
	  DispVerts[ndx++] = (SHORT)yt;
	}
	ndx = 0;
	for (i=0; i<4; ++i) {
	  control[ndx++] = (SHORT)P[0][i];
	  control[ndx++] = (SHORT)P[1][i];
	}
}

/****************************************************************/
/******************************************************** eof ***/
/****************************************************************/
