This is Monster.m in view mode; [Download] [Up]
#import <libc.h> #import "Monster.h" #import "PacManGameBrain.h" #import "Player.h" #import "Maze.h" #import <appkit/appkit.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[1] = [NXImage findImageNamed:"Ghosts.tiff"]; ghosts[2] = [NXImage findImageNamed:"GhostsBig.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]; } if (myY == (door_y + GHOST_SIZE - 1)) py = 1; if (myY == (door_y + GHOST_SIZE + 1)) py = -1; 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; long rnum = random() & 0x3FF; register BOOL noReverse = (rnum != 1); register int sense, curPath; 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; } } // determine where the pac is at in relation to us. curPath = find[sgn(pac_y - myY) + 1][sgn(pac_x - myX) + 1]; // we don't use random sense if ghost can reverse, // since it ends up looking dumb if (((random() & 0x0f) > 4) || !noReverse) // 75% of time it's algorithmic sense = curPath; else sense = random() & 0x07; // first, find the directions in which this ghost can go if ([maze playerWall:(myX+2+GHOST_SIZE) :myY] || ((px < 0) && noReverse)) tdir &= ~0x01; if ([maze playerWall:(myX-2) :myY] || ((px > 0) && noReverse)) tdir &= ~0x02; if ([maze playerWall:myX :(myY-2)] || ((py > 0) && noReverse)) tdir &= ~0x04; if ([maze playerWall:myX :(myY+2+GHOST_SIZE)] || ((py < 0) && noReverse)) tdir &= ~0x08; // now choose the new direction for the ghost 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; } 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 * scale, col * GHOST_SIZE * scale, GHOST_SIZE * scale, GHOST_SIZE * scale); pos.x = myX * scale + posx; pos.y = myY * scale + posy; [ghosts[scale] composite:NX_SOVER fromRect:&from toPoint:&pos]; [self powerCount]; // count down time till power dot wears off return self; } - powerCount { 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.