
/* Generated by Interface Builder */

#import "Othello.h"

@implementation Othello

- saveGame:sender
{
  NXStream *theStream;
  
  if (!savePanel) savePanel = [SavePanel new];
  
  if ([savePanel runModal]) {
    theStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
    NXWrite(theStream, theField, NUM_SPACES * sizeof(short));
    NXWrite(theStream, &theTurn, sizeof(short));
    NXFlush(theStream);
    NXSaveToFile(theStream, [savePanel filename]);
    NXClose(theStream);
  }
  
  return self;
}

- openGame:sender
{
  NXStream *theStream;
  static const char *const othType[2] = {"oth", NULL};
  id openPanel = [[OpenPanel new] allowMultipleFiles:NO];
  
  if ([openPanel runModalForTypes:othType]) {
    theStream = NXMapFile([openPanel filename], NX_READONLY);
    NXRead(theStream, theField, NUM_SPACES * sizeof(short));
    NXRead(theStream, &theTurn, sizeof(short));
    NXClose(theStream);
    [self updateField:self];
  }
  return self;
}

int count_pieces(theField, thePlayer)
     short *theField, thePlayer;
{
  int i, count=0;
  
  for(i=0; i<NUM_SPACES; i++) {
    if(theField[i]==thePlayer) count++;
  }
  
  return count;
}

- newGame:sender
{
  bzero(&theField, NUM_SPACES * sizeof(short));
  
  /* this needs to be computed-- algorithm to find the middle for squares
   * and place the appropriate pieces in those squares.
   */
  theField[27]= WHITE;
  theField[28]= BLACK;
  theField[35]= BLACK;
  theField[36]= WHITE;
  
  [self updateField:self];
  theTurn = WHITE;
  theField[NUM_SPACES]=theTurn;
  [status setStringValue:"White's turn."];
  [white_number setIntValue:count_pieces(theField, WHITE)];
  [black_number setIntValue:count_pieces(theField, BLACK)];
  
  return self;
}


- updatePlace:(int) theCell forPlayer:(int) thePlayer
{
  switch(thePlayer) {
 case WHITE:
    [[playField findCellWithTag:theCell] setIcon:"White_piece"];
    break;
 case BLACK:
    [[playField findCellWithTag:theCell] setIcon:"Black_piece"];
    break;
 default:
    [[playField findCellWithTag:theCell] setIcon:""];
  }
  
  return self;
}

- updateField:sender
{
  int i;
  
  for(i=0; i<NUM_SPACES; i++) {
    switch(theField[i]) {
  case WHITE:
      [[playField findCellWithTag:i] setIcon:"White_piece"];
      break;
  case BLACK:
      [[playField findCellWithTag:i] setIcon:"Black_piece"];
      break;
  default:
      [[playField findCellWithTag:i] setIcon:""];
    }
  }
  
  return self;
}


int opp(player)
     int player;
{
  if (player == WHITE) {
    return BLACK;
  }
  return WHITE;
}

- (BOOL) checkDirection:(int) xDelta:(int) yDelta fromCell:(int)theCell
 forPlayer:(int) thePlayer
{
  int x = x(theCell)+xDelta;
  int y = y(theCell)+yDelta;
  int deltaCell = cell(x, y);
  
  if(x < 0 || x > FIELD_WIDTH-1 || y < 0 || y > FIELD_HEIGHT-1) {
    return NO;
  }
  
  while(theField[deltaCell] == opp(thePlayer)) {
    x = x(deltaCell)+xDelta;
    y = y(deltaCell)+yDelta;
    if(x < 0 || x > FIELD_WIDTH-1 || y < 0 || y > FIELD_HEIGHT-1) {
      return NO;
    }
    deltaCell=cell(x, y);
  }
  
  if (theField[deltaCell]==thePlayer) {
    return YES;
  }
  return NO;
}

- (void) doDirection:(int) xDelta:(int) yDelta fromCell:(int)theCell
 forPlayer:(int) thePlayer
{
  int deltaCell=cell((x(theCell)+xDelta), (y(theCell)+yDelta));
  
  while(theField[deltaCell] == opp(thePlayer)) {
    theField[deltaCell] = thePlayer;
    [self updatePlace:deltaCell forPlayer:thePlayer];
    
    deltaCell=cell((x(deltaCell)+xDelta), (y(deltaCell)+yDelta));
  }
}

