
//============================================================================
//----------------------------------------------------------------------------
//									Graphics.c
//----------------------------------------------------------------------------
//============================================================================

// I like to isolate all the graphic routines - put them in their own file.
// This way all the thousands of Rect variables and Pixmaps have a place to go.
// Anyway, this file contains all the drawing routines.

#include "Externs.h"
#include <Palettes.h>


#define kUpperEyeHeight			100
#define kLowerEyeHeight			200
#define kNumLightningPts		8
#define kMaxNumUpdateRects		32


void QuickUnionRect (Rect *, Rect *, Rect *);
void CheckPlayerWrapAround (void);
void DrawHand (void);
void DrawEye (void);
void DrawPlayer (void);
void CheckEnemyWrapAround (short);
void DrawEnemies (void);


Rect		backSrcRect, workSrcRect, obSrcRect, playerSrcRect;
Rect		numberSrcRect, idleSrcRect, enemyWalkSrcRect, enemyFlySrcRect;
Rect		obeliskRects[4], playerRects[11], numbersSrc[11], numbersDest[11];
Rect		updateRects1[kMaxNumUpdateRects], updateRects2[kMaxNumUpdateRects];
Rect		flameSrcRect, flameDestRects[2], flameRects[4], eggSrcRect;
Rect		platformSrcRect, platformCopyRects[9], helpSrcRect, eyeSrcRect;
Rect		helpSrc, helpDest, handSrcRect, handRects[2], eyeRects[4];
Point		leftLightningPts[kNumLightningPts], rightLightningPts[kNumLightningPts];
CGrafPtr	backSrcMap, workSrcMap, obeliskSrcMap, playerSrcMap, eyeSrcMap;
CGrafPtr	numberSrcMap, idleSrcMap, enemyWalkSrcMap, enemyFlySrcMap;
CGrafPtr	flameSrcMap, eggSrcMap, platformSrcMap, helpSrcMap, handSrcMap;
GrafPtr		playerMaskMap, enemyWalkMaskMap, enemyFlyMaskMap, eggMaskMap;
GrafPtr		handMaskMap, eyeMaskMap;
RgnHandle	playRgn;
short		numUpdateRects1, numUpdateRects2;
Boolean		whichList, helpOpen, scoresOpen;

extern	handInfo	theHand;
extern	eyeInfo		theEye;
extern	prefsInfo	thePrefs;
extern	playerType	thePlayer;
extern	enemyType	theEnemies[];
extern	Rect		enemyRects[24];
extern	WindowPtr	mainWindow;
extern	long		theScore, wasTensOfThousands;
extern	short		livesLeft, levelOn, numEnemies;
extern	Boolean		evenFrame;


//==============================================================  Functions
//--------------------------------------------------------------  DrawPlatforms

// This function draws all the platforms on the background pixmap and theÉ
// work pixmap.  It needs to know merely how many of them to draw.

void DrawPlatforms (short howMany)
{
	if (howMany > 3)			// If there are more than 3 platformsÉ
	{							// Draw a platform to background pixmap.
		CopyBits(&((GrafPtr)platformSrcMap)->portBits, 
				&((GrafPtr)backSrcMap)->portBits, 
				&platformCopyRects[2], &platformCopyRects[7], srcCopy, playRgn);
								// Draw a platform to work pixmap.
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&platformCopyRects[7], &platformCopyRects[7], srcCopy, playRgn);
								// Add rectangle to update list to be drawn to screen.
		AddToUpdateRects(&platformCopyRects[7]);
								// Ditto for a second platform.
		CopyBits(&((GrafPtr)platformSrcMap)->portBits, 
				&((GrafPtr)backSrcMap)->portBits, 
				&platformCopyRects[4], &platformCopyRects[8], srcCopy, playRgn);
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&platformCopyRects[8], &platformCopyRects[8], srcCopy, playRgn);
		AddToUpdateRects(&platformCopyRects[8]);
	}
	else						// If there are 3 or less platformsÉ
	{
		CopyBits(&((GrafPtr)platformSrcMap)->portBits, 
				&((GrafPtr)backSrcMap)->portBits, 
				&platformCopyRects[3], &platformCopyRects[7], srcCopy, playRgn);
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&platformCopyRects[7], &platformCopyRects[7], srcCopy, playRgn);
		AddToUpdateRects(&platformCopyRects[7]);
		
		CopyBits(&((GrafPtr)platformSrcMap)->portBits, 
				&((GrafPtr)backSrcMap)->portBits, 
				&platformCopyRects[5], &platformCopyRects[8], srcCopy, playRgn);
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&platformCopyRects[8], &platformCopyRects[8], srcCopy, playRgn);
		AddToUpdateRects(&platformCopyRects[8]);
	}
	
	if (howMany > 5)		// If there are more than 5 platformsÉ
	{
		CopyBits(&((GrafPtr)platformSrcMap)->portBits, 
				&((GrafPtr)backSrcMap)->portBits, 
				&platformCopyRects[0], &platformCopyRects[6], srcCopy, playRgn);
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&platformCopyRects[6], &platformCopyRects[6], srcCopy, playRgn);
		AddToUpdateRects(&platformCopyRects[6]);
	}
	else					// If there are 5 or less platformsÉ
	{
		CopyBits(&((GrafPtr)platformSrcMap)->portBits, 
				&((GrafPtr)backSrcMap)->portBits, 
				&platformCopyRects[1], &platformCopyRects[6], srcCopy, playRgn);
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&platformCopyRects[6], &platformCopyRects[6], srcCopy, playRgn);
		AddToUpdateRects(&platformCopyRects[6]);
	}
}

