#import "GameState.h"
#import <stdlib.h>
#import <stdio.h>
#import <string.h>

//| the underlying board.  Says whether a square is OFFBOARD, PLAIN,
//| CORNER, or CENTER.  Should never change.
const unsigned char board[256] = {
0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0,
0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0,

0, 0,  3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0, 0, 0,
0, 0,  3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,  0, 0, 0,

0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0,
0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0,
0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0
};
BOOL pawn_legal[256];
BOOL loki_legal[256];

//| These are used to short-circuit the slow, slow, slooow
//| regular objc_msgsend() that takes place for every standard message.
//| They are set in [GameState initialize]
//| This direct-call method instead of the usual indirect-messaging
//| does _not_ affect what you should do in any way.  You can still
//| call everything in the good ol' way you call any Objective-C method.
IMPMakeMove makeMove, makeWhiteMove, makeBlackMove;
IMPUndoMove undoMove, undoWhiteMove, undoBlackMove;
IMPAnyLegalMoves anyLegalMoves;


@implementation GameState

+ initialize
{
   int i;

   makeMove = (IMPMakeMove)[self instanceMethodFor:@selector(makeMove:)];
   makeWhiteMove = (IMPMakeMove)[self instanceMethodFor:@selector(makeWhiteMove:)];
   makeBlackMove = (IMPMakeMove)[self instanceMethodFor:@selector(makeBlackMove:)];
   undoMove = (IMPUndoMove)[self instanceMethodFor:@selector(undoMove)];
   undoWhiteMove = (IMPUndoMove)[self instanceMethodFor:@selector(undoWhiteMove)];
   undoBlackMove = (IMPUndoMove)[self instanceMethodFor:@selector(undoBlackMove)];
   anyLegalMoves = (IMPAnyLegalMoves)[self instanceMethodFor:@selector(anyLegalMoves)];
   for (i=0; i<256; i++) {
      switch (board[i]) {
         case OFFBOARD:
            pawn_legal[i] = NO;
            loki_legal[i] = NO;
         break;
         case PLAIN:
            pawn_legal[i] = YES;
            loki_legal[i] = YES;
         break;
         case CENTER: case CORNER:
            pawn_legal[i] = NO;
            loki_legal[i] = YES;
         break;
      }
   }

   return [super initialize];
}

- init
{
   [self resetState];
   return [super init];
}

- (void)resetState
{ int k;
  const unsigned char startingLocs[36] = { /* 24 white pawns */ XYTONUM(10,3), XYTONUM(6,0), XYTONUM(0,4), XYTONUM(9,5), XYTONUM(6,10), XYTONUM(5,1), XYTONUM(0,5), XYTONUM(10,6), XYTONUM(5,10), XYTONUM(0,3), XYTONUM(4,0), XYTONUM(5,9), XYTONUM(3,10), XYTONUM(7,0), XYTONUM(7,10), XYTONUM(4,10), XYTONUM(5,0), XYTONUM(1,5), XYTONUM(0,7), XYTONUM(10,4), XYTONUM(10,5), XYTONUM(0,6), XYTONUM(3,0), XYTONUM(10,7), /* 12 black pawns */ XYTONUM(6,6), XYTONUM(6,4), XYTONUM(4,4), XYTONUM(5,3), XYTONUM(5,4), XYTONUM(4,5), XYTONUM(7,5), XYTONUM(5,7), XYTONUM(3,5), XYTONUM(6,5), XYTONUM(4,6), XYTONUM(5,6) };
   for (k=0; k<256; k++)
      pieces[k] = (struct spot){NOBODY,0};
   for (k=0; k<24; k++)
   {
      pieceLocs[k] = startingLocs[k];
      pieces[startingLocs[k]] = (struct spot){W_PAWN,k};
   }
   for (k=24; k<36; k++)
   {
      pieceLocs[k] = startingLocs[k];
      pieces[startingLocs[k]] = (struct spot){B_PAWN,k};
   }
   pieceLocs[36] = XYTONUM(5,5);
   pieces[XYTONUM(5,5)] = (struct spot){LOKI,36};
   numPawns[WHITE] = 24; 
   numPawns[BLACK] = 12;
   whoseTurn = WHITE;
   numMoves = 0;
   numCaptures = 0;
}

- (void)makeMove:(struct move)request
{
   if (whoseTurn == WHITE)
      makeWhiteMove(self, @selector(makeWhiteMove:), request);
   else
      makeBlackMove(self, @selector(makeBlackMove:), request);
}

