/* 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 */