
// Handles moving and rendering the Pac, whether under player or demo control.

#import <libc.h>
#import "Player.h"
#import "Monster.h"
#import "Maze.h"
#import "PacManView.h"
#import <gamekit/gamekit.h>
#import <appkit/appkit.h>
#import "PacMovement.h"

// used to translate PAC_<direction> to px, py velocities
static int pacxvec[9] = { 0, 2, -2, 0,  0, 0, 0, 0,  0 };
static int pacyvec[9] = { 0, 0,  0, 0, -2, 0, 0, 0,  2 };
//static int   opdir[9] = { 0, 2,  1, 0,  8, 0, 0, 0,  4 };
static int bothdir[9] = { 0, 3,  3, 0, 12, 0, 0, 0, 12 };

// the y-coord in the Pacs.tiff image indexed by direction
static int pacypic[9] = { 0, 5 * PAC_WIDTH,  4 * PAC_WIDTH, 0,
	6 * PAC_WIDTH, 0, 0, 0, 7 * PAC_WIDTH };
static int pacydie[9] = { 0, 1 * PAC_WIDTH,  0,			  0,
	2 * PAC_WIDTH, 0, 0, 0, 3 * PAC_WIDTH };


@implementation Player

- init						// initialize the player
{
	[super init];
	pacs[1] = [NXImage findImageNamed:"Pacs.tiff"];
	pacs[2] = [NXImage findImageNamed:"PacsBig.tiff"];
	nextDir = PAC_STOP;
	curDir = PAC_STOP;
	playerStopped = NO;
	return self;
}

- (BOOL)newPlayer			// get and set up a new Pac.  Returns NO if can't
{
	if (![pacsLeft decNumUp:self]) return NO;		// no pacs left
	[self resetPlayer];
	return YES;
}

- resetPlayer				// reset all player info
{
	curDir = PAC_RIGHT;
	nextDir = PAC_RIGHT;
	px = 0; py = 0;
	state = PAC_ALIVE;
	cycle = 0;
	lastx = - 4 * GHOST_SIZE;
	lasty = - 4 * GHOST_SIZE;
	playerStopped = NO;
	[maze playerPosition:&myX :&myY];
	return self;
}

- (BOOL)pacAlive				// returns YES if Pac is alive
{
	if (state < PAC_ALIVE) return NO;
	return YES;
}

- demoMove:sender	// handles Pac movement during the demo sequence
{
	int i, zz; float xx, yy; int zi = 1;
    int ghost_x = 0;
	int ghost_y = 0;
	int closest = (BLOCK_WIDTH + BLOCK_HEIGHT) * GHOST_SIZE;
	register int tdir = 0x0f;
	register int sense = 0;
	int dx = myX % GHOST_SIZE; int dy = myY % GHOST_SIZE;
	
	// keep us in the track between maze walls
	if (dx) tdir &= 0x03;
	if (dy) tdir &= 0x0c;
	
	for (i=0; i<4; i++) {	// find closest ghost; run away from it.
		[[sender ghost:i] at:&xx :&yy];
		zz = abs(xx - myX) + abs(yy - myY);
		if (zz < closest) {
			zi = i;
			closest = zz;
			ghost_x = xx;
			ghost_y = yy;
	}	}
	
// first, find the directions in which pac can go
	if ((abs(ghost_x - myX) >= GHOST_SIZE * 2 - 2) ||
			(abs(ghost_y - myY) >= GHOST_SIZE * 2 - 2)) {
		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;
		if ((random() & 0x0f) > 10) {
			// worry about shorter distance first.
			if (abs(ghost_x - myX) < abs(ghost_y - myY)) {
				if (tdir & 0x03) tdir &= 0x03;
			} else {
				if (tdir & 0x0c) tdir &= 0x0c;
		}	}
	} else { // if a ghost is close by, we can reverse direction.
		if ([maze playerWall:myX     :(myY-1)]) tdir &= ~0x04;
		if ([maze playerWall:myX :(myY+GHOST_SIZE)]) tdir &= ~0x08;
		if ([maze playerWall:(myX+GHOST_SIZE) :myY]) tdir &= ~0x01;
		if ([maze playerWall:(myX-1) :myY]) tdir &= ~0x02;
	}
	
// now choose the new direction for the pac
	if ([[sender ghost:zi] canBeEaten]) {
	// chase an eatable ghost
		sense = demofind[sgn(ghost_y - myY) + 1][sgn(ghost_x - myX) + 1];
		px = chasexvec[tdir][sense];
		py = chaseyvec[tdir][sense];
	} else {
		if (((abs(ghost_x - myX) <= GHOST_SIZE * 2 - 2) &&
				(abs(ghost_y - myY) <= GHOST_SIZE * 2 - 2)) ||
				((random() & 0x0f) > 2)) {	// if close or random allows, go
				// according to program
			sense = demofind[sgn(ghost_y - myY) + 1][sgn(ghost_x - myX) + 1];
		} else {	// random direction occasionally
		 	sense = random() & 0x07;
		}
		px = demoxvec[tdir][sense];
		py = demoyvec[tdir][sense];
	}	// set up the direction:
	curDir = demoDir[sgn(py) + 1][sgn(px) + 1];
	return self;
}

