
#import <stdio.h>
#import <libc.h>
#import "Maze.h"
#import "PacManGameBrain.h"
#import <appkit/appkit.h>

@implementation Maze

- init
{
	char *appPath, *slashPos;
	int i, j, k;
	FILE *fp;
	
	[super init];
	
	// load image
	mazeParts[1] = [NXImage findImageNamed:"Maze.tiff"];
	mazeParts[2] = [NXImage findImageNamed:"MazeBig.tiff"];
	
	scale = 1;
	
	// get the path to the app wrapper (to find the maze file)
    appPath = (char *)malloc(1024);
    strcpy(appPath, NXArgv[0]);
    if (slashPos = strrchr(appPath, '/')) {
    	slashPos[1] = '\0';
    } else {
        strcpy(appPath, "./");
    }
	strcat(appPath, "mazes");
	
	// read in the maze data
	fp = fopen(appPath, "r");
	if (fp == NULL) fprintf(stderr, "PacMan:  Cannot read maze data!\n");
	for (i=0; i<MAZES; i++) {
		for (j=BLOCK_HEIGHT-1; j>=0; j--) {
			for (k=0; k<BLOCK_WIDTH; k++) {
				mazes[i][k][j] = getc(fp);
				switch(mazes[i][k][j]) { // handle special cases
					case 'D' : { mazes[i][k][j] = 5; // Door
								break; }
					case '>' : { mazes[i][k][j] = 26; // door cap
								break; }
					case '<' : { mazes[i][k][j] = 27; // door cap
								break; }
					case ']' : { mazes[i][k][j] = 33; // end cap
								break; }
					case '[' : { mazes[i][k][j] = 32; // end cap
								break; }
					case ' ' : { mazes[i][k][j] = 28; // dot
								break; }
					case 'O' : { mazes[i][k][j] = 29; // power dot
								break; }
					case '|' : { mazes[i][k][j] = 31; // vert wall
								break; }
					case '-' : { mazes[i][k][j] = 30; // horiz wall
								break; }
					case '^' : { mazes[i][k][j] = 34; // end cap
								break; }
					case 'P' : { mazes[i][k][j] = 7; // player (blank image)
								break; }
					case 'G' : { mazes[i][k][j] = 10; // ghost (blank image)
								break; }
					case '+' : { mazes[i][k][j] = 8; // blank (inside cage)
								break; }
					default  : { mazes[i][k][j] -= 'a'; // get index
								break; }
				}
			}
			getc(fp);
		}
	}
	fclose(fp);
	
	// initizlize other instance vars.
	[self makeMaze:1];
	powerDotShown = YES;
	visibleMaze = YES;
	
	return self;
}


- render:(NXRect *)rect at:(NXPoint *)pos	 // used to render in another view
{
	int j, k;
	NXRect from, check;
	NXPoint pos2;
	
	if (!visibleMaze) return self;
	for (j=0; j<BLOCK_HEIGHT; j++) {
		pos2.y = j * GHOST_SIZE * scale + pos->y;
		for (k=0; k<BLOCK_WIDTH; k++) {
		    if ((maze[k][j] != POWER) || powerDotShown) {	// if power dot
				// blinked off, we don't render it.
				NXSetRect(&check, k * GHOST_SIZE, j * GHOST_SIZE,
					GHOST_SIZE, GHOST_SIZE);
				if (NXIntersectsRect(&check, rect)) { // only draw maze bits
					// in the rects that intersect the rect passed to us
					NXSetRect(&from,
						(maze[k][j] % PER_ROW) * GHOST_SIZE * scale,
						(maze[k][j] / PER_ROW) * GHOST_SIZE * scale,
						GHOST_SIZE * scale, GHOST_SIZE * scale);
					pos2.x = k * GHOST_SIZE * scale + pos->x;
					[mazeParts[scale] composite:NX_SOVER
						fromRect:&from toPoint:&pos2];
	}	}	}	}
	return self;

}