//--------------------------------------------------------------  ScrollHelp

// This function scrolls the help screen.  You pass it a number of pixelsÉ
// to scroll up or down (positive or negative number).

void ScrollHelp (short scrollDown)
{
	OffsetRect(&helpSrc, 0, scrollDown);		// Move the source rectangle.
	
	if (helpSrc.bottom > 398)					// Check to see we don't go too far.
	{
		helpSrc.bottom = 398;
		helpSrc.top = helpSrc.bottom - 199;
	}
	else if (helpSrc.top < 0)
	{
		helpSrc.top = 0;
		helpSrc.bottom = helpSrc.top + 199;
	}
												// Draw "scrolled" help screen.
	CopyBits(&((GrafPtr)helpSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&helpSrc, &helpDest, srcCopy, 0L);
}

//--------------------------------------------------------------  OpenHelp

// Bring up the help screen.  This is a kind of "wipe" or "barn door" effect.

void OpenHelp (void)
{
	Rect		wallSrc, wallDest;
	short		i;
	
	SetRect(&helpSrc, 0, 0, 231, 0);	// Initialize source and destination rects.
	helpDest = helpSrc;
	OffsetRect(&helpDest, 204, 171);
	
	SetRect(&wallSrc, 0, 0, 231, 199);
	OffsetRect(&wallSrc, 204, 171);
	wallDest = wallSrc;
	
	for (i = 0; i < 199; i ++)			// Loop through 1 pixel at a time.
	{
		LogNextTick(1L);				// Speed governor.
		helpSrc.bottom++;				// Grow help source rect.
		helpDest.bottom++;				// Grow help dest as well.
		wallSrc.bottom--;				// Shrink wall source.
		wallDest.top++;					// Shrink wall dest.
		
										// So, as the help graphic grows, the wall graphicÉ
										// shrinks.  Thus it is as though the wall isÉ
										// lifting up to expose the help screen beneath.
		
										// Copy slightly larger help screen.
		CopyBits(&((GrafPtr)helpSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&helpSrc, &helpDest, srcCopy, 0L);
										// Copy slightly smaller wall graphic.
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&wallSrc, &wallDest, srcCopy, 0L);
		
		WaitForNextTick();				// Speed governor.
	}
	helpOpen = TRUE;					// When done, set flag to indicate help is open.
}

//--------------------------------------------------------------  CloseWall

// Close the wall over whatever screen is up (help screen or high scores).
// Since the wall just comes down over the opening - covering whatever was beneath,É
// it's simpler than the above function.

void CloseWall (void)
{
	Rect		wallSrc, wallDest;
	short		i;
	
	SetRect(&wallSrc, 0, 0, 231, 0);	// Initialize source and dest rects.
	wallDest = wallSrc;
	OffsetRect(&wallDest, 204, 370);
	OffsetRect(&wallSrc, 204, 171);
	
	for (i = 0; i < 199; i ++)			// Do it one pixel at a time.
	{
		wallSrc.bottom++;				// Grow bottom of wall source.
		wallDest.top--;					// Move down wall dest.
										// Draw wall coming down.
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&wallSrc, &wallDest, srcCopy, 0L);
	}									// Note, no speed governing (why bother?).
}

//--------------------------------------------------------------  OpenHighScores

// This function is practically identical to the OpenHelp().  The only realÉ
// difference is that we must first draw all the high scores offscreen beforeÉ
// lifting the wall to reveal them.

void OpenHighScores (void)
{
	RGBColor	theRGBColor, wasColor;
	Rect		wallSrc, wallDest;
	Rect		scoreSrc, scoreDest;
	Str255		scoreStr;
	short		i, scoreWide;
	
	SetRect(&scoreSrc, 0, 0, 231, 0);				// Initialize source and dest rects.
	OffsetRect(&scoreSrc, 204, 171);
	scoreDest = scoreSrc;
	
	SetRect(&wallSrc, 0, 0, 231, 199);
	OffsetRect(&wallSrc, 204, 171);
	wallDest = wallSrc;
	
	SetPort((GrafPtr)workSrcMap);					// We'll draw scores to the work pixmap.
	PaintRect(&wallSrc);							// Paint it black.
	
	GetForeColor(&wasColor);						// Save the foreground color.
	
	TextFont(geneva);								// Use Geneva 12 point Bold font.
	TextSize(12);
	TextFace(bold);
	
	Index2Color(132, &theRGBColor);					// Get the 132nd color in RGB form.
	RGBForeColor(&theRGBColor);						// Make this color the pen color.
	MoveTo(scoreSrc.left + 36, scoreSrc.top + 20);	// Get pen in right position to draw.
	DrawString("\pGlypha III High Scores");			// Draw the title.
	
	TextFont(geneva);								// Use Geneva 9 point Bold font.
	TextSize(9);
	TextFace(bold);
	
	for (i = 0; i < 10; i++)						// Walk through all 10 high scores.
	{
		Index2Color(133, &theRGBColor);				// Use color 133 (in palette).
		RGBForeColor(&theRGBColor);
		NumToString((long)i + 1L, scoreStr);		// Draw "place" (1, 2, 3, É).
		MoveTo(scoreSrc.left + 8, scoreSrc.top + 40 + (i * 16));
		DrawString(scoreStr);
		
		Index2Color(128, &theRGBColor);				// Use color 128 (from palette).
		RGBForeColor(&theRGBColor);
		MoveTo(scoreSrc.left + 32, scoreSrc.top + 40 + (i * 16));
		DrawString(thePrefs.highNames[i]);			// Draw the high score name (Sue, É).
		
		Index2Color(164, &theRGBColor);				// Use color 164 (from palette).
		RGBForeColor(&theRGBColor);
		NumToString(thePrefs.highScores[i], scoreStr);
		scoreWide = StringWidth(scoreStr);			// Right justify.
		MoveTo(scoreSrc.left + 191 - scoreWide, scoreSrc.top + 40 + (i * 16));
		DrawString(scoreStr);						// Draw the high score (12,000, É).
		
		Index2Color(134, &theRGBColor);				// Use color 134 (from palette).
		RGBForeColor(&theRGBColor);
		NumToString(thePrefs.highLevel[i], scoreStr);
		scoreWide = StringWidth(scoreStr);			// Right justify.
		MoveTo(scoreSrc.left + 223 - scoreWide, scoreSrc.top + 40 + (i * 16));
		DrawString(scoreStr);						// Draw highest level (12, 10, É).
	}
	
	RGBForeColor(&wasColor);						// Restore foreground color.
	
	SetPort((GrafPtr)mainWindow);
	
	for (i = 0; i < 199; i ++)						// Now the standard scroll functions.
	{
		LogNextTick(1L);
		scoreSrc.bottom++;
		scoreDest.bottom++;
		wallSrc.bottom--;
		wallDest.top++;
		
		CopyBits(&((GrafPtr)workSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&scoreSrc, &scoreDest, srcCopy, 0L);
		
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&wallSrc, &wallDest, srcCopy, 0L);
		
		WaitForNextTick();
	}
	
	scoresOpen = TRUE;								// Flag that the scores are up.
}

