/*
 * piece.c
 *
 * Rules for all pieces.
 */
/*

    3Dc, a game of 3-Dimensional Chess
    Copyright (C) 1995  Paul Hicks

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    E-Mail: paulh@euristix.ie
*/

#include <malloc.h>
#include "machine.h"
#include "3Dc.h"

#define PARAMS (Piece *, File, Rank, Level)

Local INLINE Boolean
  KingMayMove     PARAMS,
  QueenMayMove    PARAMS,
  BishopMayMove   PARAMS,
  KnightMayMove   PARAMS,
  RookMayMove     PARAMS,
  PrinceMayMove   PARAMS,
  PrincessMayMove PARAMS,
  AbbeyMayMove    PARAMS,
  CannonMayMove   PARAMS,
  GalleyMayMove   PARAMS,
  PawnMayMove     PARAMS;

#undef PARAMS

/* This function interprets the result of TraverseDir(piece...) */
Global Boolean
IsMoveLegal(const Piece *piece, const Piece *dest)
{
  if (dest == SQUARE_EMPTY)
    return TRUE;
  if (dest == SQUARE_INVALID)
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }
  else if ( piece->bwSide == dest->bwSide )
    {
      n3DcErr = E3DcBLOCK;
      return FALSE;
    }

  return TRUE;
}

Global Piece *
PieceNew(const Title nType,
         const File x, const Rank y, const Level z,
         const Colour col)
{
  Piece *piece;

  piece = (Piece *)malloc(sizeof(Piece));

  if (!piece)
    return NULL;

  piece->xyzPos.xFile = x;
  piece->xyzPos.yRank = y;
  piece->xyzPos.zLevel = z;

  piece->bwSide = col;
  piece->nName = nType;
  piece->bVisible = TRUE;
  piece->bHasMoved = FALSE;

  return piece;
}

Global void
PieceDelete(Piece *piece)
{
  if (Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] ==
      piece)
    Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] =
      NULL;

  /* We don't need to remove the piece from the muster, as pieceDelete
   * is only called when restarting, at which time init3Dc is also called,
   * thereby overwriting Muster's reference to the piece.  The only reason
   * for removing the piece from Board above is so that the board may be
   * redrawn cleanly.
   */

  free(piece);
  piece = NULL;
}

Global Boolean
PieceMayMove(Piece *piece,
             const File xNew, const Rank yNew, const Level zNew)
{
  Boolean retval;

  if (!piece || !piece->bVisible)
    {
      n3DcErr = E3DcINVIS;
      return FALSE;
    }

  /* Do bits which are the same for all pieces first */
  if (xNew == piece->xyzPos.xFile &&
      yNew == piece->xyzPos.yRank &&
      zNew == piece->xyzPos.zLevel)
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  if ((Board[zNew][yNew][xNew] != NULL) &&
      (Board[zNew][yNew][xNew]->bVisible == TRUE) &&
      (Board[zNew][yNew][xNew]->bwSide == piece->bwSide))
    {
      n3DcErr = E3DcBLOCK;
      return FALSE;  /* Can't take a piece on your team */
    }

  switch (piece->nName)
    {
    case king:
      retval = KingMayMove(piece, xNew, yNew, zNew);
      break;
    case queen:
      retval = QueenMayMove(piece, xNew, yNew, zNew);
      break;
    case bishop:
      retval = BishopMayMove(piece, xNew, yNew, zNew);
      break;
    case knight:
      retval = KnightMayMove(piece, xNew, yNew, zNew);
      break;
    case rook:
      retval = RookMayMove(piece, xNew, yNew, zNew);
      break;
    case prince:
      retval = PrinceMayMove(piece, xNew, yNew, zNew);
      break;
    case princess:
      retval = PrincessMayMove(piece, xNew, yNew, zNew);
      break;
    case abbey:
      retval = AbbeyMayMove(piece, xNew, yNew, zNew);
      break;
    case cannon:
      retval = CannonMayMove(piece, xNew, yNew, zNew);
      break;
    case galley:
      retval = GalleyMayMove(piece, xNew, yNew, zNew);
      break;
    case pawn:
      retval = PawnMayMove(piece, xNew, yNew, zNew);
      break;
    default:
      retval = FALSE;
      n3DcErr = E3DcSIMPLE;
    }

  if ( retval != FALSE )
    {
      if ( FakeMoveAndIsKingChecked(piece, xNew, yNew, zNew) == TRUE )
        {
          n3DcErr = E3DcCHECK;
          return FALSE;
        }
    }

  return retval;
}