- move:sender				// Move the PacMan one animation frame
{
	int dx = myX % PAC_WIDTH; int dy = myY % PAC_WIDTH;
	register int tdir = 0x0f;

	if (state < PAC_DEAD) { // Pac is dying
		state++;
	} else if (state >= PAC_ALIVE) { // Pac is alive, so move it

		if ([gameView demoMode]) {
		// if demo mode, we move the pac ourselves
			[self demoMove:sender];
		} else {
		// if not demo mode, we let the user decide how to move the pac.
			// first, find the directions in which the pac can go
			if (dx || dy) tdir = bothdir[curDir]; // curr. dir or opp.
				// direction are allowed; other dir. only at junctions.
			if ([maze playerWall:(myX+PAC_WIDTH) :myY]) tdir &= ~PAC_RIGHT;
			if ([maze playerWall:(myX-2) :myY]) tdir &= ~PAC_LEFT;
			if ([maze playerWall:myX     :(myY-2)]) tdir &= ~PAC_DOWN;
			if ([maze playerWall:myX :(myY+PAC_WIDTH)]) tdir &= ~PAC_UP;

			// see if we can go the "next" direction the player wants or not...
			if (nextDir & tdir) { // we can!
				curDir = nextDir;
			}
			if (playerStopped) tdir = 0x00; // don't move if stopped
			// now choose the new direction for the pac
			//   the "&" makes sure we stop if we can't go further in this dir.
			px = pacxvec[curDir & tdir];
			py = pacyvec[curDir & tdir];
		}

	} else { // Pac is dead so do nothing.
		px = 0; py = 0;
	}
	return self;
}

- newDirection:(int)newDir		// send Pac in new direction.
{
	if (newDir == PAC_STOP) {
		playerStopped = YES;
	} else {
		nextDir = newDir;
		playerStopped = NO;
	}
	return self;
}

- pacDie { state = PAC_DYING; return self; }	// the pac will melt

- renderAt:(int)posx :(int)posy move:(BOOL)moveOk	// draw pac
		// you should lock focus on view that gets the Pac first.
{
	NXRect from;
	NXPoint pos;

	[super renderAt:posx :posy move:moveOk];
	pos.x = myX * scale + posx; pos.y = myY * scale + posy;

	if (state == PAC_ALIVE) {	// draw living pac
		NXSetRect(&from, cycle * PAC_WIDTH * scale, pacypic[curDir] * scale,
			PAC_WIDTH * scale, PAC_WIDTH * scale);
	} else if (state < PAC_DEAD) { // draw dying pac
		NXSetRect(&from, (state - PAC_DYING) * PAC_WIDTH * scale,
				pacydie[curDir] * scale, PAC_WIDTH * scale, PAC_WIDTH * scale);
		if (!cycle) state++;
		if (cycle == 1) cycle = -1; // change period while we die
	} else { // if dead, draw no pac (upper rt. of image is blank)
		NXSetRect(&from, 10 * PAC_WIDTH * scale, 7 * PAC_WIDTH * scale,
			PAC_WIDTH * scale, PAC_WIDTH * scale);
	}
	[pacs[scale] composite:NX_SOVER fromRect:&from toPoint:&pos];

	cycle++; if (cycle > 5) cycle = 0;	// cycle through six images.
	return self;
}


@end