//--------------------------------------------------------------  UpdateLivesNumbers

// During a game, this function is called to reflect the current number of lives.
// This is "lives remaining", so 1 is subtracted before displaying it to the screen.
// The lives is "wrapped around" after 99.  So 112 lives will display as 12.  It'sÉ
// a lot easier to handle numbers this way (it beats a recursive function that mightÉ
// potentially draw across the entire screen.

void UpdateLivesNumbers (void)
{
	short		digit;
	
	digit = (livesLeft - 1) / 10;		// Get the "10's" digit.
	digit = digit % 10L;				// Keep it less than 10 (0 -> 9).
	if ((digit == 0) && ((livesLeft - 1) < 10))
		digit = 10;						// Use a "blank" space if zero and less than 10.
										// Draw digit.
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[0], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[0], &numbersDest[0], srcCopy, 0L);
	
	digit = (livesLeft - 1) % 10;		// Get 1's digit.
										// Draw digit.
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[1], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[1], &numbersDest[1], srcCopy, 0L);
}

//--------------------------------------------------------------  UpdateScoreNumbers

// This function works just like the above function.  However, we allow theÉ
// score to go to 6 digits (999,999) before rolling over.  Note however, thatÉ
// in both the case of the score, number of lives, etc., the game does in factÉ
// keep track of the "actual" number.  It is just that only so many digits areÉ
// being displayed.

void UpdateScoreNumbers (void)
{
	long		digit;
	
	digit = theScore / 100000L;		// Get "hundreds of thousands" digit.
	digit = digit % 10L;			// Clip off anything greater than 9.
	if ((digit == 0) && (theScore < 1000000L))
		digit = 10;					// Use blank space if zero.
									// Draw digit.
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[2], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[2], &numbersDest[2], srcCopy, 0L);
	
	digit = theScore / 10000L;		// Get "tens of thousands" digit.
	if (digit > wasTensOfThousands)	// Check for "extra life" here.
	{
		livesLeft++;				// Increment number of lives.
		UpdateLivesNumbers();		// Reflect new lives on screen.
		wasTensOfThousands = digit;	// Note that life was given.
	}
	digit = digit % 10L;			// Clip off anything greater than 9.
	if ((digit == 0) && (theScore < 100000L))
		digit = 10;					// Use blank space if zero.
									// Draw digit.
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[3], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[3], &numbersDest[3], srcCopy, 0L);
	
	digit = theScore / 1000L;		// Handle "thousands" digit.
	digit = digit % 10L;
	if ((digit == 0) && (theScore < 10000L))
		digit = 10;
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[4], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[4], &numbersDest[4], srcCopy, 0L);
	
	digit = theScore / 100L;		// Handle 100's digit.
	digit = digit % 10L;
	if ((digit == 0) && (theScore < 1000L))
		digit = 10;
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[5], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[5], &numbersDest[5], srcCopy, 0L);
	
	digit = theScore / 10L;			// Handle 10's digit.
	digit = digit % 10L;
	if ((digit == 0) && (theScore < 100L))
		digit = 10;
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[6], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[6], &numbersDest[6], srcCopy, 0L);
	
	digit = theScore % 10L;			// Handle 1's digit.
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[7], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[7], &numbersDest[7], srcCopy, 0L);
}

//--------------------------------------------------------------  UpdateLevelNumbers

// Blah, blah, blah.  Just like the above functions but handles displaying theÉ
// level the player is on.  We allow 3 digits here (up to 999) before wrapping.