/*
 * Execute the move
 */
Global Boolean
PieceMove(Piece *piece,
          const File xNew, const Rank yNew, const Level zNew)
{
  Move thisMove;
  Boolean moveType; /* Not quite Boolean... */

  if (!(moveType = PieceMayMove(piece, xNew, yNew, zNew)))
    return FALSE;

  /*
   * Keep record of move
   */
  thisMove.xyzBefore.xFile = piece->xyzPos.xFile;
  thisMove.xyzBefore.yRank = piece->xyzPos.yRank;
  thisMove.xyzBefore.zLevel = piece->xyzPos.zLevel;
  thisMove.xyzAfter.xFile = xNew;
  thisMove.xyzAfter.yRank = yNew;
  thisMove.xyzAfter.zLevel = zNew;

  if (moveType == EnPASSANT)
    {
      thisMove.nHadMoved = EnPASSANT;
      thisMove.pVictim = Board[zNew][yNew + (piece->bwSide == WHITE ?
                                             -1 : 1)][xNew];
    }
  else if (moveType == CASTLE)
    {
      thisMove.nHadMoved = CASTLE;
      thisMove.pVictim = Board[1][yNew][xNew < ((FILES-1)/2) ? 0 : FILES-1];
    }
  else if (moveType == PROMOTE)
    {
      thisMove.nHadMoved = PROMOTE;
      thisMove.pVictim = Board[zNew][yNew][xNew];
    }
  else
    {
      thisMove.nHadMoved = piece->bHasMoved;
      thisMove.pVictim = Board[zNew][yNew][xNew];
    }

  StackPush(MoveStack, &thisMove);

  piece->bHasMoved = TRUE;
  PieceDisplay(piece, FALSE);
  Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] = NULL;

  if (Board[zNew][yNew][xNew]) /* Kill victim */
    {
      PieceDisplay(Board[zNew][yNew][xNew], FALSE);
      Board[zNew][yNew][xNew]->bVisible = FALSE;
      Board[zNew][yNew][xNew] = NULL;
    }

  Board[zNew][yNew][xNew] = piece;
  piece->xyzPos.xFile = xNew;
  piece->xyzPos.yRank = yNew;
  piece->xyzPos.zLevel = zNew;
  PieceDisplay(piece, TRUE);

  /* Now move any special pieces */
  if (moveType == CASTLE)
    {
      int xRookSrc, xRookDest;

      /* If xNew on right of board then move to left
       * else move to right */
      if (xNew > (FILES/2))
        {
          xRookSrc = FILES -1;
          xRookDest = xNew -1;
        }
      else
        {
          xRookSrc = 0;
          xRookDest = xNew +1;
        }

      PieceDisplay(Board[1][yNew][xRookSrc], FALSE);

      Board[1][yNew][xRookDest] = Board[1][yNew][xRookSrc];
      Board[1][yNew][xRookSrc] = NULL;

      (Board[1][yNew][xRookDest])->xyzPos.xFile = xRookDest;
      (Board[1][yNew][xRookDest])->bHasMoved = TRUE;

      PieceDisplay(Board[1][yNew][xRookDest], TRUE);
    }
  else if (moveType == EnPASSANT)
    {
      int yPawnSrc;

      /* If yNew is forward of half-way then victim is back one
       * else it is forward one */
      yPawnSrc = (yNew > (RANKS/2) ? yNew-1 : yNew+1);
      PieceDisplay(Board[zNew][yPawnSrc][xNew], FALSE);
      Board[zNew][yPawnSrc][xNew]->bVisible = FALSE;
      Board[zNew][yPawnSrc][xNew] = NULL;
    }

