
/* Generated by Interface Builder */

#import <libc.h>
#import "Monster.h"
#import "GameBrain.h"
#import "Player.h"
#import "Maze.h"
#import <appkit/NXImage.h>
#import <appkit/Application.h>
#import <appkit/graphics.h>

// Get defs of static data arrays which control movement choices
#import "MonsterMovement.h"


@implementation Monster

- init
{
	return [self initGhost:0 player:nil maze:nil at:0 :0];
}

- initGhost:(int)num player:(id)pac maze:(id)world at:(int)sx :(int)sy
{	// initialize all instance vars
	[super init];
	ghosts = [NXImage findImageNamed:"Ghosts.tiff"];
	lastx = - 4 * GHOST_SIZE;
	lasty = - 4 * GHOST_SIZE;
	player = pac;
	maze = world;
	eatable = NO;
	state = HOVER;
	myX = sx * GHOST_SIZE; myY = sy * GHOST_SIZE;
	myColor = num;
	px = 2; py = 0;
	loops = 0;
	fix = NO;
	
	return self;
}

- (BOOL)canBeEaten		// returns "eatable".
{ return eatable; }

- move:sender		// move one frame
{
	// use current state to decide which type of movement to do
	switch (state) {
		case HOVER : {
			[self hover];
			break;
		}
		case CHASE : {
			[self chase];
			break;
		}
		case RUN   : {
			[self runAway];
			break;
		}
		case HOME  : {
			[self goHome];
			break;
		}
		default    : {	// no movement, since illegal state
			// (we should _never_ get here!)
			px = 0; py = 0;
			break;
		}
	}
	return self;
}

- hover				// hover moves solid ghost when in ghost chamber
{
	int door_x, door_y;
	int k;
	
	[maze doorPosition:&door_x :&door_y];

	if (myX == door_x) {
		if (myY >= (door_y + GHOST_SIZE)) {
// The ghost is now completely outside the box; we will
// change its state so that it follows the player around
// (or runs away if power dot was eaten)
			if (eatable) {
				state = RUN;
				[self runAway];
			} else {
				state = CHASE;
				[self chase];
			}
			return self;
		} else if (myY >= (door_y - 1 - GHOST_SIZE)) {
// The ghost is directly underneath the door to the outside.
// Send it out if more than 3 loops made, or 50% chance to leave if
// at least one loop is complete.
			if ((++loops) > 1) {
				if ((loops > 3) || ((random() & 0x0f) > 7) || eatable) {
					if (eatable) py = 1; // keep right speed
					else py = 2;
					px = 0;
					return self;
	}	}	}	}
// The rest of the method drives the ghost around the
// box in a counterclockwise pattern.
	k = 0;
	if (px > 0) {
		if ([maze monsterWall:(myX + 1 + GHOST_SIZE) :myY])
			{ k = 1; px = 0; py = 2; }
	} else if (px < 0) {
		if ([maze monsterWall:(myX - 2) :myY])
			{ k = 1; px = 0; py = -2; }
	} else if (py > 0) {
		if ([maze monsterWall:myX :(myY + 1 + GHOST_SIZE)])
			{ k = 1; px = -2; py = 0; }
	} else if ([maze monsterWall:myX :(myY - 2)])
			{ k = 1; px = 2; py = 0; }
	if (eatable && k) {
		px /= 2;
		py /= 2;
	}
	if ((abs(py) == 2) && (myY & 0x1)) fix = YES;	// keep synced
	if ((abs(px) == 2) && (myX & 0x1)) fix = YES;	// keep synced
	return self;
}

