/* yb2.c (c)1988 Ali T. Ozer
** main() and tons of other stuff for YaBoing II.
** Freely distributable.
*/

/* This is a second version of YaBoing!, "Yet Another Boing." Actually, this
** program started off with "I should fix YaBoing! up so that it'll run on 
** morerows'ed or interlaced screens." But I got carried away --- This program
** has almost nothing to do with the original YaBoing! (except that the 
** goal involves chasing sprites aroung with the mouse pointer).
**
** YaBoing! originally written Sep 1986 and posted Sep 21, 1986.
** YaBoing II written Dec 1987.
*/

/* YaBoing II is really a "stack-calculator" simulator where you have to 
** hunt down the numbers to be input and the operators to be applied. 
**
** -The "calculator" has a 4-location stack. The window displays the four
**  entries, with the bottom of the stack at the top (right under the title
**  bar).
** -Inputs are one-digit only; when you catch a "number" sprite the value (0-9)
**  is pushed to the stack. 
** -If the stack overflows, you lose the bottom entries. 
** -The operators "+", "-", "*", "/", pop the top two entries of the stack and
**  push the result. If you push X and then Y, the result is X op Y 
**  (and not Y op X). If the stack has less than 2 entries, nothing happens.
** -The "POP" operator pushes the top element off the stack.
** -The "SWAP" operator swaps the top two elements.
** -The "?" is a mystery number greater than 9.
** -The stack locations hold 32-bit unsigned numbers. If "+" or "*" causes 
**  overflow, the result is simply truncated.
** -If subtraction causes a negative result, then the result is 0.
** -If you divide by ZERO, the system GURUs. 
** -No, no, kidding. If you divide by ZERO the stack is cleared.
**
** So what's the goal? To end up with the highest number on top of the stack
** at the end of the game. The game lasts about 40-45 seconds, and about 
** five seconds before the end your mouse pointer changes shape to warn you.
**
** The high score is (2^32) - 1. Good luck!
**
** -Ali 
*/

/* Besides changes in the game-play, there are also some technical
** differences between YaBoing! and YaBoing II:
**
** -Sprites are not bound to the WB screen --- when WB is pulled down, the 
**  sprites will remain displayed. On some screens the sprites might get 
**  splattered (depending on your preferences settings).
** -The valid sprite movement area is determined by looking at the user's
**  screen parameters.
** -The Amiga timer device is used to time the sprite movement rather than
**  just a counter. Thus sprites will move around at a somewhat constant rate
**  (as opposed to slowing down when the load is high), although the movement
**  might be rather jerky.
** -The game is deactivated when the window is made inactive (like in YaBoing!).
**  to continue the game it's not enough to activate the window --- You need to
**  click either mouse button anywhere within the window (not on a gadget).
**  This allows you to depth-arrange and move the window without the sprites in
**  the way.
*/


/****************************************************************************/

#include "yb2.h"   

/* The following declarations replace Manx's and save about 900 bytes. Don't
** worry about the linker's "multiply defined" complaints...
*/
_wb_parse () {}
_cli_parse () {}

/* Collision bits in register CLXDAT. The three bits below correspond 
** to collisions between sprite 0 (the mouse) and sprites 2, 4, and 6,
** respectively.
*/
#define COL0AND2 0x0200
#define COL0AND4 0x0400
#define COL0AND6 0x0800
#define COL2AND4 0x1000
#define ALLCOL   0x1e00

unsigned short colmasks[] = {COL0AND2, COL0AND4, COL0AND6};

#define MAXVEL 24   /* Twice max velocity in pixels/move */

/* Number of sprites. There are dependencies on the value of this, for
** instance, InitSprites asks for sprites by number, and if this number is 
** changed, the mapping in InitSprites for yaboing sprite num -> HW sprite
** should be fixed also.
*/
#define NUMSPR 3
struct sprrec ybspr[NUMSPR];