#if 0
  /* I think that this code is obsolete.. */
  /* Check that the king isn't in check */
  if (IsKingChecked( piece->bwSide ))
    {
      /* Oops, this move puts the king in check;
       * it's illegal, so undo it */
      PieceUndo();
      n3DcErr = E3DcCHECK;
      return FALSE;
    }
#endif

  /* If this bit is up with EnPASSANT and CASTLE, then the
   * promotion dialog pops up even though the promotion is
   * illegal.  A promotion doesn't affect whether or not
   * the opponent checks your king (even if you promote to
   * cannon or something, you're still in the same place as
   * the dissappearing pawn..) so it works out better all
   * around if we just do it here. */
  if (moveType == PROMOTE)
    {
      PieceDisplay(piece, FALSE);
      PiecePromote(piece); /* This function asks for promotion type, etc. */
    }

  return TRUE;
}

/*
 * Undo the move
 */
Global Boolean
PieceUndo(void)
{
  Move *move;
  Colour bwMoved, bwTaken;
  Coord src, dest;

  move = StackPop(MoveStack);
  if (move == NULL)
    return FALSE;

  src = move->xyzAfter;
  dest = move->xyzBefore;

  bwMoved = Board[src.zLevel][src.yRank][src.xFile]->bwSide;
  bwTaken = (bwMoved == WHITE ? BLACK : WHITE);

  /* Clear the "moved-to" square */
  PieceDisplay(Board[src.zLevel][src.yRank][src.xFile], FALSE);

  /* Move the "moved" piece back */
  Board[dest.zLevel][dest.yRank][dest.xFile] =
    Board[src.zLevel][src.yRank][src.xFile];
  (Board[dest.zLevel][dest.yRank][dest.xFile])->xyzPos = dest;

  Board[src.zLevel][src.yRank][src.xFile] = NULL;

  switch (move->nHadMoved)
    {
    case PROMOTE:
      /* This piece was promoted from a pawn: demote it */
      Board[dest.zLevel][dest.yRank][dest.xFile]->nName = pawn;
      (Board[dest.zLevel][dest.yRank][dest.xFile])->bHasMoved = TRUE;
      break;

    case CASTLE:
      {
        int xRookSrc, xRookDest; /* xRookDest is beside edge */

        /* The move undone was a castle */
        /* The king is back in the right place; now
         * fix the rook */
        if (src.xFile < dest.xFile)
          {
          /* Castled to a smaller-id square (Queen's side for white,
           * King's side for black) */
            xRookSrc = dest.xFile -1;
            xRookDest = 0;
          }
        else
          {
          /* Castled to a larger-id square (Queen's side for black,
           * King's side for white) */
            xRookSrc = dest.xFile +1;
            xRookDest = FILES -1;
          }
            
        PieceDisplay(Board[1][dest.yRank][xRookSrc], FALSE);
        Board[1][dest.yRank][xRookDest] = Board[1][dest.yRank][xRookSrc];
        Board[1][dest.yRank][xRookSrc] = NULL;
        (Board[1][dest.yRank][xRookDest])->xyzPos.xFile = xRookDest;
        (Board[1][dest.yRank][xRookDest])->xyzPos.yRank = dest.yRank;
        (Board[1][dest.yRank][xRookDest])->xyzPos.zLevel = 1;
        PieceDisplay(Board[1][dest.yRank][xRookDest], TRUE);

        /* And finally---reset the bHasMoved flags */
        (Board[1][dest.yRank][dest.xFile])->bHasMoved = FALSE;
        (Board[1][dest.yRank][xRookDest])->bHasMoved = FALSE;
        }
      break;

    case EnPASSANT:
      FallThrough();

    default:
      (Board[dest.zLevel][dest.yRank][dest.xFile])->bHasMoved =
        move->nHadMoved;
    }

  /* Draw the piece in its original space */
  PieceDisplay(Board[dest.zLevel][dest.yRank][dest.xFile], TRUE);

  /* Put any taken piece back */
  if (move->pVictim)
    {
      Coord srcPos;

      /* Don't use src as the victim's square, as it could have
       * been en passant */
      srcPos = move->pVictim->xyzPos;

      Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile] = move->pVictim;
      Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile]->bVisible = TRUE;

      PieceDisplay(Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile], TRUE);
    }

  free(move);

  return TRUE;
}