- runAway			// ghost runs away at 1/2 speed after power pill
{
    int pac_x = [player xpos];
	int pac_y = [player ypos];
	register int tdir = 0x0f, sense;
	int dx = myX % GHOST_SIZE; int dy = myY % GHOST_SIZE;
	

	if (dx) tdir &= 0x03; // only allow l/r to sync us up
	if (dy) tdir &= 0x0c; // only allow u/d to sync us up
// first, find the directions in which this ghost can go
	if ([maze playerWall:(myX+GHOST_SIZE) :myY]	|| (px < 0)) tdir &= ~0x01;
	if ([maze playerWall:(myX-1) :myY] 			|| (px > 0)) tdir &= ~0x02;
	if ([maze playerWall:myX     :(myY-1)] 		|| (py > 0)) tdir &= ~0x04;
	if ([maze playerWall:myX :(myY+GHOST_SIZE)]	|| (py < 0)) tdir &= ~0x08;

// now choose the new direction for the ghost
	if ((random() & 0x0f) > 4)
		sense = find[sgn(pac_y - myY) + 1][sgn(pac_x - myX) + 1];
	else sense = random() & 0x07;
	px = rxvec[tdir][sense];
	py = ryvec[tdir][sense];
	return self;
}

- chase				// ghost chases player down
{
    int pac_x = [player xpos];
	int pac_y = [player ypos];
	register int tdir = 0x0f;
	register int sense;
	int dx = myX % GHOST_SIZE; int dy = myY % GHOST_SIZE;
	
// sync position to block boundaries
	if (px < 0) {
	  if (dx > 1) return self;
	  if (dx == 1) { px = -1; return self; }
	} else if (px > 0) {
	  if ((dx < GHOST_SIZE - 1) && dx) return self;
	  if (dx == GHOST_SIZE - 1) { px = 1; return self; }
	} else if (py < 0) {
	  if (dy > 1) return self;
	  if (dy == 1) { py = -1; return self; }
	} else if (py > 0) {
	  if ((dy < GHOST_SIZE - 1) && dy) return self;
	  if (dy == GHOST_SIZE - 1) { py = 1; return self; }
	}

// first, find the directions in which this ghost can go
	if ([maze playerWall:(myX+2+GHOST_SIZE) :myY]	|| (px < 0)) tdir &= ~0x01;
	if ([maze playerWall:(myX-2) :myY]				|| (px > 0)) tdir &= ~0x02;
	if ([maze playerWall:myX     :(myY-2)]			|| (py > 0)) tdir &= ~0x04;
	if ([maze playerWall:myX :(myY+2+GHOST_SIZE)]	|| (py < 0)) tdir &= ~0x08;

// now choose the new direction for the ghost
	if ((random() & 0x0f) > 4) // 75% of time, it's algorithmic
		sense = find[sgn(pac_y - myY) + 1][sgn(pac_x - myX) + 1];
	else sense = random() & 0x07;
	px = fxvec[tdir][sense];
	py = fyvec[tdir][sense];
	return self;
}

- goHome			// used to make the eyes run to ghost chamber at 2x speed
{
	int door_x, door_y;
	register int tdir = 0x0f, sense;
	int dx = myX % GHOST_SIZE; int dy = myY % GHOST_SIZE;

	[maze doorPosition:&door_x :&door_y];
	// there, we'll "catch" it and suck it into the box.
	if (myX == door_x) { // using GHOST_SIZE * 2 moves it to bottom of box
		if ((myY >= door_y - GHOST_SIZE * 2) && (myY <= door_y + GHOST_SIZE)) {
			if ((myY >= door_y - GHOST_SIZE) && (myY <= door_y + GHOST_SIZE)) {
// The ghost is right above the door to the ghost box.
// We'll send it down into the box.  We're assuming
// here that the ghost box is below the door.
// If not, the results will be unpredictable.
				px = 0;
				if (dy && (dy < 4)) py = -dy; // sync to blocks
				else py = -4;
			} else if (myY == (door_y - GHOST_SIZE * 2)) {
// The ghost is all the way inside the box.  Here it'll
// be "reborn" -- its state will be changed to that of a
// solid ghost hovering inside the ghost box.
				state = HOVER;
				loops = 0;
				eatable = NO;
				px = 2;
				py = 0;
			}
			return self;
	}	}

	door_y += GHOST_SIZE;	// seek the block above the door; when ghost gets
// now, sync ghost to maze blocks
	if (px < 0) {
	  if (dx > 3) return self;
	  if (dx) { px = -dx; return self; }
	} else if (px > 0) {
	  if ((dx < GHOST_SIZE - 3) && dx) return self;
	  else if (dx) { px = GHOST_SIZE - dx; return self; }
	} else if (py < 0) {
	  if (dy > 3) return self;
	  if (dy) { py = -dy; return self; }
	} else if (py > 0) {
	  if ((dy < GHOST_SIZE - 3) && dy) return self;
	  else if (dy) { py = GHOST_SIZE - dy; return self; }
	}

	if (dx) tdir &= 0x03;
	if (dy) tdir &= 0x0c;
	
// first, find the directions in which this ghost can go
	if ([maze monsterWall:(myX+4+GHOST_SIZE) :myY]	|| (px < 0)) tdir &= ~0x01;
	if ([maze monsterWall:(myX-4) :myY]				|| (px > 0)) tdir &= ~0x02;
	if ([maze monsterWall:myX     :(myY-4)]			|| (py > 0)) tdir &= ~0x04;
	if ([maze monsterWall:myX :(myY+4+GHOST_SIZE)]	|| (py < 0)) tdir &= ~0x08;

// now choose the new direction for the ghost--not random!
	sense = find[sgn(door_y - myY) + 1][sgn(door_x - myX) + 1];
	px = pxvec[tdir][sense];
	py = pyvec[tdir][sense];
	return self;
}