- makeMaze:(int)num
{	// build a maze from the template; fill it with dots, set up new player and
	// ghost positions, etc.
	int j, k;
	int ghost = 0;
	int pdot = 0;
	
	dots = 0;
	if ((num >= 0) && (num < MAZES)) { // make sure in range
		mazeNum = num;
		player[0] = -32; player[1] = -32; // sum is neg if no player spot found
		// set up player/ghost/power dot position, etc. for maze.
		for (j=0; j<BLOCK_HEIGHT; j++) {
			for (k=0; k<BLOCK_WIDTH; k++) {
				maze[k][j] = mazes[num][k][j];
				switch (maze[k][j]) {
					case GHDOOR  : {	door[0] = k;	// door for ghosts
						door[1] = j; break; }
					case PLAYER  : { player[0] = k;		// Player
						player[1] = j;
						maze[k][j] = EMPTY; break; }
					case GHOST : { ghosts[ghost * 2] = k;	// Ghost
						ghosts[ghost * 2 + 1] = j;
						maze[k][j] = EMPTY;
						if (++ghost > MAX_GHOSTS) { fprintf(stderr,
							"Maze %d has too many ghosts.\n", num); }
						break; }
					case POWER : { power[pdot * 2] = k;	// Power Dot
						power[pdot * 2 + 1] = j; dots++;
						if (++pdot > MAX_POWER_DOTS) { fprintf(stderr,
							"Maze %d has too many power dots.\n", num); }
						break; }
					case DOT : { dots++;					// Normal Dot
						break; }
					default : { break; }				// do nothing
				}
			}
		}
		if (!ghost) { fprintf(stderr, "Maze %d has no ghosts.\n", num); }
		if (!pdot) { fprintf(stderr, "Maze %d has no power dots.\n", num); }
		if (player[0]+player[1] < 0) {
			fprintf(stderr, "Maze %d has no player.\n", num); }
	}
	return self;
}


- playerPosition:(int *)x :(int *)y	// return x, y of player/fruit
{
	*x = player[0] * GHOST_SIZE; *y = player[1] * GHOST_SIZE;
	return self;
}


- doorPosition:(int *)x :(int *)y	// return x, y of door to ghost chamber
{
	*x = door[0] * GHOST_SIZE; *y = door[1] * GHOST_SIZE;
	return self;
}


- (const int *)powerDot			// return pointer to power array
{
	return power;
}


- (const int *)ghosts				// return pointer to ghosts array
{
	return ghosts;
}


- (BOOL)powerDotAt:(int)x :(int)y	// return YES if power dot in block
{
	// only have to be 1/2 way into block to eat dot:
	int basex = x / GHOST_SIZE + ((x % GHOST_SIZE) > (GHOST_SIZE / 2));
	int basey = y / GHOST_SIZE + ((y % GHOST_SIZE) > (GHOST_SIZE / 2));
	
	if (maze[basex][basey] == POWER) { // munch power dot
		[[[NXApp delegate] scoreKeeper] addToScore:POWERDOTPOINTS];
		maze[basex][basey] = EMPTY;
		dots--;
		dotx = basex; doty = basey;	// save them away
		return YES;
	}
	return NO;
}

- (BOOL)eatDotAt:(int)x :(int)y		// return YES if OK, NO if no dot was there
{
	int basex = x / GHOST_SIZE;
	int basey = y / GHOST_SIZE;
	int dx = x % GHOST_SIZE;
	int dy = y % GHOST_SIZE;
	
	if (!dx) { // in a vertical column or corner/junction
		if (dy > GHOST_SIZE / 2) basey++;
	} else { // on a horiz. row
		if (dx > GHOST_SIZE / 2) basex++;
	}
	
	if (maze[basex][basey] == DOT) { // munch dot
		[[[NXApp delegate] scoreKeeper] addToScore:DOTPOINTS];
		maze[basex][basey] = EMPTY;
		dots--;
		dotx = basex; doty = basey;	// save them away
		return YES;
	}
	return NO;
}

- (int)dots							// return the number of dots left in maze
{
	return dots;
}

- lastDot:(int *)xx :(int *)yy
{
	*xx = dotx * GHOST_SIZE;
	*yy = doty * GHOST_SIZE;
	return self;
}

- (BOOL)playerWall:(float)x :(float)y	// YES if player can't be in block
{
	int mazex = x / GHOST_SIZE;
	int mazey = y / GHOST_SIZE;
	
	if ((maze[mazex][mazey] == EMPTY)	||
		(maze[mazex][mazey] == DOT)		||
		(maze[mazex][mazey] == POWER)) return NO;
	return YES;
}

- (BOOL)monsterWall:(float)x :(float)y	// YES if ghost can't be in block
{
	int mazex = x / GHOST_SIZE;
	int mazey = y / GHOST_SIZE;
	
	if ((maze[mazex][mazey] == EMPTY)	||
		(maze[mazex][mazey] == DOT)		||
		(maze[mazex][mazey] == GHDOOR)	||
		(maze[mazex][mazey] == POWER)) return NO;
	return YES;
}

- blinkPowerDot					// toggle ON/OFF state of power dots
{
	powerDotShown = !powerDotShown;
	return self;
}

- visible:(BOOL)flag				// used to tell us if the maze is visible
{
	visibleMaze = flag;
	return self;
}

- (BOOL)isVisible;					// used to tell others if maze is visible
{ return visibleMaze; }

- (int)scale
{
	return scale;
}

- setScale:(int)newScale
{
	if ((newScale < 1) || (newScale > 2)) return self;
	scale = newScale;
	return self;
}


@end