- (void)makeWhiteMove:(struct move)request
{
   BOOL lokiFlag = NO;

   pieces[request.to] = pieces[request.from];
   pieceLocs[pieces[request.to].idnum] = request.to;
   pieces[request.from].who = NOBODY;
   
#define WHITEPAWNCAP(DIR) \
if (BLACKPIECE(request.to+DIR) && (WHITEPIECE(request.to+(2*DIR)) || CAPTURE_HELPER(request.to+(2*DIR)))) { \
 if (pieceLocs[36] == request.to+DIR) \
  lokiFlag = YES; \
 else { \
  captures[numCaptures].when = numMoves; \
  captures[numCaptures].where = request.to+DIR; \
  captures[numCaptures].idnum = pieces[request.to+DIR].idnum; \
  numCaptures++; \
  pieceLocs[pieces[request.to+DIR].idnum] = 0; \
  pieces[request.to+DIR].who = NOBODY; \
  numPawns[BLACK]--; \
 } \
}

   WHITEPAWNCAP(NORTH);
   WHITEPAWNCAP(SOUTH);
   WHITEPAWNCAP(EAST);
   WHITEPAWNCAP(WEST);

   moves[numMoves] = request;
   numMoves++;
   whoseTurn = BLACK;
   if ((lokiFlag) && (((pieces[pieceLocs[36] + EAST].who == W_PAWN) || (pieceLocs[36] == XYTONUM(4,5)))  && ((pieces[pieceLocs[36] + WEST].who == W_PAWN) || (pieceLocs[36] == XYTONUM(6,5))) && ((pieces[pieceLocs[36] + NORTH].who == W_PAWN) || (pieceLocs[36] == XYTONUM(5,4))) && ((pieces[pieceLocs[36] + SOUTH].who == W_PAWN) || (pieceLocs[36] == XYTONUM(5,6)))))
      whoseTurn = WHITE_WON;
   else if ((!anyLegalMoves(self, @selector(anyLegalMoves))) || ((numCaptures > 0) ? (numMoves - captures[numCaptures-1].when > 50) : (numMoves >= 50)))
      whoseTurn = DRAW;
}

- (void)makeBlackMove:(struct move)request
{
   pieces[request.to] = pieces[request.from];
   pieceLocs[pieces[request.to].idnum] = request.to;
   pieces[request.from].who = NOBODY;

#define BLACKPAWNCAP(DIR) \
if (WHITEPIECE(request.to+DIR) && (BLACKPIECE(request.to+(2*DIR)) || CAPTURE_HELPER(request.to+(2*DIR)))) { \
  captures[numCaptures].when = numMoves; \
  captures[numCaptures].where = request.to+DIR; \
  captures[numCaptures].idnum = pieces[request.to+DIR].idnum; \
  numCaptures++; \
  pieceLocs[pieces[request.to+DIR].idnum] = 0; \
  pieces[request.to+DIR].who = NOBODY; \
  numPawns[WHITE]--; \
}

   BLACKPAWNCAP(NORTH);
   BLACKPAWNCAP(SOUTH);
   BLACKPAWNCAP(EAST);
   BLACKPAWNCAP(WEST);

   moves[numMoves] = request;
   numMoves++;
   whoseTurn = WHITE;
   if (board[request.to] == CORNER)
      whoseTurn = BLACK_WON;
   else if ((!anyLegalMoves(self, @selector(anyLegalMoves))) || ((numCaptures > 0) ? (numMoves - captures[numCaptures-1].when > 50) : (numMoves >= 50)))
      whoseTurn = DRAW;
}

- (void)undoMove
{
   if (numMoves % 2) /* if numMoves is odd, white just made a move */
      undoWhiteMove(self, @selector(undoWhiteMove));
   else /* otherwise black made the move */
      undoBlackMove(self, @selector(undoBlackMove));
}

- (void)undoWhiteMove
{
   whoseTurn = WHITE;
   numMoves--;
   /* undo captures */
   if (numCaptures) while (captures[numCaptures-1].when == numMoves)
   {
      numCaptures--;
      numPawns[BLACK]++;
      pieces[captures[numCaptures].where] = (struct spot){B_PAWN, captures[numCaptures].idnum};
      pieceLocs[captures[numCaptures].idnum] = captures[numCaptures].where;
   }
   /* unmove piece */
   pieces[moves[numMoves].from] = pieces[moves[numMoves].to];
   pieceLocs[pieces[moves[numMoves].from].idnum] = moves[numMoves].from;
   pieces[moves[numMoves].to].who = NOBODY;
}

- (void)undoBlackMove
{
   whoseTurn = BLACK;
   numMoves--;
   /* undo captures */
   if (numCaptures) while (captures[numCaptures-1].when == numMoves)
   {
      numCaptures--;
      numPawns[WHITE]++;
      pieces[captures[numCaptures].where] = (struct spot){W_PAWN, captures[numCaptures].idnum};
      pieceLocs[captures[numCaptures].idnum] = captures[numCaptures].where;
   }
   /* unmove piece */
   pieces[moves[numMoves].from] = pieces[moves[numMoves].to];
   pieceLocs[pieces[moves[numMoves].from].idnum] = moves[numMoves].from;
   pieces[moves[numMoves].to].who = NOBODY;
}