- powerDot:(BOOL)eat	// called when power dots are eaten or run out.
{
	if (state != HOME) eatable = eat;
	if ((state == CHASE) && (eat == YES)) {
		state = RUN;
		px  = 0; py = 0;	// allows quick reverse of direction
		[self runAway];
	}
	if ((state == RUN)   && (eat ==  NO)) {
		state = CHASE;
		if (abs(px) < 2) px *= 2; // ifs should actually be unnecessary...
		if (abs(py) < 2) py *= 2;
		[self chase];
	}
	if (state == HOVER) {	// speed up or slow down hovering ghost
		// has to happen here, since hover leaves it alone until it hits
		// an edge of the box...
		if (eat) {
			if (abs(px) == 2) px /=2;
			if (abs(py) == 2) py /= 2;
		} else {
			if (abs(px) == 1) px *=2;
			if (abs(py) == 1) py *= 2;
	}	}
	if (eat) {
		powerState = DOT_EATEN;
		powerTime = 0;
		[[NXApp delegate] resetGhostScore];
	} else  powerState = DOT_NONE;
	return self;
}

- (int)munch			// called when pac intersects ghost
{
	if (state == HOME) return HARMLESS;
	if (!eatable) return NO;
	px  = 0; py  = 0;	// allows reverse of direction
	state = HOME;
	[self goHome];
	eatable = NO;
	return YES;
}

- moveOneFrame
{
	[super moveOneFrame];		// move the ghost
	if (fix) {
		myY -= sgn(py);	// fix for when hovering -- to re-sync
		myX -= sgn(px);
		fix = 0;
	}
	return self;
}

- renderAt:(int)posx :(int)posy move:(BOOL)moveOk	// draw ghost
{
	int col = myColor & 0x3;
	NXRect from;
	NXPoint pos;
	
	[super renderAt:posx :posy move:moveOk];

	// decide to draw invisible or eye ghosts if necessary
	if (eatable && ((powerState != DOT_FADING) || (img & 0x4)))
		col = INVISIBLE_GHOST;
	else if (state == HOME) col = GHOST_EYES;
	NXSetRect(&from,
		((++img >> 1) & 0x3) * GHOST_SIZE,	col * GHOST_SIZE,
		GHOST_SIZE, GHOST_SIZE);
	pos.x = myX + posx; pos.y = myY + posy;
	[ghosts composite:NX_SOVER fromRect:&from toPoint:&pos];
	if (![[NXApp delegate] paused]) {
		if (((++powerTime) >> 4) > flash_ticks[[[NXApp delegate] level]])
			powerState = DOT_FADING;
		if ((powerTime >> 4) > off_ticks[[[NXApp delegate] level]])
			[self powerDot:NO];
	}
	return self;
}


@end