/*
 * Here down are the specific piece-movement functions
 *
 * These all assume that piece is of the correct type.
 * No check is made and things get very odd if this assumption
 * is contradicted, so be careful.
 */

Local INLINE Boolean
KingMayMove(Piece *piece,
            const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff, xCur, xInc;
  Rank yDiff;
  Level zDiff;

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;
  zDiff = zNew - piece->xyzPos.zLevel;

  xDiff = ABS(xDiff);
  yDiff = ABS(yDiff);
  zDiff = ABS(zDiff);

  /* Not allowed move more than 1 except when castling */
  if ( (piece->bHasMoved && (xDiff > 2)) ||
       (yDiff > 1) || (zDiff > 1) )
    {
      n3DcErr = E3DcDIST;
      return FALSE;
    }

  /*
   * At this stage, we have determined that, given an empty board,
   * the move is legal.  Now take other pieces into account.
   */
  if (FakeMoveAndIsKingChecked( piece, xNew, yNew, zNew) ||
      ( (xDiff == 2) &&
       FakeMoveAndIsKingChecked( piece, (xNew + piece->xyzPos.xFile)/2,
                                yNew, zNew ) ))
    {
      n3DcErr = E3DcCHECK;
      return FALSE;
    }

  if (xDiff == 2)
    { /* Castling */
      File xRook;

      if (yDiff || zDiff)
        {
          n3DcErr = E3DcSIMPLE;
          return FALSE;
        }

      /*
       * Determine x-pos of castling rook
       */
      if (xNew > piece->xyzPos.xFile)
        xRook = FILES-1;
      else
        xRook = 0;

      if (piece->bHasMoved ||
               Board[1][yNew][xRook]->bHasMoved)
        {
          n3DcErr = E3DcMOVED;
          return FALSE;
        }
      else if (!Board[1][yNew][xRook])
        {
          n3DcErr = E3DcSIMPLE;
          return FALSE;
        }

      xInc = ( xRook == 0 ) ? -1 : 1 ;

      for (xCur = piece->xyzPos.xFile + xInc; xCur != xRook; xCur += xInc)
        {  /* Is the castle blocked? */
          if (Board[1][yNew][xCur])
            {
              n3DcErr = E3DcBLOCK;
              return FALSE;
            }
        }

      return CASTLE;
    }

  return TRUE;
}

Local INLINE Boolean
QueenMayMove(Piece *piece,
             const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;
  Level zDiff;
  Piece
    *pDestSquare;

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;
  zDiff = zNew - piece->xyzPos.zLevel;

  if ((xDiff && yDiff && (ABS(xDiff) != ABS(yDiff))) ||
      (xDiff && zDiff && (ABS(xDiff) != ABS(zDiff))) ||
      (yDiff && zDiff && (ABS(yDiff) != ABS(zDiff))))
    {
      n3DcErr = E3DcSIMPLE;
      return False;
    }

  /*
   * At this stage, we have determined that, given an empty board,
   * the move is legal.  Now take other pieces into account.
   */
  pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff,
                            MAX(ABS(xDiff), MAX(ABS(yDiff), ABS(zDiff))));
  return IsMoveLegal(piece, pDestSquare);
}

Local INLINE Boolean
BishopMayMove(Piece *piece,
              const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;
  Level zDiff;
  Piece *pDestSquare;

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;
  zDiff = zNew - piece->xyzPos.zLevel;

  if (!DIAG3D(xDiff, yDiff, zDiff))
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  /*
   * At this stage, we have determined that, given an empty board,
   * the move is legal.  Now take other pieces into account.
   */
  pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff,
                            MAX(ABS(xDiff), ABS(yDiff)));
  return IsMoveLegal(piece, pDestSquare);
}