- (BOOL)anyLegalMoves
{
   int k;

   if (whoseTurn == WHITE)
   {
      for (k=0; k<24; k++) if (pieceLocs[k] != 0)
      {
         if (UNOCCUPIED(pieceLocs[k]+EAST) && (pawn_legal[pieceLocs[k]+EAST])) return YES;
         if (UNOCCUPIED(pieceLocs[k]+WEST) && (pawn_legal[pieceLocs[k]+WEST])) return YES;
         if (UNOCCUPIED(pieceLocs[k]+NORTH) && (pawn_legal[pieceLocs[k]+NORTH])) return YES;
         if (UNOCCUPIED(pieceLocs[k]+SOUTH) && (pawn_legal[pieceLocs[k]+SOUTH])) return YES;
      }
      return NO;
   }
   else if (whoseTurn == BLACK)
   {
      for (k=24; k<36; k++) if (pieceLocs[k] != 0)
      {
         if (UNOCCUPIED(pieceLocs[k]+EAST) && (pawn_legal[pieceLocs[k]+EAST])) return YES;
         if (UNOCCUPIED(pieceLocs[k]+WEST) && (pawn_legal[pieceLocs[k]+WEST])) return YES;
         if (UNOCCUPIED(pieceLocs[k]+NORTH) && (pawn_legal[pieceLocs[k]+NORTH])) return YES;
         if (UNOCCUPIED(pieceLocs[k]+SOUTH) && (pawn_legal[pieceLocs[k]+SOUTH])) return YES;
      }
      if (UNOCCUPIED(pieceLocs[36]+EAST) && (loki_legal[pieceLocs[k]+EAST])) return YES;
      if (UNOCCUPIED(pieceLocs[36]+WEST) && (loki_legal[pieceLocs[k]+WEST])) return YES;
      if (UNOCCUPIED(pieceLocs[36]+NORTH) && (loki_legal[pieceLocs[k]+NORTH])) return YES;
      if (UNOCCUPIED(pieceLocs[36]+SOUTH) && (loki_legal[pieceLocs[k]+SOUTH])) return YES;
      return NO;
   }
   else return NO;
}

- (BOOL)checkMove :(struct move)request
{
   int direction; unsigned char scanSpot;
   BOOL isLokiMove = NO;

   switch (pieces[request.from].who) {
      case NOBODY: return NO;
      case W_PAWN: case B_PAWN: break;
      case LOKI: isLokiMove = YES; break;
   }
   direction = request.to - request.from;
   if (((abs(direction) % 16 == 0) && (direction / 16 != 0)) ||
      ((abs(direction) % 16 != 0) && (direction / 16 == 0))) {
      direction = (direction % 16 == 0) ?
                     16 * direction / abs(direction) :
                     (direction % 16) / abs(direction % 16);
      scanSpot=request.from;
      do {
         scanSpot+=direction;
         if (!UNOCCUPIED(scanSpot)) return NO;
         switch (board[scanSpot]) {
            case OFFBOARD: return NO;
            case CORNER: if (!isLokiMove) return NO;
            case CENTER: if (!isLokiMove) return NO;
            case PLAIN: break;
         }
      } while (scanSpot!=request.to);
      return YES;
   } else return NO;
}

- write:(NXTypedStream *)stream
{
   [super write:stream];
   NXWriteArray(stream, "c", 37, &pieceLocs);
   NXWriteArray(stream, "{cc}", 256, &pieces);
   NXWriteTypes(stream, "ccc", &whoseTurn, &numPawns[WHITE], &numPawns[BLACK]);
   NXWriteArray(stream, "{cc}", 1024, &moves);
   NXWriteType(stream, "s", &numMoves);
   NXWriteArray(stream, "{scc}", 36, &captures);
   NXWriteType(stream, "s", &numCaptures);
   return self;
}

- read:(NXTypedStream *)stream
{
   [super read:stream];
   NXReadArray(stream, "c", 37, &pieceLocs);
   NXReadArray(stream, "{cc}", 256, &pieces);
   NXReadTypes(stream, "ccc", &whoseTurn, &numPawns[WHITE], &numPawns[BLACK]);
   NXReadArray(stream, "{cc}", 1024, &moves);
   NXReadType(stream, "s", &numMoves);
   NXReadArray(stream, "{scc}", 36, &captures);
   NXReadType(stream, "s", &numCaptures);
   return self;
}

@end