- (BOOL) allDirectionsFromCell:(int)theCell flipPieces:(BOOL) move
 forPlayer:(int) thePlayer
{
  int xDelta, yDelta, x, y;
  BOOL validMove=NO;
  
  for (yDelta=-1; yDelta<2; yDelta++) {
    y = y(theCell) + yDelta;
    if (y < 0 || y > FIELD_HEIGHT-1) continue; /* off board */
    for (xDelta=-1; xDelta<2; xDelta++) {
      x = x(theCell) + xDelta;
      if (x < 0 || x > FIELD_WIDTH-1) continue; /* off board */
      if (theField[cell(x, y)] != opp(thePlayer)) continue; /* not flanked
							       by opponent */
      /* there is an ajacent opponent this direction-- check to see if there
       * is a flank and make the changes if there is.  the eight directions
       * that need to be changed are independent-- we can safely make the
       * board changes w/o having to worry about an affect on the other
       * directions.
       */
      if ([self checkDirection:xDelta:yDelta fromCell:theCell
       forPlayer:thePlayer]) {
	if (move) [self doDirection:xDelta:yDelta fromCell:theCell
	       forPlayer:thePlayer];
	validMove=YES;
      }
    }
  }
  
  return validMove;
}

- (BOOL) allDirectionsFromCell:(int)theCell flipPieces:(BOOL) move
{
  return
      [self allDirectionsFromCell:theCell flipPieces:move forPlayer:theTurn];
}


- (BOOL) canPlay:(int) thePlayer
{
  int i;
  
  /* if there is any move at all, immediately return YES. */
  for(i=0; i<NUM_SPACES; i++) {
    if(theField[i] == EMPTY)
	if([self allDirectionsFromCell:i flipPieces:NO
	forPlayer:thePlayer]) {
	  return YES;
	}
  }
  /* otherwise, return NO. */
  return NO;
}


- (char *) playerToString:(int) thePlayer
{
  char *s;
  s = malloc(6*sizeof(char));
  if (thePlayer == WHITE) {
    strcpy(s, "White");
  } else strcpy(s, "Black");
  return s;
}

- (char *) turnString
{
  char *s;
  
  s = malloc(15*sizeof(char));
  strcpy(s, [self playerToString:theTurn]);
  strcat(s, "'s turn.");
  return s;
}

- moveMade:sender
{
  char msg[25];
  int theCell;
  int white,black;
  
  if ((theCell = [sender selectedTag]) < 0)
      return self;/* invalid click trap */
  
  if (theField[theCell]) return self; /* bad move -- already occupied */
  
  /* try the move */
  if([self allDirectionsFromCell:theCell flipPieces:NO]) {
    [self backup];
    [self allDirectionsFromCell:theCell flipPieces:YES];
    theField[theCell] = theTurn; /* place the piece */
    [self updatePlace: theCell forPlayer:theTurn];
    
    /* check to see if the next player has a valid move available
     * this could be combined w/the above but would dog out the board
     * refresh and wouldn't be aesthetically pleasing
     */
    
    if([self canPlay:opp(theTurn)]) {
      theTurn=opp(theTurn);
      theField[NUM_SPACES]=theTurn;
      sprintf(msg, "%s's turn.", [self playerToString:theTurn]);
    } else {
      if(![self canPlay:theTurn]) {
	white=count_pieces(theField, WHITE);
	black=count_pieces(theField, BLACK);
	if (black > white) {
	  sprintf(msg, "Black wins by %d.", black - white);
	} else
	    if (white > black) {
	      sprintf(msg, "White wins by %d.", white - black);
	    } else sprintf(msg, "Tie game.");
      } else
	  sprintf(msg, "%s has no move.  %s's turn.",
		  [self playerToString:opp(theTurn)],
		  [self playerToString:theTurn]);
    }
  } else sprintf(msg, "Invalid move. %s's turn.",
		 [self playerToString:theTurn]);
  
  [white_number setIntValue:count_pieces(theField, WHITE)];
  [black_number setIntValue:count_pieces(theField, BLACK)];
  
  [status setStringValue:msg];
  
  return self;
}

- undo:sender
{
  [self swap];
  
  return self;
}

- swap
{
  short temp[NUM_SPACES+1];
  char msg[20];
  
  
  bcopy(theBckField, temp, (NUM_SPACES+1) * sizeof(short));
  bcopy(theField, theBckField, (NUM_SPACES+1) * sizeof(short));
  bcopy(temp, theField, (NUM_SPACES+1) * sizeof(short));
  
  [self updateField:self];
  
  theTurn=theField[NUM_SPACES];
  
  sprintf(msg, "%s", [self turnString]);
  [status setStringValue:msg];
  
  return self;
}

- backup
{
  bcopy(theField, theBckField, NUM_SPACES * sizeof(short));
  theBckField[NUM_SPACES] = theTurn;
  
  return self;
}

- appDidInit:sender
{
  [self newGame:self];
  return self;
}
@end