Local INLINE Boolean
KnightMayMove(Piece *piece,
              const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;

  if (zNew != piece->xyzPos.zLevel)
    {
      n3DcErr = E3DcLEVEL;
      return FALSE; /* Knights may not change level */
    }

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;

  xDiff = ABS(xDiff);
  yDiff = ABS(yDiff);

  if ((xDiff == 0) ||
      (yDiff == 0) ||
      ((xDiff + yDiff) != 3))
    return FALSE;

  return TRUE;
}

Local INLINE Boolean
RookMayMove(Piece *piece,
            const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;
  Level zDiff;
  Piece *pDestSquare;

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;
  zDiff = zNew - piece->xyzPos.zLevel;

  if (!HORZ3D(xDiff, yDiff, zDiff))
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  /*
   * At this stage, we have determined that, given an empty board,
   * the move is legal.  Now take other pieces into account.
   */
  pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff,
                            MAX(ABS(xDiff), MAX(ABS(yDiff), ABS(zDiff))));
  return IsMoveLegal(piece, pDestSquare);
}

Local INLINE Boolean
PrinceMayMove(Piece *piece,
              const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;

  if (zNew != piece->xyzPos.zLevel)
    {
      n3DcErr = E3DcLEVEL;
      return FALSE; /* Princes may not change level */
    }

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;

  xDiff = ABS(xDiff);
  yDiff = ABS(yDiff);

  if (xDiff > 1 || yDiff > 1) /* Not allowed move more than 1 */
    {
      n3DcErr = E3DcDIST;
      return FALSE;
    }

  return TRUE;
}

Local INLINE Boolean
PrincessMayMove(Piece *piece,
                const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;
  Piece * pDestSquare;

  if (zNew != piece->xyzPos.zLevel)
    {
      n3DcErr = E3DcLEVEL;
      return FALSE; /* Princesses may not change level */
    }

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;

  if (xDiff && yDiff && (ABS(xDiff) != ABS(yDiff)))
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  /*
   * At this stage, we have determined that, given an empty board,
   * the move is legal.  Now take other pieces into account.
   */
  pDestSquare = TraverseDir(piece, xDiff, yDiff, 0,
                            MAX(ABS(xDiff), ABS(yDiff)));
  return IsMoveLegal(piece, pDestSquare);
}

Local INLINE Boolean
AbbeyMayMove(Piece *piece,
             const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;
  Piece *pDestSquare;

  if (zNew != piece->xyzPos.zLevel)
    {
      n3DcErr = E3DcLEVEL;
      return FALSE; /* Abbies may not change level */
    }

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;

  if (!DIAG(xDiff, yDiff))
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  /*
   * At this stage, we have determined that, given an empty board,
   * the move is legal.  Now take other pieces into account.
   */
  pDestSquare = TraverseDir(piece, xDiff, yDiff, 0,
                            MAX(ABS(xDiff), ABS(yDiff)));
  return IsMoveLegal(piece, pDestSquare);
}

Local INLINE Boolean
CannonMayMove(Piece *piece,
              const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;
  Level zDiff;

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;
  zDiff = zNew - piece->xyzPos.zLevel;

  xDiff = ABS(xDiff);
  yDiff = ABS(yDiff);
  zDiff = ABS(zDiff);

  if (((xDiff + yDiff + zDiff) != 6) ||
      ((xDiff != 3) && (yDiff != 3)) ||
      ((xDiff != 2) && (yDiff != 2) && (zDiff != 2)))
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  return TRUE;
}