struct timerequest tr;  
struct Screen     *scr;   /* WorkBench screen, obtained from the window */
struct Window	  *win;   /* YaBoing window */
struct ViewPort	  *vp;    /* WorkBench ViewPort */
struct RastPort   *rp;    /* YaBoing window rastport */
struct Font       *font;  /* Topaz 8 --- We need 8-point font for text */
struct GfxBase    *GfxBase;
struct IntuitionBase *IntuitionBase;

int minx, miny, maxx, maxy, halfx, halfy, quartx, quarty; /* Screen params */
int xshiftfactor, yshiftfactor; /* For conversion from screen to LORES     */
int mousex, mousey; /* Updated everytime through the loop, LORES-coords    */
int spritecount;    /* Increments everytime a new sprite is generated.     */
unsigned long lastmove; /* The time at which sprites last moved */
long oldtaskpri = 0;    
struct Task *me;        

#define MAXSPRITES 110 /* The number of sprites generated before game ends  */
#define WARNSPRITE  94 /* The number of sprites after which warning's given */


/* Returns a value that increments every 1/16 second... 
*/
unsigned long TimeCount()
{
  DoIO (&tr);
  return ((tr.tr_time.tv_secs << 4L) + (tr.tr_time.tv_micro / 62500L));
}


main ()
{
    register int cnt;
    unsigned short clxdat;    /* Value of collision register */
    int sleeping = true;      /* True if game is inactive */
    struct IntuiMessage *msg; /* The intuition message, from our window port */

    /* First set the priority of this task. */
    if (me = FindTask (NULL)) oldtaskpri = SetTaskPri (me, 1L); 

    OpenStuff ();
    InitRnd ();  /* Start up the random number generator */
    InitMessage ();

    while (1) {

	while (msg = (struct IntuiMessage *)GetMsg (win->UserPort)) {
	    switch (msg->Class) {
              case CLOSEWINDOW:    
                ReplyMsg (msg); 
		CloseStuff (0);  /* Never returns */
	      case MOUSEBUTTONS:   
                if (msg->Code==SELECTDOWN || msg->Code==MENUDOWN) 
                  if (sleeping) {
		      sleeping = false;
                      if (spritecount == 0) NewGame ();
                      ShowSprites (true);
                  } else sleeping = true;
              default:             
                ReplyMsg (msg);
            }
        }

        if (spritecount > MAXSPRITES) {

	  SetWarnPointer (win, false);
          DisplayBeep (scr);
          ShowScore ();
	  while (msg = (struct IntuiMessage *)GetMsg (win->UserPort))
              ReplyMsg (msg);
	  sleeping = true;
	  spritecount = 0;
        };

        if (sleeping) {

          ShowSprites (false);
          Wait (1L << win->UserPort->mp_SigBit);


        } else {                     

          mousex = LoResMouseX();
          mousey = LoResMouseY();

	  for (cnt = 0; cnt < NUMSPR; cnt++) ProcessSprite (&ybspr[cnt]);
	  lastmove = TimeCount ();
          for (cnt = 0; cnt < NUMSPR; cnt++) LocateSprite (&ybspr[cnt]);
	  
          if ((clxdat = custom.clxdat) & ALLCOL) CheckCollisions(clxdat);
          else Delay (2L);   /* Don't hog the CPU too much! */

          WaitTOF ();
        }
   }
}

/* Mouse coords in LORES (same kind of values as sprite locations) 
*/
int LoResMouseX () { return ((scr->MouseX + vp->DxOffset) >> xshiftfactor); }
int LoResMouseY () { return ((scr->MouseY + vp->DyOffset) >> yshiftfactor); }

/* Panic puts up a requester with a single "Sigh..." box. The string
** provided in "reason" is printed in the body of the  requester. 
** If user hits "Retry," then Panic returns. Else it exits.
*/
Panic (reason)
UBYTE *reason;
{
  static struct IntuiText negtxt  = {0,1,COMPLEMENT,4,4,NULL,(UBYTE *)"Sigh...",NULL};
  static struct IntuiText bodytxt = {0,1,COMPLEMENT,10,6,NULL,NULL,NULL};

  bodytxt.IText = reason;
  if (AutoRequest (NULL, &bodytxt, NULL, &negtxt, 0L, 0L, 300L, 54L)) return;
  CloseStuff (5);
}