void UpdateLevelNumbers (void)
{
	short		digit;
	
	digit = (levelOn + 1) / 100;		// Do 100's digit.
	digit = digit % 10L;
	if ((digit == 0) && ((levelOn + 1) < 1000))
		digit = 10;
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[8], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[8], &numbersDest[8], srcCopy, 0L);
	
	digit = (levelOn + 1) / 10;			// Do 10's digit.
	digit = digit % 10L;
	if ((digit == 0) && ((levelOn + 1) < 100))
		digit = 10;
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[9], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[9], &numbersDest[9], srcCopy, 0L);
	
	digit = (levelOn + 1) % 10;			// Do 1's digit.
	CopyBits(&((GrafPtr)numberSrcMap)->portBits, 
			&((GrafPtr)backSrcMap)->portBits, 
			&numbersSrc[digit], &numbersDest[10], srcCopy, 0L);
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&(((GrafPtr)mainWindow)->portBits), 
			&numbersDest[10], &numbersDest[10], srcCopy, 0L);
}

//--------------------------------------------------------------  GenerateLightning

// This function takes a point (h and v) and then generates two lightning boltsÉ
// (one from the tip of each obelisk) to the point.  It does this by generatingÉ
// a list of segments (as the lightning is broken up into segements).  The drawingÉ
// counterpart to this function will draw a line connecting these segements (a sortÉ
// of dot-to-dot).

void GenerateLightning (short h, short v)
{
	#define kLeftObeliskH		172
	#define kLeftObeliskV		250
	#define kRightObeliskH		468
	#define kRightObeliskV		250
	#define kWander				16
	
	short		i, leftDeltaH, rightDeltaH, leftDeltaV, rightDeltaV, range;
	
	leftDeltaH = h - kLeftObeliskH;				// Determine the h and v distances betweenÉ
	rightDeltaH = h - kRightObeliskH;			// obelisks and the target point.
	leftDeltaV = v - kLeftObeliskV;
	rightDeltaV = v - kRightObeliskV;
	
	for (i = 0; i < kNumLightningPts; i++)		// Calculate an even spread of points betweenÉ
	{											// obelisk tips and the target point.
		leftLightningPts[i].h = (leftDeltaH * i) / (kNumLightningPts - 1) + kLeftObeliskH;
		leftLightningPts[i].v = (leftDeltaV * i) / (kNumLightningPts - 1) + kLeftObeliskV;
		rightLightningPts[i].h = (rightDeltaH * i) / (kNumLightningPts - 1) + kRightObeliskH;
		rightLightningPts[i].v = (rightDeltaV * i) / (kNumLightningPts - 1) + kRightObeliskV;
	}
	
	range = kWander * 2 + 1;					// Randomly scatter the points verticallyÉ
	for (i = 1; i < kNumLightningPts - 1; i++)	// but NOT the 1st or last points.
	{
		leftLightningPts[i].v += RandomInt(range) - kWander;
		rightLightningPts[i].v += RandomInt(range) - kWander;
	}
}

//--------------------------------------------------------------  FlashObelisks

// This function either draws the obelisks "normal" or draws them inverted.
// They're drawn "inverted" as if emanating energy or lit up by the boltsÉ
// of lightning.  The flag "flashThem" specifies how to draw them.

