/* The cards are specified by integers 0 to 51.  -1 specifies no card
 * in that location.  The aces are integers 0 to 3, twos are from 4 to
 * 7, etc.  If x is a card, then x % 4 == 0, 1, 2, or 3 respectively
 * indicates a spade, heart, club, or diamond.  Thus 1 + x/4 computes
 * the rank of a card, and x % 4, its suit, and x % 2, its color.
 */

#define NULLCARD(x) ((x) == -1)
#define RANK(x) (1 + (x)/4)
#define SUIT(x) ((x)%4)
#define COLOR(x) ((x)%2)


/* Test if card can be moved to its home cell */

int moveable_to_home_cell(int card, State S) {
  int hcard;
  if (NULLCARD(card)) return 0;
  hcard = S->hcell[SUIT(card)];
  return ((NULLCARD(hcard)) ? 1 == RANK(card) : 1 + RANK(hcard) == RANK(card));
}


/* Test if fromcard can be moved on tocard in a column */

int moveable_to_column(int fromcard, int tocard) {
  int result;
  if (NULLCARD(fromcard)) return 0;
  if (NULLCARD(tocard)) return 1;
  result = ((1+RANK(fromcard) == RANK(tocard)) &&
          (COLOR(fromcard) != COLOR(tocard)));

  return result;
}


#define HOMECELL 0
#define FREECELL 1
#define COLUMN 2


/* Perform a move assuming it is legal */

State do_move(State S, int loc1, int i1, int loc2, int i2) {
  int card, j;
  State new;
  new = freecell_copy(S);

  if (loc1 == COLUMN) {
    for (j = 0; j < 20 && ! NULLCARD(S->column[i1][j]); j++);
    card = S->column[i1][j-1];
    new->column[i1][j-1] = -1;
  } else {
    card = S->fcell[i1];
    new->fcell[i1] = -1;
  }

  if (loc2 == COLUMN) {
    for (j = 0; j < 20 && ! NULLCARD(S->column[i2][j]); j++);
    new->column[i2][j] = card;
  } else if (loc2 == FREECELL)
    new->fcell[i2] = card;
  else new->hcell[SUIT(card)] = card;

  /* recover_move(S, new); */

  return new;
}


/* Return the states that can be reached from S in one move */

State *freecell_expand(State S) {
  int i, i1, j, pos, emptymove;
  int endcolumn[8];
  State *successors;
  State new;

  /* There can't be more than 39 successors.
   * (2*8) At most 2 cards can be moved to a column with cards
   * 11    At most 11 cards can be moved to an empty column
   * 8     At most 8 cards can be moved to an empty free cell
   * 4     At most 4 cards can be moved to a home cell
   * 39    Total
   */
  successors = (State *) calloc(40, sizeof(State));
  pos = 0;

  /* Find end of columns */
  for (i = 0; i < 8; i++) {
    for (j = 0; j < 20 && ! NULLCARD(S->column[i][j]); j++);
    if (j == 0)
      endcolumn[i] = -1;
    else
      endcolumn[i] = S->column[i][j-1];
  }

  /* Moves from columns to home cells */
  for (i = 0; i < 8; i++)
    if (moveable_to_home_cell(endcolumn[i], S))
      successors[pos++] = do_move(S, COLUMN, i, HOMECELL, 0);

  /* Moves from free cells to home cells */
  for (i = 0; i < 4; i++)
    if (moveable_to_home_cell(S->fcell[i], S))
      successors[pos++] = do_move(S, FREECELL, i, HOMECELL, SUIT(S->fcell[i]));

  /* Moves from columns to free cells */
  for (i = 0; i < 4 && ! NULLCARD(S->fcell[i]); i++);
  if (i < 4) {
    for (i1 = 0; i1 < 8; i1++)
      if (! NULLCARD(endcolumn[i1]))
         successors[pos++] = do_move(S, COLUMN, i1, FREECELL, i);
  }

  /* Moves from one column to another */
  for (i = 0; i < 8; i++)
    if (! NULLCARD(endcolumn[i])) {
      emptymove = 0;
      for (i1 = 0; i1 < 8; i1++) {
        if (moveable_to_column(endcolumn[i], endcolumn[i1])) {
          if (NULLCARD(endcolumn[i1])) {
            if (! emptymove)
               successors[pos++] = do_move(S, COLUMN, i, COLUMN, i1);
            else emptymove = 1;
          } else
             successors[pos++] = do_move(S, COLUMN, i, COLUMN, i1);
        }
      }
    }

  /* Moves from freecells to a column */
  for (i = 0; i < 4; i++)
    if (! NULLCARD(S->fcell[i])) {
      emptymove = 0;
      for (i1 = 0; i1 < 8; i1++) {
        if (NULLCARD(endcolumn[i1])) {
          if (! emptymove)
             successors[pos++] = do_move(S, FREECELL, i, COLUMN, i1);
          else emptymove = 1;
        } else if (moveable_to_column(S->fcell[i], endcolumn[i1]))
           successors[pos++] = do_move(S, FREECELL, i, COLUMN, i1);
      }
    }

  successors[pos] = NULL;

  /* fprintf(stderr, "%d states generated by expand\n", pos); */


  return successors;
}