CloseStuff (exitcode)
int exitcode;
{
    register int cnt;
 
    for (cnt = 0; cnt < NUMSPR; cnt++) ReleaseSprite (&ybspr[cnt]);

    if (tr.tr_node.io_Message.mn_Node.ln_Type == NT_MESSAGE) CloseDevice (&tr);
    if (font) CloseFont (font);
    if (win) CloseWindow (win);
    if (GfxBase) CloseLibrary (GfxBase);
    if (IntuitionBase) CloseLibrary (IntuitionBase);
    if (me) SetTaskPri (me, oldtaskpri); 
    exit (exitcode);
}

OpenStuff ()
{
    unsigned short sprcol;  /* Used when setting */
    long creg;              /* sprite colors...  */

    static struct NewWindow ybwindow = {
      WINDOWX, WINDOWY, WINDOWWIDTH, WINDOWHEIGHT, -1, -1,
      CLOSEWINDOW | MOUSEBUTTONS, 
      SMART_REFRESH | WINDOWCLOSE | WINDOWDEPTH | ACTIVATE |
          WINDOWDRAG | NOCAREREFRESH | RMBTRAP,
      NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, WBENCHSCREEN
    };

    static struct TextAttr ybfontdesc = {(STRPTR)"topaz.font", 8, 0, 0};

    if (((IntuitionBase = (struct IntuitionBase *)
           OpenLibrary ("intuition.library", 0L)) == NULL) ||
        ((GfxBase = (struct GfxBase *)
           OpenLibrary ("graphics.library", 0L)) == NULL) ||
        ((font = OpenFont (&ybfontdesc)) == NULL)) CloseStuff (10); /* ROM? */

    if (OpenDevice(TIMERNAME, UNIT_VBLANK, &tr, 0L) != 0) Panic ("No timer");
    tr.tr_node.io_Message.mn_Node.ln_Type = NT_MESSAGE;
    tr.tr_node.io_Command = TR_GETSYSTIME;

    if (InitSprites () == false) Panic ("No sprites!");
    if ((win = OpenWindow (&ybwindow)) == NULL) Panic ("No memory");

    /* Get the various stuff we want to access often into global variables. */
    scr = win->WScreen;
    vp  = (struct ViewPort *)ViewPortAddress (win);
    rp  = win->RPort;

    sprcol = GetRGB4 (vp->ColorMap, 0L) + 0x0888;
    
    for (creg = 20L; creg < 32L; creg += 2L) {
      SetRGB4 (vp, creg, 0L, 0L, 0L);
      SetRGB4 (vp, creg+1, (long)((sprcol & 0x0f00) >> 8),
			   (long)((sprcol & 0x00f0) >> 4),
			   (long)(sprcol & 0x000f));
    }

    SetAPen (rp, 1L); 
    SetBPen (rp, 0L);
    SetFont (rp, font);
    RectFill (rp, 0L, 10L, WINDOWWIDTH-1L, WINDOWHEIGHT-1L);
    SetDrMd (rp, JAM2 | INVERSVID);
    SetWindowTitles (win, "YaBoing II", COPYRIGHT);

    if (vp->Modes & HIRES) xshiftfactor = 1; else xshiftfactor = 0;
    if (vp->Modes & LACE)  yshiftfactor = 1; else yshiftfactor = 0;

    quartx = (halfx = (maxx = vp->DWidth >> xshiftfactor) >> 1) >> 1;
    quarty = (halfy = (maxy = vp->DHeight >> yshiftfactor) >> 1) >> 1;
    minx = -8; miny = -10; maxx += minx;  maxy += miny;
    /* Minx, miny, maxx, maxy determine the box in which the sprites roam. */
}

/* NewGame initializes everything necessary for a new game.
*/
NewGame ()
{
    int cnt;
    long creg; 

    for (cnt = 0; cnt < NUMSPR; cnt++) ybspr[cnt].mode = SPRITEDEAD;

    ClearStack ();
    spritecount = 1;
}
       