void FlashObelisks (Boolean flashThem)
{	
	if (flashThem)		// Draw them "inverted"
	{
		CopyBits(&((GrafPtr)obeliskSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&obeliskRects[0], &obeliskRects[2], 
				srcCopy, 0L);
		CopyBits(&((GrafPtr)obeliskSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&obeliskRects[1], &obeliskRects[3], 
				srcCopy, 0L);
	}
	else			// Draw them "normal"
	{
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&obeliskRects[2], &obeliskRects[2], 
				srcCopy, 0L);
		CopyBits(&((GrafPtr)backSrcMap)->portBits, 
				&(((GrafPtr)mainWindow)->portBits), 
				&obeliskRects[3], &obeliskRects[3], 
				srcCopy, 0L);
	}
}

//--------------------------------------------------------------  StrikeLightning

// This function draws the lightning bolts.  The PenMode() is set to patXOrÉ
// so that the lines are drawn inverted (colorwise).  This way, drawing theÉ
// lightning twice over will leave no pixels disturbed.

void StrikeLightning (void)
{
	short		i;
	
	SetPort((GrafPtr)mainWindow);				// Draw straight to screen.
	PenSize(1, 2);								// Use a tall pen.
	PenMode(patXor);							// Use XOR mode.
												// Draw lightning bolts with inverted pen.
	MoveTo(leftLightningPts[0].h, leftLightningPts[0].v);
	for (i = 0; i < kNumLightningPts - 1; i++)	// Draw left lightning bolt.
	{
		MoveTo(leftLightningPts[i].h, leftLightningPts[i].v);
		LineTo(leftLightningPts[i + 1].h - 1, leftLightningPts[i + 1].v);
	}
	
	MoveTo(rightLightningPts[0].h, rightLightningPts[0].v);
	for (i = 0; i < kNumLightningPts - 1; i++)	// Draw right lightning bolt.
	{
		MoveTo(rightLightningPts[i].h, rightLightningPts[i].v);
		LineTo(rightLightningPts[i + 1].h - 1, rightLightningPts[i + 1].v);
	}
	
	PenNormal();								// Return pen to normal.
}

//--------------------------------------------------------------  DumpBackToWorkMap

// Simple handy function that copies the entire background pixmap to theÉ
// work pixmap.

void DumpBackToWorkMap (void)
{
	CopyBits(&((GrafPtr)backSrcMap)->portBits, 
			&((GrafPtr)workSrcMap)->portBits, 
			&backSrcRect, &backSrcRect, srcCopy, 0L);
}

//--------------------------------------------------------------  DumpBackToWorkMap

// Simple handy function that copies the entire work pixmap to theÉ
// screen.

void DumpMainToWorkMap (void)
{
	CopyBits(&(((GrafPtr)mainWindow)->portBits), 
			&((GrafPtr)workSrcMap)->portBits, 
			&backSrcRect, &backSrcRect, srcCopy, 0L);
}

//--------------------------------------------------------------  QuickUnionRect

// The Mac Toolbox gives you a UnionRect() function, but, like any ToolboxÉ
// routine, if we can do it faster, we ought to.  Well, the function belowÉ
// is quick because (among other reasons), it assumes that the two rectsÉ
// being compared are the same size.

void QuickUnionRect (Rect *rect1, Rect *rect2, Rect *whole)
{
	if (rect1->left < rect2->left)		// See if we're to use rect1's left.
	{
		whole->left = rect1->left;
		whole->right = rect2->right;
	}
	else								// Use rect2's left.
	{
		whole->left = rect2->left;
		whole->right = rect1->right;
	}
	
	if (rect1->top < rect2->top)		// See if we're to use rect1's top.
	{
		whole->top = rect1->top;
		whole->bottom = rect2->bottom;
	}
	else								// Use rect2's top.
	{
		whole->top = rect2->top;
		whole->bottom = rect1->bottom;
	}
}

//--------------------------------------------------------------  AddToUpdateRects

// This is an elegant way to handle game animation.  It has some drawbacks, butÉ
// for ease of use, you may not be able to beat it.  The idea is that any timeÉ
// you want something drawn to the screen (copied from an offscreen pixmap toÉ
// the screen) you pass the rectangle to this routine.  This routine then addsÉ
// the rectangle to a growing list of these rectangles.  When the game reachesÉ
// drawing phase, another routine copies all these rectangles.  It is assumed, É
// nonetheless, that you have copied the little graphic offscreen that you wantÉ
// moved to the screen (the shpinx or whatever).  This routine will take care ofÉ
// drawing the shinx or whatever to the screen.

void AddToUpdateRects (Rect *theRect)
{
	if (whichList)		// We alternate every odd frame between two listsÉ
	{					// in order to hold a copy of rects from last frame.
		if (numUpdateRects1 < (kMaxNumUpdateRects - 1))
		{				// If we are below the maximum # of rects we can handleÉ
						// Add the rect to the list (array).
			updateRects1[numUpdateRects1] = *theRect;
						// Increment the number of rects held in list.
			numUpdateRects1++;
						// Do simple bounds checking (clip to screen).
			if (updateRects1[numUpdateRects1].left < 0)
				updateRects1[numUpdateRects1].left = 0;
			else if (updateRects1[numUpdateRects1].right > 640)
				updateRects1[numUpdateRects1].right = 640;
			if (updateRects1[numUpdateRects1].top < 0)
				updateRects1[numUpdateRects1].top = 0;
			else if (updateRects1[numUpdateRects1].bottom > 480)
				updateRects1[numUpdateRects1].bottom = 480;
		}
	}
	else				// Exactly like the above section, but with the other list.
	{
		if (numUpdateRects2 < (kMaxNumUpdateRects - 1))
		{
			updateRects2[numUpdateRects2] = *theRect;
			numUpdateRects2++;
			if (updateRects2[numUpdateRects2].left < 0)
				updateRects2[numUpdateRects2].left = 0;
			else if (updateRects2[numUpdateRects2].right > 640)
				updateRects2[numUpdateRects2].right = 640;
			if (updateRects2[numUpdateRects2].top < 0)
				updateRects2[numUpdateRects2].top = 0;
			else if (updateRects2[numUpdateRects2].bottom > 480)
				updateRects2[numUpdateRects2].bottom = 480;
		}
	}
}

//--------------------------------------------------------------  CheckPlayerWrapAround

// This handles drawing wrap-around.  It is such that, when a player walks partlyÉ
// off the right edge of the screen, you see the player peeking through on the leftÉ
// side of the screen.  Since we can't (shouldn't) assume that the physical screenÉ
// memory wraps around, we'll draw the right player clipped against the right edgeÉ
// of the screen and draw a SECOND PLAYER on the left edge (clipped to the left).

void CheckPlayerWrapAround (void)
{
	Rect		wrapRect, wasWrapRect, src;
	
	if (thePlayer.dest.right > 640)		// Player off right edge of screen.
	{
		thePlayer.wrapping = TRUE;		// Set "wrapping" flag.
		wrapRect = thePlayer.dest;		// Start out with copy of player bounds.
		wrapRect.left -= 640;			// Offset it a screenwidth to left.
		wrapRect.right -= 640;
										// Ditto with old location.
		wasWrapRect = thePlayer.wasDest;
		wasWrapRect.left -= 640;
		wasWrapRect.right -= 640;
		
		if (thePlayer.mode == kBones)	// Draw second bones.
		{
			src = playerRects[thePlayer.srcNum];
			src.bottom = src.top + thePlayer.frame;
			CopyMask(&((GrafPtr)playerSrcMap)->portBits, 
					&((GrafPtr)playerMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&src, &src, &wrapRect);
		}
		else							// Draw second player (not bones).
		{
			CopyMask(&((GrafPtr)playerSrcMap)->portBits, 
					&((GrafPtr)playerMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&playerRects[thePlayer.srcNum], 
					&playerRects[thePlayer.srcNum], 
					&wrapRect);
		}
		thePlayer.wrap = wrapRect;
		AddToUpdateRects(&wrapRect);	// Add this to our list of update rects.
	}
	else if (thePlayer.dest.left < 0)	// Else if off the left edgeÉ
	{
		thePlayer.wrapping = TRUE;		// Set "wrapping" flag.
		wrapRect = thePlayer.dest;		// Start out with copy of player bounds.
		wrapRect.left += 640;			// Offset it a screenwidth to right.
		wrapRect.right += 640;
										// Ditto with old location.
		wasWrapRect = thePlayer.wasDest;
		wasWrapRect.left += 640;
		wasWrapRect.right += 640;
		
		if (thePlayer.mode == kBones)	// Draw second bones.
		{
			src = playerRects[thePlayer.srcNum];
			src.bottom = src.top + thePlayer.frame;
			CopyMask(&((GrafPtr)playerSrcMap)->portBits, 
					&((GrafPtr)playerMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&src, &src, &wrapRect);
		}
		else							// Draw second player (not bones).
		{
			CopyMask(&((GrafPtr)playerSrcMap)->portBits, 
					&((GrafPtr)playerMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&playerRects[thePlayer.srcNum], 
					&playerRects[thePlayer.srcNum], 
					&wrapRect);
		}
		thePlayer.wrap = wrapRect;
		AddToUpdateRects(&wrapRect);	// Add this to our list of update rects.
	}
	else
		thePlayer.wrapping = FALSE;		// Otherwise, we're not wrapping.
}

//--------------------------------------------------------------  DrawTorches

// This handles drawing the two torch's flames.  It chooses randomly fromÉ
// 4 torch graphics and draws right over the old torches.

void DrawTorches (void)
{
	short		who;
	
	who = RandomInt(4);
	if (evenFrame)		// Only draw 1 torch - left on even framesÉ
	{
		CopyBits(&((GrafPtr)flameSrcMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&flameRects[who], &flameDestRects[0], srcCopy, 0L);
		AddToUpdateRects(&flameDestRects[0]);
	}
	else				// and draw the right torch on odd frames.
	{					// We do this even/odd thing for speed.  Why draw both?
		CopyBits(&((GrafPtr)flameSrcMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&flameRects[who], &flameDestRects[1], srcCopy, 0L);
		AddToUpdateRects(&flameDestRects[1]);
	}
}

//--------------------------------------------------------------  DrawHand

// This function takes care of drawing the hand offscreen.  There are onlyÉ
// two (well really three) choices - hand open, hand clutching (or no handÉ
// in which case both options are skipped).

void DrawHand (void)
{
	if (theHand.mode == kOutGrabeth)		// Fingers open.
	{
		CopyMask(&((GrafPtr)handSrcMap)->portBits, 
				&((GrafPtr)handMaskMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&handRects[0], 
				&handRects[0], 
				&theHand.dest);
		AddToUpdateRects(&theHand.dest);
	}
	else if (theHand.mode == kClutching)	// Fingers clenched.
	{
		CopyMask(&((GrafPtr)handSrcMap)->portBits, 
				&((GrafPtr)handMaskMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&handRects[1], 
				&handRects[1], 
				&theHand.dest);
		AddToUpdateRects(&theHand.dest);
	}
}

//--------------------------------------------------------------  DrawEye

// This function draws the eye (if it's floating about - stalking).

void DrawEye (void)
{
	if (theEye.mode == kStalking)
	{
		CopyMask(&((GrafPtr)eyeSrcMap)->portBits, 
				&((GrafPtr)eyeMaskMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&eyeRects[theEye.srcNum], 
				&eyeRects[theEye.srcNum], 
				&theEye.dest);
		AddToUpdateRects(&theEye.dest);
	}
}

//--------------------------------------------------------------  CopyAllRects

// This function goes through the list of "update rects" and copies from anÉ
// offscreen pixmap to the main screen.  It is at this instant (during theÉ
// execution of the below function) that the screen actually changes.  TheÉ
// whole rest of Glypha is, in essence, there only to lead up, ultimately, É
// to this function.

void CopyAllRects (void)
{
	short		i;
	
	if (whichList)	// Every other frame, we alternate which list we use.
	{				// Copy new graphics to screen (sphinxes, player, etc.).
		for (i = 0; i < numUpdateRects1; i++)
		{
			CopyBits(&((GrafPtr)workSrcMap)->portBits, 
					&(((GrafPtr)mainWindow)->portBits), 
					&updateRects1[i], &updateRects1[i], srcCopy, playRgn);
		}
					// Patch up old graphics from last frame (old sphinx locations, etc.).
		for (i = 0; i < numUpdateRects2; i++)
		{
			CopyBits(&((GrafPtr)workSrcMap)->portBits, 
					&(((GrafPtr)mainWindow)->portBits), 
					&updateRects2[i], &updateRects2[i], srcCopy, playRgn);
		}
					// Clean up offscreen (get rid of sphinxes, etc.).
		for (i = 0; i < numUpdateRects1; i++)
		{
			CopyBits(&((GrafPtr)backSrcMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&updateRects1[i], &updateRects1[i], srcCopy, playRgn);
		}
		
		numUpdateRects2 = 0;	// Reset number of rects to zero.
		whichList = !whichList;	// Toggle flag to use other list next frame.
	}
	else
	{				// Copy new graphics to screen (sphinxes, player, etc.).
		for (i = 0; i < numUpdateRects2; i++)
		{
			CopyBits(&((GrafPtr)workSrcMap)->portBits, 
					&(((GrafPtr)mainWindow)->portBits), 
					&updateRects2[i], &updateRects2[i], srcCopy, playRgn);
		}
					// Patch up old graphics from last frame (old sphinx locations, etc.).
		for (i = 0; i < numUpdateRects1; i++)
		{
			CopyBits(&((GrafPtr)workSrcMap)->portBits, 
					&(((GrafPtr)mainWindow)->portBits), 
					&updateRects1[i], &updateRects1[i], srcCopy, playRgn);
		}
					// Clean up offscreen (get rid of sphinxes, etc.).
		for (i = 0; i < numUpdateRects2; i++)
		{
			CopyBits(&((GrafPtr)backSrcMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&updateRects2[i], &updateRects2[i], srcCopy, playRgn);
		}
		
		numUpdateRects1 = 0;	// Reset number of rects to zero.
		whichList = !whichList;	// Toggle flag to use other list next frame.
	}
}

//--------------------------------------------------------------  DrawPlayer

// Although called "DrawPlayer()", this function actually does its drawingÉ
// offscreen.  It is the above routine that will finally copy our offscreenÉ
// work to the main screen.  Anyway, the below function draws the playerÉ
// offscreen in the correct position and state.

void DrawPlayer (void)
{
	Rect		src;
	
	if ((evenFrame) && (thePlayer.mode == kIdle))
	{			// On even frames, we'll draw the "flashed" graphic of the player.
				// If you've played Glypha, you notice that the player begins aÉ
				// game flashing alternately between bones and a normal player.
		CopyMask(&((GrafPtr)idleSrcMap)->portBits, 
				&((GrafPtr)playerMaskMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&idleSrcRect, 
				&playerRects[thePlayer.srcNum], 
				&thePlayer.dest);
	}
	else if (thePlayer.mode == kBones)
	{			// If the player is dead and a pile of bonesÉ
		src = playerRects[thePlayer.srcNum];
		src.bottom = src.top + thePlayer.frame;
		CopyMask(&((GrafPtr)playerSrcMap)->portBits, 
				&((GrafPtr)playerMaskMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&src, &src, &thePlayer.dest);
	}
	else		// Else, if the player is neither idle nor deadÉ
	{
		CopyMask(&((GrafPtr)playerSrcMap)->portBits, 
				&((GrafPtr)playerMaskMap)->portBits, 
				&((GrafPtr)workSrcMap)->portBits, 
				&playerRects[thePlayer.srcNum], 
				&playerRects[thePlayer.srcNum], 
				&thePlayer.dest);
	}
				// Now we add the player to the update rect list.
	AddToUpdateRects(&thePlayer.dest);
				// Record old locations.
	thePlayer.wasH = thePlayer.h;
	thePlayer.wasV = thePlayer.v;
				// Record old bounds rect.
	thePlayer.wasDest = thePlayer.dest;
}

//--------------------------------------------------------------  CheckEnemyWrapAround

// This function both determines whether or not an enemy (sphinx) is wrapping around.
// If it is, the "second" wrapped-around enemy is drawn.

void CheckEnemyWrapAround (short who)
{
	Rect		wrapRect, wasWrapRect, src;
	
	if (theEnemies[who].dest.right > 640)	// Is enemy off the right edge of screen?
	{
		wrapRect = theEnemies[who].dest;	// Copy bounds.
		wrapRect.left -= 640;				// Offset bounds copy to left (one screen width).
		wrapRect.right -= 640;
											// Ditto with old bounds.
		wasWrapRect = theEnemies[who].wasDest;
		wasWrapRect.left -= 640;
		wasWrapRect.right -= 640;
											// Handle "egg" enemies.
		if ((theEnemies[who].mode == kFalling) || (theEnemies[who].mode == kEggTimer))
		{									// Handle "egg" enemy sinking into platform.
			if ((theEnemies[who].mode == kEggTimer) && (theEnemies[who].frame < 24))
			{
				src = eggSrcRect;
				src.bottom = src.top + theEnemies[who].frame;
			}
			else
				src = eggSrcRect;
			CopyMask(&((GrafPtr)eggSrcMap)->portBits, 
					&((GrafPtr)eggMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&src, &src, &wrapRect);
		}
		else								// Otherwise, if enemy not an eggÉ
		{
			CopyMask(&((GrafPtr)enemyFlySrcMap)->portBits, 
					&((GrafPtr)enemyFlyMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&enemyRects[theEnemies[who].srcNum], 
					&enemyRects[theEnemies[who].srcNum], 
					&wrapRect);
		}
		AddToUpdateRects(&wrapRect);		// Add bounds to update rect list.
	}
	else if (theEnemies[who].dest.left < 0)	// Check to see if enemy off left edge instead.
	{
		wrapRect = theEnemies[who].dest;	// Make a copy of enemy bounds.
		wrapRect.left += 640;				// Offset it right one screens width.
		wrapRect.right += 640;
											// Ditto with old bounds.
		wasWrapRect = theEnemies[who].wasDest;
		wasWrapRect.left += 640;
		wasWrapRect.right += 640;
		if ((theEnemies[who].mode == kFalling) || (theEnemies[who].mode == kEggTimer))
		{									// Blah, blah, blah.  This is just like the above.
			if ((theEnemies[who].mode == kEggTimer) && (theEnemies[who].frame < 24))
			{
				src = eggSrcRect;
				src.bottom = src.top + theEnemies[who].frame;
			}
			else
				src = eggSrcRect;
			CopyMask(&((GrafPtr)eggSrcMap)->portBits, 
					&((GrafPtr)eggMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&src, &src, &wrapRect);
		}
		else
		{
			CopyMask(&((GrafPtr)enemyFlySrcMap)->portBits, 
					&((GrafPtr)enemyFlyMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&enemyRects[theEnemies[who].srcNum], 
					&enemyRects[theEnemies[who].srcNum], 
					&wrapRect);
		}
		AddToUpdateRects(&wrapRect);
	}
}

//--------------------------------------------------------------  DrawEnemies

// This function draws all the sphinx enemies (or eggs if they're in that state).
// It doesn't handle wrap-around (the above function does) but it does call it.

void DrawEnemies (void)
{
	Rect		src;
	short		i;
	
	for (i = 0; i < numEnemies; i++)	// Go through all enemies.
	{
		switch (theEnemies[i].mode)		// Handle the different modes as seperate cases.
		{
			case kSpawning:				// Spawning enemies are "growing" out of the platform.
			src = enemyRects[theEnemies[i].srcNum];
			src.bottom = src.top + theEnemies[i].frame;
			CopyMask(&((GrafPtr)enemyWalkSrcMap)->portBits, 
					&((GrafPtr)enemyWalkMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&src, &src, &theEnemies[i].dest);
			AddToUpdateRects(&theEnemies[i].dest);
										// Don't need to check wrap-around, when enemiesÉ
										// spawn, they're never on the edge of screen.
			theEnemies[i].wasDest = theEnemies[i].dest;
			theEnemies[i].wasH = theEnemies[i].h;
			theEnemies[i].wasV = theEnemies[i].v;
			break;
			
			case kFlying:				// Flying enemies are air borne (gee).
			CopyMask(&((GrafPtr)enemyFlySrcMap)->portBits, 
					&((GrafPtr)enemyFlyMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&enemyRects[theEnemies[i].srcNum], &enemyRects[theEnemies[i].srcNum], 
					&theEnemies[i].dest);
			AddToUpdateRects(&theEnemies[i].dest);
			CheckEnemyWrapAround(i);	// I like the word "air bourne".
			theEnemies[i].wasDest = theEnemies[i].dest;
			theEnemies[i].wasH = theEnemies[i].h;
			theEnemies[i].wasV = theEnemies[i].v;
			break;
			
			case kWalking:				// Walking enemies are walking.  Enemies.
			CopyMask(&((GrafPtr)enemyWalkSrcMap)->portBits, 
					&((GrafPtr)enemyWalkMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&enemyRects[theEnemies[i].srcNum], &enemyRects[theEnemies[i].srcNum], 
					&theEnemies[i].dest);
			AddToUpdateRects(&theEnemies[i].dest);
										// Don't need to check wrap-around, enemies walkÉ
										// only briefly, and never off edge of screen.
			theEnemies[i].wasDest = theEnemies[i].dest;
			theEnemies[i].wasH = theEnemies[i].h;
			theEnemies[i].wasV = theEnemies[i].v;
			break;
			
			case kFalling:				// Falling enemies are in fact eggs!
			CopyMask(&((GrafPtr)eggSrcMap)->portBits, 
					&((GrafPtr)eggMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&eggSrcRect, &eggSrcRect, &theEnemies[i].dest);
			AddToUpdateRects(&theEnemies[i].dest);
			CheckEnemyWrapAround(i);	// Check for wrap around.
			theEnemies[i].wasDest = theEnemies[i].dest;
			theEnemies[i].wasH = theEnemies[i].h;
			theEnemies[i].wasV = theEnemies[i].v;
			break;
			
			case kEggTimer:				// These are idle, perhaps hatching, eggs.
			if (theEnemies[i].frame < 24)
			{							// Below countdown = 24, the egss are sinkingÉ
				src = eggSrcRect;		// into the platform (hatch time!).
				src.bottom = src.top + theEnemies[i].frame;
			}
			else
				src = eggSrcRect;
			CopyMask(&((GrafPtr)eggSrcMap)->portBits, 
					&((GrafPtr)eggMaskMap)->portBits, 
					&((GrafPtr)workSrcMap)->portBits, 
					&src, &src, &theEnemies[i].dest);
			AddToUpdateRects(&theEnemies[i].dest);
			CheckEnemyWrapAround(i);	// Check for wrap around.
			theEnemies[i].wasDest = theEnemies[i].dest;
			theEnemies[i].wasH = theEnemies[i].h;
			theEnemies[i].wasV = theEnemies[i].v;
			break;
		}
	}
}

//--------------------------------------------------------------  DrawFrame

// This function is the "master" drawing function that calls all the aboveÉ
// routines.  It is called once per frame.

void DrawFrame (void)
{
	DrawTorches();				// Gee, draws the torches?
	DrawHand();					// Draws the hand?
	DrawEye();					// A clue to easing your documentation demandsÉ
	DrawPlayer();				// is to use "smart" names for your functions.
	CheckPlayerWrapAround();	// Check for player wrap-around.
	DrawEnemies();				// Handle all sphinx-type enemy drawing.
	CopyAllRects();				// Put it all onscreen.
}