Local INLINE Boolean
GalleyMayMove(Piece *piece,
              const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff;
  Piece *pDestSquare;

  if (zNew != piece->xyzPos.zLevel)
    {
      n3DcErr = E3DcLEVEL;
      return FALSE; /* Gallies may not change level */
    }

  xDiff = xNew - piece->xyzPos.xFile;
  yDiff = yNew - piece->xyzPos.yRank;

  if (!HORZ(xDiff, yDiff))
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  /*
   * At this stage, we have determined that, given an empty board,
   * the move is legal.  Now take other pieces into account.
   */
  pDestSquare = TraverseDir(piece, xDiff, yDiff, 0,
                            MAX(ABS(xDiff), ABS(yDiff)));
  return IsMoveLegal(piece, pDestSquare);
}

Local INLINE Boolean
PawnMayMove(Piece *piece,
            const File xNew, const Rank yNew, const Level zNew)
{
  File xDiff;
  Rank yDiff, yInc;

  if (zNew != piece->xyzPos.zLevel)
    {
      n3DcErr = E3DcLEVEL;
      return FALSE; /* Pawns may not change level */
    }

  xDiff = xNew - piece->xyzPos.xFile;
  yInc = yDiff = yNew - piece->xyzPos.yRank;

  xDiff = ABS(xDiff);
  yDiff = ABS(yDiff);

  /*
   * Pawns must move at least 1 forward
   */
  if ((yDiff == 0) ||
      ((yInc < 0) && (piece->bwSide == WHITE)) ||
      ((yInc > 0) && (piece->bwSide == BLACK))) /* Moving backwards */
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  /* Check the definitely-illegal moves first.. */
  if (xDiff > 1 ||
      (xDiff == 1 && yDiff != 1))
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  /*
   * It is difficult to cater for 'en passant' in the middle of a
   * conditional.  So, against all convention laid out in other
   * rules functions, I am checking a move and returning true if it
   * is valid, rather than returning FALSE if it is invalid.
   */
#if 0
   /*
    * TODO:
    *  Only allow en passant taking of pawns that moved two spaces
    * forward in one go (in the previous move only?)
    *  Each piece must have an identifier; either its memory location
    *  or its offset into the Muster.  That way this can be used as the
    *  4th line of this conditional.
    */
  (  StackPeek(MoveStack, 1)->nId == Board[zNew][yNew - yInc][xNew]->nId &&
    !(StackPeek(MoveStack, 1)->nHadMoved) &&
      Board[zNew][yNew - yInc][xNew]->bHasMoved /* Moved only once */
   )
#endif /* 0 */
    if (xDiff == 1 && yDiff == 1 && !Board[zNew][yNew][xNew])
      { /* En passant? */
        if (Board[zNew][yNew - yInc][xNew] && /* 'Takable' piece */
            Board[zNew][yNew - yInc][xNew]->nName == pawn && /* Is pawn */
            Board[zNew][yNew - yInc][xNew]->bwSide != piece->bwSide && /* Is enemy */
            1) /* Dummy line to reduce no. of changes */
          {
            return EnPASSANT;
          }
        else
          {
            n3DcErr = E3DcSIMPLE;
            return FALSE;
          }
      }

  /*
   * Pawns can not move forward under these conditions:
   *  They move more than 2
   *  They move more than 1 and they have already moved
   *  They attempt to take any piece (catered for in next conditional)
   */
  if (yDiff > 2 || /* Move too far */
      (piece->bHasMoved && yDiff == 2)) /* Move too far */
    {
      n3DcErr = E3DcDIST;
      return FALSE;
    }

  /*
   * Pawns may not take anything under these conditions:
   *  They do not move diagonally forward one space
   *  The victim is an ally
   */
  if (Board[zNew][yNew][xNew]  && /* Taking something */
      (!(xDiff == 1 && yDiff == 1) || /* Not moving diagonally */
       Board[zNew][yNew][xNew]->bwSide == piece->bwSide))
    {
      n3DcErr = E3DcSIMPLE;
      return FALSE;
    }

  /* Check for possible promotion */
  if ((yNew == FILES-1 && piece->bwSide == WHITE) ||
      (yNew == 0 && piece->bwSide == BLACK))
    return PROMOTE;

  return TRUE;
}