/* CheckCollisions is called when a collision is detected. CheckCollisions
** checks to see who collided with whom and takes action accordingly...
*/
CheckCollisions (clxdat)
unsigned short clxdat;  /* Sprite collision register image */
{
    int cnt;

    /* First check collision of the two "number" sprites (sprites 2 and 4) */
    if ((clxdat & COL2AND4) && 
       (ybspr[0].mode==SPRITEALIVE) && (ybspr[1].mode==SPRITEALIVE))
      ybspr[0].mode = ybspr[1].mode = SPRITEHIT1; /* Mark them as collided */

    /* Now check collisions between the mouse and the 3 sprites */
    for (cnt = 0; cnt < NUMSPR; cnt++) 
      if ((clxdat & colmasks[cnt]) && (ybspr[cnt].mode==SPRITEALIVE)) {
        ybspr[cnt].mode = SPRITEHIT1;
        ProcessHit (&ybspr[cnt]);
      }
}             


/* Locate sprite will move a sprite to its new location if it's not dead. 
*/
LocateSprite (spr)
struct sprrec *spr;
{
  if (MODE != SPRITEDEAD) 
    MoveSprite (NULL, &(spr->actualsprite), (long)PX, (long)PY);
}

/* The "dissolvemasks" array determines how the sprites disappear when they die.
*/
#define MAXDISSOLVEMASKS 7
unsigned short dissolvemasks[MAXDISSOLVEMASKS] = {
  0xeffb,0xeffb,0xef7b,0xcb6b,0xc94a,0x2108,0x0100
};

ProcessSprite (spr)
struct sprrec *spr;
{
    if (MODE != SPRITEDEAD) AdjustSprite (spr);

    switch (MODE) {
     case SPRITEALIVE: ChangeNumValue (spr); break;
     case SPRITEHIT1:  
        VAL = AX = AY = 0;
        VX >>= 1; VY >>= 1;
        MODE = SPRITEHIT2; /* Fall through */
     case SPRITEHIT2:
        DissolveSprite (SPRMEM, dissolvemasks[VAL++]);
        if (VAL == MAXDISSOLVEMASKS) MODE = SPRITEDEAD;
	break;
     case SPRITEDEAD:
        ShowSprite (spr, false);
        if (Rnd(5) == 0) EnterSprite (spr);
        break;
    }
}

/* Given a sprite, this routine increases or decreases its value. For numbers,
** new value depends on the distance between the sprite and the mouse. For
** operator sprites, the value is incremented regularly.
*/
ChangeNumValue (spr)
struct sprrec *spr;
{
   int origval = VAL;
   unsigned long newtc = TimeCount();

   if (newtc > CHANGE) {
     if (TYPE == OPSPRITE) {
       CHANGE = newtc + 14;   /* Change OPs every ~.9 seconds */
       if (Rnd(40) == 0) VAL = OPVALUE+OPVALUES-1;
       else if ((++VAL) >= OPVALUE+OPVALUES-1) VAL = OPVALUE;
     } else {
       CHANGE == newtc + 5;  /* And numbers 16/5 times a second */
       if ((VAL > Rnd(8)) && (PX-mousex < quartx) && (PX-mousex > -quartx) && 
           (PY-mousey < quarty) && (PY-mousey > -quarty)) VAL--;
       else if (VAL < Rnd(10)) VAL += Rnd(3) - (VAL == DIGITVALUE ? 0 : 1);
     }
     if (origval != VAL) LoadSpriteImage (spr->sprmem, VAL);
   }
}


/* This routine moves a sprite and adjusts its velocity and acceleration.
** It also checks to see if the sprite is out of bounds --- If it is, the
** sprite is made inactive.
** Delta is the change in time in 1/8 sec since last move.
*/
AdjustSprite (spr)
struct sprrec *spr;
{
    int delta = (TimeCount() - lastmove + 1);
    if (delta > 32 || delta < 0) delta = 32;
    
