/*
 * engine.c
 *
 * The rules engine for 3Dc.
 */
/*

    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 <stdio.h>
#include <stdlib.h>
#include <limits.h>

#include "machine.h"
#include "3Dc.h"

/*
 * Returns a pointer to any one piece of the specified colour threatening
 * the mentioned square.  Will return NULL if the square is not
 * threatened.
 */
Global Piece *
SquareThreatened(Colour bwSide,
                 const File xFile, const Rank yRank, const Level zLevel)
{
  int pieceIdx;

  for (pieceIdx = 0; pieceIdx < PIECES; ++pieceIdx)
    {
      if (Muster[bwSide][pieceIdx]->bVisible &&
          PieceMayMove( Muster[bwSide][pieceIdx],
                        xFile, yRank, zLevel ))
        return Muster[bwSide][pieceIdx];
    }

  return NULL;
}

/* Go dist in given direction.  Direction is positive, negative, 0,
 * with obvious meanings (think of the axes).
 * Return SQUARE_EMPTY, SQUARE_INVALID, or a pointer to the piece
 * first encountered (even if it is before the "destination"
 * location).
 *
 * SQUARE_EMPTY and SQUARE_INVALID are of type (Piece *); the only
 * legitimate value in them is xyzPos which is the coord of the
 * square in question (SQUARE_EMPTY) or the coord of the last
 * legitimate square on the route (SQAURE_INVALID).  If any or all
 * members of the xyzPos struct are equal to UINT_MAX then there was
 * error which utterly precludes moving (e.g. dist == 0).
 */
Global Piece *
TraverseDir(const Piece *piece, Dir xDir, Dir yDir, Dir zDir, unsigned dist)
{
  int x, y, z, d = 0;

  /* Most move at least one in a real direction */
  if ((dist == 0) ||
      ((xDir == 0) &&
       (yDir == 0) &&
       (zDir == 0)))
    {
      SQUARE_INVALID->xyzPos.xFile =
        SQUARE_INVALID->xyzPos.yRank =
          SQUARE_INVALID->xyzPos.zLevel = UINT_MAX;

      return SQUARE_INVALID;
    }

  if ((piece->nName != knight) &&
      (piece->nName != cannon))
    {
      /* Make all directions be 1, -1 or 0 */
      if (xDir != 0) xDir /= ABS(xDir);
      if (yDir != 0) yDir /= ABS(yDir);
      if (zDir != 0) zDir /= ABS(zDir);
    }
  else
    dist = 1;

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

  do
    {
      x += xDir;
      y += yDir;
      z += zDir;

      if (!((x >= 0)    && (y >= 0)    && (z >= 0) &&
          (x < FILES) && (y < RANKS) && (z < LEVELS)))
        {
          SQUARE_INVALID->xyzPos.xFile = x;
          SQUARE_INVALID->xyzPos.yRank = y;
          SQUARE_INVALID->xyzPos.zLevel = z;
          return SQUARE_INVALID;
        }

      if (Board[z][y][x])
        {
          if (Board[z][y][x]->bwSide == piece->bwSide)
            {
              SQUARE_INVALID->xyzPos.xFile = x;
              SQUARE_INVALID->xyzPos.yRank = y;
              SQUARE_INVALID->xyzPos.zLevel = z;
              return SQUARE_INVALID;
            }
          else
            return Board[z][y][x];
        }
    }
  while (++d < dist);

  /*
   * At this point, because we haven't returned, we know these things:
   *  We have not encountered another piece.
   *  We have moved dist spaces.
   */
  if ((x >= 0)     && (y >= 0)    && (z >= 0) &&
      (z < LEVELS) && (y < RANKS) && (x < FILES))
    {
      /* Valid (empty) square */
      SQUARE_EMPTY->xyzPos.xFile = x;
      SQUARE_EMPTY->xyzPos.yRank = y;
      SQUARE_EMPTY->xyzPos.zLevel = z;

      return SQUARE_EMPTY;
    }

  /*
   * We fell off the board. Go back one place to the last valid
   * location.
   */
  SQUARE_INVALID->xyzPos.xFile = x - xDir;
  SQUARE_INVALID->xyzPos.yRank = y - yDir;
  SQUARE_INVALID->xyzPos.zLevel = z - zDir;

  return SQUARE_INVALID;
}

/*
 * Return TRUE if the king is checked in the current board layout.
 */
Inline Global Boolean
IsKingChecked( Colour bwSide )
{
  Coord xyz;

  xyz = Muster[ bwSide ][ MusterIdx( king, 0 ) ]->xyzPos;

  return ( SquareThreatened( (bwSide == WHITE) ? BLACK : WHITE,
                             xyz.xFile, xyz.yRank, xyz.zLevel ) != NULL );
}

/* Check move re. putting own king in check */
Inline Global Boolean
FakeMoveAndIsKingChecked( Piece *piece,
                         const File x, const Rank y, const Level z)
{
  Piece *temp;
  Boolean retVal;
  Coord xyz;

  xyz = piece->xyzPos;
  temp = Board[z][y][x];
  if ( temp != NULL )
    temp->bVisible = FALSE;
  Board[z][y][x] = piece;
  Board[xyz.zLevel][xyz.yRank][xyz.xFile] = NULL;

  if (piece->nName == king)
    {
      /* We're moving the king, so it's xyzPos may not be accurate.
       * check manually. */
      retVal = (SquareThreatened( (piece->bwSide == WHITE) ? BLACK : WHITE,
                                x, y, z ) != NULL) ;
    }
  else
    retVal = IsKingChecked(piece->bwSide);

  Board[z][y][x] = temp;
  if ( temp != NULL )
    temp->bVisible = TRUE;
  Board[xyz.zLevel][xyz.yRank][xyz.xFile] = piece;

  return retVal;
}