    /* Below we update the sprite positions and change the velocities.
    ** We make sure we remain within the speed limit (MAXVEL).
    */
    PX += (VX >> 1) * delta; /* Important that ">>" works */
    PY += (VY >> 1) * delta; /* OK on signed quantities!  */  
    VX += AX;  if (VX > MAXVEL || VX < -MAXVEL) {AX = 0; VX >>= 1;}; 
    VY += AY;  if (VY > MAXVEL || VY < -MAXVEL) {AY = 0; VY >>= 1;}; 

    if (PX < minx || PX > maxx || PY < miny || PY > maxy) { 
      MODE = SPRITEDEAD; ShowSprite (spr, false);  /* Out of bounds! Kill it! */
    } else switch (Rnd(150)) {                     /* Randomly change stuff.  */
      case 0: AX += (Rnd(5) - 2); AY += (Rnd(5) - 2); break;
      case 1: VX = (VX > halfx ? -MAXVEL : MAXVEL); AX = AY = 0; break;
      case 2: VX = -VX; break;
      case 3: VY = -VY; break;
      case 4: VX = -VX; AX = -AX; break;
      case 5: VY = -VY; AY = -AY; break;
      default: if (Rnd(7) == 0 && TYPE != OPSPRITE) { /* Move away from mouse */
                if (mousex > PX) AX = -Rnd(4); else AX = Rnd(4);
                if (mousey > PY) AY = -Rnd(4); else AY = Rnd(4);
               }; break;
    };
}
        
/* Determines where a sprites comes into the screen from and sets the
** various parameters (velocity, acceleration, & position) accordingly...
*/
EnterSprite (spr)
struct sprrec *spr;
{
  int v = Rnd(5)+4;
  int vo = Rnd(5)-2;
  int a = (TYPE == OPSPRITE ? 0 : Rnd(3));

  switch (Rnd(4)) {
    case 0: VX=v;  AX=a;  AY=0; VY=vo; PX=minx+1; PY=Rnd(halfy)+quarty; break;
    case 1: VX=-v; AX=-a; AY=0; VY=vo; PX=maxx-1; PY=Rnd(halfy)+quarty; break;
    case 2: VY=v;  AY=a;  AX=0; VX=vo; PY=miny+1; PX=Rnd(halfx)+quartx; break;
    case 3: VY=-v; AY=-a; AX=0; VX=vo; PY=maxy-1; PX=Rnd(halfx)+quartx; break;
  }

  if (TYPE == NUMSPRITE) VAL = Rnd(DIGITVALUES) + DIGITVALUE;
  else VAL = Rnd(OPVALUES) + OPVALUE;  
  
  CHANGE = TimeCount();

  LoadSpriteImage (SPRMEM, VAL);
  ShowSprite (spr, true);
  MODE = SPRITEALIVE;  
  
  if (++spritecount == WARNSPRITE) SetWarnPointer (win, true);
}
    
     
/* ShowSprites disables/enables all sprites. Used when the user deactivates
** the YaBoing window.
*/
ShowSprites (show)
int show;
{
    int cnt;
    for (cnt = 0; cnt < NUMSPR; cnt++)
      if (show == false || ybspr[cnt].mode != SPRITEDEAD) 
        ShowSprite (&ybspr[cnt], show);
}


/* InitSprites attempts to obtain the sprites we need. If unsuccessful, gives
** up in shame.
*/
int InitSprites ()
{
    int cnt;
    for (cnt = 0; cnt < NUMSPR; cnt++) {
      ybspr[cnt].actualsprite.height = 0;
      ybspr[cnt].type = (cnt == NUMSPR-1 ? OPSPRITE : NUMSPRITE);
      if (InitSprite(&ybspr[cnt],cnt+cnt+2) == false) return (false);
    };
    return (true);
}

  
static unsigned long rndseed;

InitRnd ()
{
  rndseed = TimeCount();
}


/* Returns random integer between 0 and max-1 inclusive. 
*/
int Rnd (max)
int max;
{
  long res = (rndseed & 0x00000002L) | (rndseed & 0x00000010L);
  rndseed >>= 1;
  if (res == 0x00000012 || res == 0x00000000) rndseed |= 0x80000000L;
  return (((int)((rndseed & 0x00007fffL) % max)));
}


/* YaBoing The Next Generation, by Ali T. Ozer */



