#define __MSDOS__
#if 1
/*
* Program for testing winnability of opening positions in Free Cell.
* Can also generate random positions, optionally filtered for winnability.
* Does not actually support interactive playing of the game. See below for
* a brief explanation of the rules.
*
* (c) Copyright 1994, Donald R. Woods. Shareware: Right is granted to
* freely redistribute this source provided this notice is retained.
*/
/*****************************************************************************
Brief summary of the game:
Deal four columns of six cards each, and four of seven cards each, spreading
each column so the cards are all visible. These columns form the "tableau".
There is a "holding area" that can hold a maximum of four cards. The only
cards that can be moved are the bottommost card in each column and the cards
in the holding area. As Aces become available (i.e., can be moved) they are
moved to start four "foundation" piles, which are then built up in suit to
Kings. The object is to move all the cards to the foundations.
A move consists of moving one card. A card can move onto a tableau column
if the card being moved is the opposite color and one rank lower than the
card it is being played onto (i.e., the tableau builds down, alternating
color). Any card can be moved to an empty holding area spot, or to an empty
column in the tableau.
There is a variant using ten column of five cards each (with the other two
cards starting in the holding area), where the tableau builds down in suit
instead of alternating color, and only kings can be moved into empty tableau
columns. This is sometimes called Seahaven Towers. It is a somewhat easier
game to solve since more moves are "forced" (in the sense of being provably
good).
General approach: Depth-first search seems to be fast enough, so we won't try
anything fancier.
Positions are hashed and cached so the search doesn't expand them more than
once. A position is cached in canonical form, obtained by sorting the cards
in the holding area and -- for the purposes of comparison of positions --
sorting the tableau columns based on the cards at the head of each column.
(Actually sorting the columns fails due to the need to maintain a matching
data structure that maps each card to its position.)
Part of the data structure representing a position gives just ranks and
colors. A separate part notes, for each card (rank+suit, not just color),
where the card is located, and whether it is eligible to be interchanged
with the like-color card (because the resulting position was reached
elsewhere in the search tree), or whether it MUST be interchanged (because
at some point the only accessible card of the pair was moved to a
foundation pile). A newly-reached position can be ignored if the first
part of the data structure matches a previously-reached position and the
second part has no cards that can be interchanged where the previous
position didn't.
The only "forced moves" entail moving a card to the foundation piles if
(a) it is eligible to be moved there and (b) both of the cards that could
be played on this one (i.e., next lower rank and opposite color) are
either already on the foundations or are eligible to be played there.
Forced moves are made one card at a time to simplify undoing them, but no
other moves are considered if a forced move is available.
Input format is 52 cards represented either as rank then suit or suit then
rank, where suit is one of CDHS and rank is one of A23456789TJQK. Lower-case
is okay too. Other characters such as spaces and line breaks are ignored and
can be used for readability. E.g.: 222
7h 9s Kc 5d 8h 3h 6d 9d
7d As 3c Js 8c Kd 2d Ac
2s Qc Jh 8d Th Ts 9h 5h
Qs 6c 4s 6h Ad 8s 2h 4d
Ah 7s 3d Jd 9c 3s Qd Kh
7c 4c Jc Qh 2c Tc 5s 4h
5c Td Ks 6s
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define true (1==1)
#define false (1==0)
typedef unsigned char uchar;
typedef uchar Pair; /* 000crrrr: c=color(0=black), r=rank(1-13) */
typedef uchar Card;
#define Rank(card) ((card) & 0x0F)
#define Suit(card) ((card) >> 4)
#define Colormate(card) ((card) ^ 0x20)
#define Either(card) ((card) & 0x1F)
typedef uchar Loc; /* cccnnnnn: c=column, n=index in column; or
00011111: in holding area; or
11111111: on foundation */
#define HOLDING 0x1F
#define FOUNDATION 0xFF
#define Col(loc) ((loc) >> 5)
#define Index(loc) ((loc) & 0x1F)
typedef uchar Swap; /* 0 = cannot be swapped (includes cases where
it could've been but the other card has
since been removed), 1 = can be swapped,
2 =has been swapped (matters only when
reconstructing path to a position) */
#define NO_SWAP 0
#define CAN_SWAP 1
#define DID_SWAP 2
typedef struct Column {
uchar count; /* number of cards in column */
Pair cards[19];
} Column;
typedef struct Position {
uchar foundations[4];
Pair hold[4];
uchar colSeq[8]; /* sorted order for tableau columns */
Column tableau[8];
Loc location[52];
Swap swappable[26];
Card moved; /* most recently moved card */
int mustTry; /* must move to/from this col if possible */
struct Position *via; /* position from which this was reached */
struct Position *swapvia; /* ditto but with most recently moved
card swapped with its mate */
struct Position *chain; /* for chaining in hash table */
long hash;
} Position;
#define SortedCol(pos, index) &(pos)->tableau[(pos)->colSeq[index]]
int cardToIndex[0x3F]; /* maps Card -> index in info[] (0-51) */
#define CLUBS (0 << 4)
#define DIAMONDS (1 << 4)
#define SPADES (2 << 4)
#define HEARTS (3 << 4)
static int verbosity = 0; /* how much to print out while running */
static int show = false; /* whether to show solution if found */
static int showorig = false;/* whether to show original layout */
static int maxdepth = 100000;/* max search depth before giving up a line */
#define TRACTABLE 200000 /* max distinct positions before giving up */
/* statistics gathering */
static long generated = 0; /* # times AddToTree called */
static long distinct = 0; /* # different positions reached */
static long hashes = 0; /* # hash table slots used */
static long rejected = 0; /* # rejected due to repeated column */
static long swaps = 0; /* # positions combining lines via swaps */
static long maxout = 0; /* # trees pruned for hitting max depth,
or max depth reached if no limit imposed */
static long maxfree = 0; /* max of (open holds+1) * (2^spaces) */
static long brokeSeq = 0; /* # trees pruned due to breaking sequences */
static long windepth; /* depth of winning line */
/* Random number package. Roll our own since so many systems seem to have
* pretty bad ones. Include code for both MSDOS and UNIX time functions to
* initialise it.
*/
#ifdef __MSDOS__
#include "time.h"
#define GET_TIME(var) {time(&var);}
#else
#include "sys/time.h"
#define GET_TIME(var) { /
struct timeval now; /
gettimeofday(&now, 0); /
var = now.tv_sec + now.tv_usec; /
}
#endif
#define RAND_BUFSIZE 98
#define RAND_HASHWORD 27
#define RAND_BITSUSED 29
#define RAND_MASK ((1 << RAND_BITSUSED) - 1)
static long rand_buf[RAND_BUFSIZE], *rand_bufLoc;
static long Rand();
static char rand_flips[1<<6];
static short rand_flipper;
#define RAND_FLIPMASK (sizeof(rand_flips) - 1)
static RandFlipInit(loc) int loc;
{
static char f[] =
" 11 111 1 1 11 1 111 1 11 11 11111 11 1 1 11 1 11 ";
rand_flipper = loc;
for (loc=0; loc<=RAND_FLIPMASK; loc++)
rand_flips[loc] = (f[loc]==' '? 0 : -1);
}
static RandInit(seed) long seed;
{
int n;
if (seed == 0) GET_TIME(seed);
memset(rand_buf, 0, sizeof(rand_buf));
for (n=0; n<RAND_BITSUSED; n++)
{ rand_buf[n] = RAND_MASK >> n;
rand_buf[n+RAND_BITSUSED+3] = 1 << n;
}
for (n=RAND_BITSUSED*2+3; n<RAND_BUFSIZE; n++)
{ rand_buf[n] = seed & RAND_MASK;
seed = seed * 3 + 01234567;
}
rand_bufLoc = rand_buf;
RandFlipInit(0);
for (n=(seed & 0377); n<50000; n++) Rand();
}
static long Rand()
{
long f;
if (!rand_bufLoc) RandInit(0);
rand_bufLoc = (rand_bufLoc == rand_buf ?
rand_bufLoc+RAND_BUFSIZE-1 : rand_bufLoc-1);
*rand_bufLoc ^= *(rand_bufLoc >= rand_buf+RAND_HASHWORD ?
rand_bufLoc-RAND_HASHWORD
: rand_bufLoc+(RAND_BUFSIZE-RAND_HASHWORD));
f = rand_flips[rand_flipper++ & RAND_FLIPMASK];
return(*rand_bufLoc ^ (f & RAND_MASK));
}
/* End of random number package *******************************/
/* special in that only tableau[] is set, and it stores Cards instead of Pairs */
static Position orig;
/* Read original position from stdin into orig.
*/
static void ReadOriginal(char *filename)
{
int count = 0;
Card cd = 0;
FILE *f = stdin;
if (filename != NULL && (f = fopen(filename, "r")) == NULL) {
printf("free_cell: can't open %s/n", filename);
exit(1);
}
memset(&orig, 0, sizeof(orig));
while (count < 104) {
int c = getc(f);
if (c == EOF) {
printf("Insufficient input./n");
exit(1);
}
if (c >= '2' && c <= '9') cd += c - '0';
else if ((c |= 0x20) == 'a') cd += 1; // |= 0x20 将大写字母变为小写
else if (c == 't') cd += 10;
else if (c == 'j') cd += 11;
else if (c == 'q') cd += 12;
else if (c == 'k') cd += 13;
else if (c == 'c') cd += CLUBS;
else if (c == 'd') cd += DIAMONDS;
else if (c == 'h') cd += HEARTS;
else if (c == 's') cd += SPADES;
else continue;
if (count++ % 2 != 0) {
Column *col = &orig.tableau[((count-1)>>1)%8];
col->cards[col->count++] = (Pair) cd;
cd = 0;
}
}
if (filename != NULL) fclose(f);
}
/* Build original as simple sorted deck.
*/
static void BuildOriginal()
{
int i;
memset(&orig, 0, sizeof(orig));
for (i=0; i<52; i++) {
Column *col = &orig.tableau[i%8];
Card cd = (i>>2)+1 + ((i&3)<<4);
col->cards[col->count++] = (Pair) cd;
}
}
/* Shuffle the starting positions in orig.
*/
static void Shuffle()
{
int i = 52;
while (i > 1) {
int swap = Rand() % (i--);
Pair *c1 = &orig.tableau[i%8].cards[i/8];
Pair *c2 = &orig.tableau[swap%8].cards[swap/8];
Pair temp = *c1;
*c1 = *c2;
*c2 = temp;
}
}
/* Initialise a Position record based on orig.
*/
static void InitialPosition(Position *pos)
{
int i;
for (i=0; i<52; i++) {
cardToIndex[((i/13)<<4)+(i%13)+1] = i;
pos->location[i] = 0xFF;
}
for (i=0; i<52; i++) {
int col = (i % 8), index = (i / 8);
Card c = (Card) orig.tableau[col].cards[index];
int x = cardToIndex[c];
if (pos->location[x] != 0xFF) {
printf("Card %02x appears twice!/n", c);
exit(1);
}
pos->tableau[col].cards[index] = Either(c);
pos->tableau[col].count++;
pos->location[x] = (col<<5) + index;
}
memset(pos->swappable, NO_SWAP, sizeof(pos->swappable));
for (i=0; i<8; i++) pos->colSeq[i] = i;
}
/* Allocate a new Position structure. Allocates many at a time to reduce
* malloc overhead. Saves linked list of blocks of positions to enable
* freeing the storage, which can be necessary if we're examining hundreds
* of positions in a single run of the program.
*/
#define POS_BLOCK 2000
typedef struct Block {
Position block[POS_BLOCK];
struct Block *link;
} Block;
static int pos_avail = 0;
static Block *block = NULL;
static Position *NewPosition()
{
if (pos_avail-- == 0) {
Block *more = (Block *) calloc(1, sizeof(Block));
if (more == NULL) {
printf("/nERROR: Out of memory!/n");
exit(2);
}
more->link = block;
block = more;
pos_avail = POS_BLOCK - 1;
}
return (block->block + pos_avail);
}
/* Show a card; rank in uppercase, suit in lowercase, trailing space.
*/
void ShowCard(Card card)
{
static char *rank = "?A23456789TJQK", *suit = "cdsh";
if (card == 0) printf(".. ");
else printf("%c%c ", rank[Rank(card)], suit[Suit(card)]);
}
/* Show the topmost card on a foundation pile.
*/
void ShowFoundation(Position *pos, int s)
{
uchar r = pos->foundations[s];
if (r == 0) printf(" "); else ShowCard( (Card)((s<<4)+r) );
}
/* Show a position as a rectangular layout.
*/
void ReadablePosition(Position *pos)
{
int suit, rank, h = 0;
int row=0, col;
for (suit=3; suit>=0; suit--) for (rank=13; rank>0; rank--) {
Card c = (suit<<4) + rank;
Loc where = pos->location[cardToIndex[c]];
if (where == HOLDING)
pos->hold[h++] = c;
else if (where != FOUNDATION)
pos->tableau[Col(where)].cards[Index(where)] = c;
}
for (col=0; col<4; col++) {
ShowCard(pos->hold[col]);
pos->hold[col] &= 0x1F;
}
for (col=0; col<4; col++) ShowFoundation(pos, col);
printf("/n");
while (true) {
int none = true;
for (col=0; col<8; col++) {
int cd = pos->tableau[col].cards[row];
pos->tableau[col].cards[row] &= 0x1F;
if (cd) {ShowCard((Card)cd); none = false;}
else printf(" ");
}
printf("/n");
if (none) break;
row++;
}
fflush(stdout);
}
int foobar = 0;
#define HASH_SIZE 19001
static Position **tree;
/* See if this position is already in the search tree. If so, return false.
* Else add it and return true.
*/
static int AddToTree(Position *pos)
{
long hash = 0;
int i;
Position **probe;
generated++;
for (i=0; i<4; i++) hash = hash*3 + pos->hold[i];
for (i=0; i<8; i++) {
Column *col = SortedCol(pos, i);
int ct = col->count;
hash *= 5;
if (ct) hash += col->cards[ct-1];
}
pos->hash = hash;
pos->swapvia = NULL;
probe = &tree[hash % HASH_SIZE];
if (*probe == NULL) hashes++;
else while (*probe != NULL) {
if ((*probe)->hash == hash && memcmp((*probe)->hold, pos->hold,
4*sizeof(Card)) == 0) {
for (i=0; i<8; i++) {
Column *c1 = SortedCol(pos, i);
Column *c2 = SortedCol(*probe, i);
if (c1->count != c2->count) break;
if (c1->count == 0) {i=8; break;}
if (memcmp(c1->cards, c2->cards,
c1->count*sizeof(Card)) != 0) break;
}
if (i == 8) for (i=0; i<26; i++) {
if (pos->swappable[i] > (*probe)->swappable[i])
break;
}
if (i == 26) {
int latest = cardToIndex[pos->moved],
mate = cardToIndex[Colormate(pos->moved)],
either = cardToIndex[Either(pos->moved)];
if (pos->swappable[either] == NO_SWAP &&
pos->location[latest] !=
(*probe)->location[latest] &&
pos->location[mate] ==
(*probe)->location[latest]) {
pos->swappable[either] = CAN_SWAP;
pos->swapvia = *probe;
}
else
return(false);
}
}
probe = &((*probe)->chain);
}
if (pos->swapvia) swaps++;
*probe = pos;
pos->chain = NULL;
if ((distinct++ & 0x7FFF) == 0x7FFF && verbosity == 1) {
printf("%d distinct positions.../n", distinct);
fflush(stdout);
}
return(true);
}
/* Return true if tableau has an empty column.
*/
static int HasSpace(Position *pos)
{
return(pos->tableau[pos->colSeq[7]].count == 0);
}
/* Return card at bottom of column (resolving Pair to single Card).
*/
static Card Bottom(Position *pos, int i)
{
Column *col = &pos->tableau[i];
Card cd = col->cards[col->count - 1];
if (pos->location[cardToIndex[cd]] != (i<<5) + col->count - 1)
cd ^= 0x20;
return(cd);
}
/* Return true if card is available to be played.
*/
static int Available(Position *pos, Card card)
{
Loc where;
if (card == 0) return(false);
where = pos->location[cardToIndex[card]];
if (where == HOLDING) return(true);
if (where == FOUNDATION) return(false);
if (pos->tableau[Col(where)].count == Index(where)+1) return(true);
return(false);
}
/* Sort the holding area into descending order (hence spaces come last).
* Use homogeneous sorting network.
*/
static void SortHoldingArea(Position *pos)
{
Card *h = pos->hold;
Card temp;
#define SWAP(i,j) if (h[i]<h[j]) {temp=h[i]; h[i]=h[j]; h[j]=temp;}
// 4个元素的排序,比普通的冒泡排序少比较一次
SWAP(0,1)
SWAP(2,3)
SWAP(0,2) // 找到最大的值,放到序号0
SWAP(1,3) // 找到最小的值,放到序号3
SWAP(1,2)
}
/* Sort the tableau columns based on the top cards of the columns.
* Only called when the top card of a column actually changes. Note
* that the columns will be mostly sorted already.
*/
static void SortColumns(Position *pos)
{
int thru = 7;
while (thru > 0) {
int i, need = 0;
for (i=1; i<=thru; i++) {
if (pos->tableau[pos->colSeq[i]].cards[0] >
pos->tableau[pos->colSeq[i-1]].cards[0]) {
uchar temp = pos->colSeq[i];
pos->colSeq[i] = pos->colSeq[i-1];
pos->colSeq[i-1] = temp;
need = i-1;
}
}
thru = need;
}
}
/* Move the specified card from wherever it is to new location. If this
* requires swapping a swappable pair, update struct accordingly. Assumes
* destination of move is legal for card being moved.
*/
static Loc MoveCard(pos, card, whither) Position *pos; Card card; Loc whither;
{
Loc *where = &pos->location[cardToIndex[card]];
if (!Available(pos, card)) {
Loc temp, *where2 = &pos->location[cardToIndex[Colormate(card)]];
Swap *swap = &pos->swappable[cardToIndex[Either(card)]];
if (*swap != CAN_SWAP
|| !Available(pos, (Card)Colormate(card))) {
printf("Bug! Move of unavailable card./n");
exit(2);
}
*swap = DID_SWAP;
temp = *where;
*where = *where2;
*where2 = temp;
}
pos->mustTry = (-1);
if (*where == HOLDING) {
int i;
for (i=0; i<4; i++) if (pos->hold[i] == Either(card)) {
pos->hold[i] = 0;
break;
}
SortHoldingArea(pos);
}
else {
Column *col = &pos->tableau[Col(*where)];
col->count--;
col->cards[col->count] = 0;
if (col->count == 0) SortColumns(pos);
else if (whither != FOUNDATION &&
col->cards[col->count-1] == ((Either(card)+1) ^ 0x10))
pos->mustTry = Col(*where);
}
*where = whither;
if (whither == FOUNDATION)
pos->foundations[Suit(card)]++;
else if (whither == HOLDING) {
pos->hold[3] = Either(card);
SortHoldingArea(pos);
}
else {
Column *col = &pos->tableau[Col(whither)];
col->cards[col->count++] = Either(card);
if (col->count == 1) SortColumns(pos);
if (Index(whither) != col->count-1) {
printf("Bug! Moved to wrong index in column./n");
exit(2);
}
}
pos->moved = card;
return 1; // added by anning
}
static depth = 0;
static ShowMove(Position *pos, char *tag, int swap)
{
Card moved = pos->moved;
Loc whither = pos->location[cardToIndex[moved]];
printf("%4d: ", depth);
ShowCard( (Card)(swap? Colormate(moved) : moved) );
printf("-> ");
if (whither == HOLDING) printf("free cell");
else if (whither == FOUNDATION) printf("foundation");
else printf("tableau %d", Col(whither)+1);
if (tag) printf(" (%s)", tag);
printf("/n");
if (verbosity > 2) fflush(stdout);
}
/* Show the winning path, in reverse order (because it's convenient).
*/
static int FollowPath(pos, tally) Position *pos; int tally;
{
Position *p = pos;
int steps = 0;
while (p->moved) {
int swap = false, index = cardToIndex[Either(p->moved)];
if (pos->swappable[index] == DID_SWAP &&
p->swappable[index] == CAN_SWAP) {
if (p->swapvia) {
if (!tally) {
printf(" Bypassing seq that swaps ");
ShowCard(p->moved);
printf(" with ");
ShowCard((Card)Colormate(p->moved));
printf("/n");
}
p = p->swapvia;
continue;
}
else swap = true;
}
if (tally) steps++;
else {ReadablePosition(p); ShowMove(p, NULL, swap); depth--;}
p = p->via;
}
if (tally) windepth = steps;
return(steps);
}
static void ShowPath(pos) Position *pos;
{
Position *p = pos;
printf("Winning path (in reverse order):/n");
depth = FollowPath(pos, true);
(void) FollowPath(pos, false);
printf("/n");
}
/* Make (and if verbose, print) a move, then recursively call DFS if the
* resulting position is new.
*/
static Position *DFS(); /* forward decl */
static Position *TryMove(Position *via, Position *pos, Card card, Loc whither)
{
int dup = false;
depth++;
*pos = *via;
pos->via = via;
MoveCard(pos, card, whither);
if ((whither & 0x1F) != 0x1F) {
/* moved to col; check for repetition */
int i = Col(whither);
Column *col = &pos->tableau[i];
Position *prev;
for (prev=via; prev; prev=prev->via) {
Column *oldcol = &prev->tableau[i];
if (oldcol->count != 0 &&
oldcol->cards[0] != col->cards[0]) break;
if (oldcol->count == col->count &&
memcmp(oldcol->cards, col->cards,
col->count*sizeof(Card)) == 0) {
int j;
for (j=0; j<col->count; j++) {
int x = cardToIndex[col->cards[j]];
int xx = x;
if (pos->location[x] != (i<<5)+j)
xx += 26;
if (prev->location[xx] != pos->location[xx]
|| prev->swappable[x] != pos->swappable[x]) break;
}
if (j >= col->count) dup = true;
break;
}
}
}
if (dup) {
if (verbosity >= 2) ShowMove(pos, "rejected", false);
rejected++;
}
else if (AddToTree(pos)) {
if (verbosity >= 2)
ShowMove(pos, (pos->swapvia? "merged" : NULL), false);
pos = DFS(pos, NewPosition());
if (pos == NULL) return(NULL);
}
else if (verbosity >= 2) ShowMove(pos, "old", false);
depth--;
return(pos);
}
/* Depth-first search. Generate all positions reachable from "via". (If
* there is a forced move, just do the forced move.) Use "pos" as the
* place to construct new positions; ask for a new pos struct as needed.
* For positions that haven't already been reached, add them and call DFS
* recursively via TryMove. If a winning position is found, return NULL.
* Else return a pointer to a not-yet-used Position struct.
*/
static Position *DFS(Position *via, Position *pos)
{
int i, j, intab = 0, goodcols = 0; Card moved; Loc onto;
/* check for victory */
if (via->foundations[0] == 13 && via->foundations[1] == 13
&& via->foundations[2] == 13 && via->foundations[3] == 13) {
if (show) ShowPath(via);
else if (verbosity > 0) (void) FollowPath(via, true);
return(NULL);
}
if (maxdepth > 99999) {
if (depth > maxout) maxout = depth;
}
else if (depth >= maxdepth) {
maxout++;
return(pos);
}
if (distinct >= TRACTABLE) return(pos); /* give up */
for (i=3; i>=0 && via->hold[i]==0; i--) {}
for (j=7; j>=0 && via->tableau[via->colSeq[j]].count==0; j--) {}
i = (3 - i + 1) << (7 - j);
if (i > maxfree) {
maxfree = i;
/* if (i == 6) ReadablePosition(via); */
}
/* check for forced moves to foundation */
for (i=0; i<4; i++) {
int r = via->foundations[i]+1; /* next rank to go here */
int opp1 = (i^1), opp2 = (opp1^2); /* other color suits */
if (r > 13 || via->foundations[opp1]+2 < r ||
via->foundations[opp2]+2 < r) continue;
/* move is forced if card is available; note that if card
is swappable with its colormate, then if either card is
available they both are, since next lower cards of other
color are by definition already moved to foundations */
moved = (i<<4) + r;
if (Available(via, moved))
return TryMove(via, pos, moved, (Loc) FOUNDATION);
/* card not available, but note good column to dig in */
goodcols |= (1 << Col(via->location[cardToIndex[moved]]));
}
/* if previous move broke a sequence (and didn't move to foundation),
then mustTry will be >= 0, saying we must move to/from that column
if it is possible to do so */
if (via->mustTry >= 0) {
Column *col = &via->tableau[via->mustTry];
int tried = false;
moved = Bottom(via, via->mustTry);
/* if not a King, try moving to another non-empty column */
if (Rank(moved) < 13) {
Card onto = (moved+1) ^ 0x10;
for (i=0; i<2; i++) {
Loc loc = via->location[cardToIndex[onto]];
if (Available(via, onto) && loc != HOLDING) {
tried = true;
pos = TryMove(via, pos, moved, (Loc)(loc+1));
if (pos == NULL) return(NULL);
}
onto = Colormate(onto);
}
}
/* if can't move within tableau, try moving to holding area
or, if that's full, to empty column */
onto = FOUNDATION;
if (via->hold[3] == 0) onto = HOLDING;
else if (HasSpace(via)) onto = (via->colSeq[7]<<5) + 0;
if (!tried && onto != FOUNDATION &&
(col->count>1 || !HasSpace(via))) {
tried = true;
pos = TryMove(via, pos, moved, onto);
if (pos == NULL) return(NULL);
}
/* if neither of the above, try move to foundation (if can move
elsewhere, will try moving to foundation from there) */
if (!tried && via->foundations[Suit(moved)]+1 == Rank(moved)) {
tried = true;
pos = TryMove(via, pos, moved, (Loc) FOUNDATION);
if (pos == NULL) return(NULL);
}
/* move colormate of just-moved card onto revealed card */
if (Available(via, (Card)Colormate(via->moved))) {
tried = true;
pos = TryMove(via, pos, (Card)Colormate(via->moved),
(Loc)((via->mustTry<<5) + col->count) );
if (pos == NULL) return(NULL);
}
/* if this column is a singleton and the only place it can
move is into another empty column, abandon the line */
if (col->count == 1 && via->tableau[via->colSeq[7]].count == 0)
tried = true;
if (tried) {
brokeSeq++;
return(pos);
}
}
/* try non-forced moves to foundation, but don't move the second
of colormates up if both lower cards are still in play */
for (i=0; i<4; i++) {
int r = via->foundations[i]+1; /* next rank to go here */
if (r > 13) continue;
if (via->foundations[i^2] >= r && via->foundations[i^1]+2 < r
&& via->foundations[i^3]+2 < r) continue;
moved = (i<<4) + r;
/* if card & colormate both avail, moving original suffices;
other must then be able to move to where original was */
if (Available(via, moved)) {
pos = TryMove(via, pos, moved, (Loc) FOUNDATION);
if (pos == NULL) return(NULL);
}
else if (via->swappable[cardToIndex[Either(moved)]] &&
Available(via, (Card)Colormate(moved))) {
pos = TryMove(via, pos, moved, (Loc) FOUNDATION);
if (pos == NULL) return(NULL);
}
}
/* next preference is moves onto the bottoms of non-empty columns;
cards which can do this never need to move to empty cols or
holding cells (though in some odd cases they may end up moving
from the other col to a holding cell) */
for (i=0; i<8; i++) {
Column *col = &via->tableau[i];
if (col->count == 0) continue;
/* bottom card guaranteed to be > Ace else force-move above */
moved = (col->cards[col->count - 1] - 1) ^ 0x10;
onto = (i<<5) + col->count;
for (j=0; j<2; j++) {
if (Available(via, moved)) {
Loc where;
pos = TryMove(via, pos, moved, onto);
if (pos == NULL) return(NULL);
where = via->location[cardToIndex[moved]];
if (where != HOLDING)
intab |= 1 << (Col(where));
}
moved ^= 0x20;
}
}
/* next try moving from columns with 2+ cards to hold; if no hold,
move to empty columns; (if both, can get to empty cols via hold) */
onto = FOUNDATION;
if (via->hold[3] == 0)
onto = HOLDING;
else if (via->tableau[via->colSeq[7]].count == 0)
onto = (via->colSeq[7]<<5) + 0;
if (onto != FOUNDATION) {
for (i=0; i<8; i++)
if ((goodcols & (1<<i)) != 0 && (intab & (1<<i)) == 0) {
if (via->tableau[i].count < 2) continue;
pos = TryMove(via, pos, Bottom(via, i), onto);
if (pos == NULL) return(NULL);
}
for (i=0; i<8; i++)
if ((goodcols & (1<<i)) == 0 && (intab & (1<<i)) == 0) {
if (via->tableau[i].count < 2) continue;
pos = TryMove(via, pos, Bottom(via, i), onto);
if (pos == NULL) return(NULL);
}
}
/* try moving from holding area to empty column; vary which one is
tried first, in case the key move we're looking for is hold[3] */
if (via->tableau[via->colSeq[7]].count == 0) {
for (i=0; i<4; i++) {
Card below;
moved = via->hold[(i+(depth>>2))&3];
if (moved == 0) continue;
if (via->location[cardToIndex[moved]] != HOLDING)
moved ^= 0x20;
/* don't move a card down if both cards that could
use it are already on foundations */
below = (moved-1) ^ 0x10;
if (via->location[cardToIndex[below]] != FOUNDATION
|| via->location[cardToIndex[below^0x20]]
!= FOUNDATION) {
pos = TryMove(via, pos, moved,
(Loc) ((via->colSeq[7]<<5)+0));
if (pos == NULL) return(NULL);
}
}
}
/* try moving from singleton columns to holding area, but only if
there isn't another empty column already */
if (via->hold[3] == 0 && !HasSpace(via)) {
for (i=0; i<8; i++) if ((intab & (1<<i)) == 0) {
if (via->tableau[i].count != 1) continue;
pos = TryMove(via, pos, Bottom(via, i), (Loc) HOLDING);
if (pos == NULL) return(NULL);
}
}
/* last, try non-forced moves to foundation, where we're moving
up the second of colormates and both lower cards are in play */
for (i=0; i<4; i++) {
int r = via->foundations[i]+1; /* next rank to go here */
if (r > 13) continue;
if (!(via->foundations[i^2] >= r && via->foundations[i^1]+2 < r
&& via->foundations[i^3]+2 < r)) continue;
moved = (i<<4) + r;
if (Available(via, moved)) {
pos = TryMove(via, pos, moved, (Loc) FOUNDATION);
if (pos == NULL) return(NULL);
}
}
return(pos);
}
/* 产生一次移动,如果移动有效(新位置pos不重复),则返回true;否则移动失败
* 返回false。该函数不递归调用DFS函数。
* 再位置via移动牌card,位置为whither。如果移动成功,新位置用pos保存
*/
static int TryMoveLoop(Position *via, Position *pos, Card card, Loc whither)
{
int dup = false;
depth++;
*pos = *via;
pos->via = via;
MoveCard(pos, card, whither);
if ((whither & 0x1F) != 0x1F) {
/* moved to col; check for repetition */
int i = Col(whither);
Column *col = &pos->tableau[i];
Position *prev;
for (prev=via; prev; prev=prev->via) {
Column *oldcol = &prev->tableau[i];
if (oldcol->count != 0 &&
oldcol->cards[0] != col->cards[0]) break;
if (oldcol->count == col->count &&
memcmp(oldcol->cards, col->cards,
col->count*sizeof(Card)) == 0) {
int j;
for (j=0; j<col->count; j++) {
int x = cardToIndex[col->cards[j]];
int xx = x;
if (pos->location[x] != (i<<5)+j)
xx += 26;
if (prev->location[xx] != pos->location[xx]
|| prev->swappable[x] != pos->swappable[x]) break;
}
if (j >= col->count) dup = true;
break;
}
}
}
if (dup) {
if (verbosity >= 2) ShowMove(pos, "rejected", false);
rejected++;
}
else if (AddToTree(pos)) {
if (verbosity >= 2)
ShowMove(pos, (pos->swapvia? "merged" : NULL), false);
return true;
}
else if (verbosity >= 2) ShowMove(pos, "old", false);
depth--;
return false; // 此时返回的pos中内容无意义
}
/* Depth-first search. Generate all positions reachable from "via". (If
* there is a forced move, just do the forced move.) Use "pos" as the
* place to construct new positions; ask for a new pos struct as needed.
* For positions that haven't already been reached, add them and call DFS
* recursively via TryMove. If a winning position is found, return NULL.
* Else return a pointer to a not-yet-used Position struct.
* 由Anning修改为非递归调用版本。因为使用递归调用有可能导致函数栈溢出。
* If a winning position is found, return true. Else return false.
*/
static int DFSLoop(Position *via)
{
Position *pos;
int newPos = true; // 是否需要产生新位置。当回溯时,不要新位置
int i, j, intab = 0, goodcols = 0; Card moved; Loc onto;
int moveSucc; // 移动是否成功
do
{
begin_label:
intab = 0, goodcols = 0;
if (newPos)
pos = NewPosition();
else
newPos = true;
/* check for victory */
if (via->foundations[0] == 13 && via->foundations[1] == 13
&& via->foundations[2] == 13 && via->foundations[3] == 13) {
if (show) ShowPath(via);
else if (verbosity > 0) (void) FollowPath(via, true);
return(true);
}
if (maxdepth > 99999) {
if (depth > maxout) maxout = depth;
}
else if (depth >= maxdepth) {
maxout++;
return(false);
}
//if (distinct >= TRACTABLE) return(false); /* give up */
for (i=3; i>=0 && via->hold[i]==0; i--) {}
for (j=7; j>=0 && via->tableau[via->colSeq[j]].count==0; j--) {}
i = (3 - i + 1) << (7 - j);
if (i > maxfree) {
maxfree = i;
/* if (i == 6) ReadablePosition(via); */
}
/* check for forced moves to foundation */
for (i=0; i<4; i++) {
int r = via->foundations[i]+1; /* next rank to go here */
int opp1 = (i^1), opp2 = (opp1^2); /* other color suits */
if (r > 13 || via->foundations[opp1]+2 < r ||
via->foundations[opp2]+2 < r) continue;
/* move is forced if card is available; note that if card
is swappable with its colormate, then if either card is
available they both are, since next lower cards of other
color are by definition already moved to foundations */
moved = (i<<4) + r;
if (Available(via, moved)) {
moveSucc = TryMoveLoop(via, pos, moved, (Loc) FOUNDATION);
if (moveSucc) {
via = pos;
goto begin_label;
}
else // 否则需要回溯
goto back_label;
}
/* card not available, but note good column to dig in */
goodcols |= (1 << Col(via->location[cardToIndex[moved]]));
}
/* if previous move broke a sequence (and didn't move to foundation),
then mustTry will be >= 0, saying we must move to/from that column
if it is possible to do so */
if (via->mustTry >= 0) {
Column *col = &via->tableau[via->mustTry];
int tried = false;
moved = Bottom(via, via->mustTry);
/* if not a King, try moving to another non-empty column */
if (Rank(moved) < 13) {
Card onto = (moved+1) ^ 0x10;
for (i=0; i<2; i++) {
Loc loc = via->location[cardToIndex[onto]];
if (Available(via, onto) && loc != HOLDING) {
tried = true;
moveSucc = TryMoveLoop(via, pos, moved, (Loc)(loc+1));
if (moveSucc) {
via = pos;
goto begin_label;
}
}
onto = Colormate(onto);
}
}
/* if can't move within tableau, try moving to holding area
or, if that's full, to empty column */
onto = FOUNDATION;
if (via->hold[3] == 0) onto = HOLDING;
else if (HasSpace(via)) onto = (via->colSeq[7]<<5) + 0;
if (!tried && onto != FOUNDATION &&
(col->count>1 || !HasSpace(via))) {
tried = true;
moveSucc = TryMoveLoop(via, pos, moved, onto);
if (moveSucc) {
via = pos;
goto begin_label;
}
}
/* if neither of the above, try move to foundation (if can move
elsewhere, will try moving to foundation from there) */
if (!tried && via->foundations[Suit(moved)]+1 == Rank(moved)) {
tried = true;
moveSucc = TryMoveLoop(via, pos, moved, (Loc) FOUNDATION);
if (moveSucc) {
via = pos;
goto begin_label;
}
}
/* move colormate of just-moved card onto revealed card */
if (Available(via, (Card)Colormate(via->moved))) {
tried = true;
moveSucc = TryMoveLoop(via, pos, (Card)Colormate(via->moved),
(Loc)((via->mustTry<<5) + col->count) );
if (moveSucc) {
via = pos;
goto begin_label;
}
}
/* if this column is a singleton and the only place it can
move is into another empty column, abandon the line */
if (col->count == 1 && via->tableau[via->colSeq[7]].count == 0)
tried = true;
if (tried) {
brokeSeq++;
goto back_label;
}
}
/* try non-forced moves to foundation, but don't move the second
of colormates up if both lower cards are still in play */
for (i=0; i<4; i++) {
int r = via->foundations[i]+1; /* next rank to go here */
if (r > 13) continue;
if (via->foundations[i^2] >= r && via->foundations[i^1]+2 < r
&& via->foundations[i^3]+2 < r) continue;
moved = (i<<4) + r;
/* if card & colormate both avail, moving original suffices;
other must then be able to move to where original was */
if (Available(via, moved)) {
moveSucc = TryMoveLoop(via, pos, moved, (Loc) FOUNDATION);
if (moveSucc) {
via = pos;
goto begin_label;
}
}
else if (via->swappable[cardToIndex[Either(moved)]] &&
Available(via, (Card)Colormate(moved))) {
moveSucc = TryMoveLoop(via, pos, moved, (Loc) FOUNDATION);
if (moveSucc) {
via = pos;
goto begin_label;
}
}
}
/* next preference is moves onto the bottoms of non-empty columns;
cards which can do this never need to move to empty cols or
holding cells (though in some odd cases they may end up moving
from the other col to a holding cell) */
for (i=0; i<8; i++) {
Column *col = &via->tableau[i];
if (col->count == 0) continue;
/* bottom card guaranteed to be > Ace else force-move above */
moved = (col->cards[col->count - 1] - 1) ^ 0x10;
onto = (i<<5) + col->count;
for (j=0; j<2; j++) {
if (Available(via, moved)) {
Loc where;
moveSucc = TryMoveLoop(via, pos, moved, onto);
if (moveSucc) {
via = pos;
goto begin_label;
}
where = via->location[cardToIndex[moved]];
if (where != HOLDING)
intab |= 1 << (Col(where));
}
moved ^= 0x20;
}
}
/* next try moving from columns with 2+ cards to hold; if no hold,
move to empty columns; (if both, can get to empty cols via hold) */
onto = FOUNDATION;
if (via->hold[3] == 0)
onto = HOLDING;
else if (via->tableau[via->colSeq[7]].count == 0)
onto = (via->colSeq[7]<<5) + 0;
if (onto != FOUNDATION) {
for (i=0; i<8; i++)
if ((goodcols & (1<<i)) != 0 && (intab & (1<<i)) == 0) {
if (via->tableau[i].count < 2) continue;
moveSucc = TryMoveLoop(via, pos, Bottom(via, i), onto);
if (moveSucc) {
via = pos;
goto begin_label;
}
}
for (i=0; i<8; i++)
if ((goodcols & (1<<i)) == 0 && (intab & (1<<i)) == 0) {
if (via->tableau[i].count < 2) continue;
moveSucc = TryMoveLoop(via, pos, Bottom(via, i), onto);
if (moveSucc) {
via = pos;
goto begin_label;
}
}
}
/* try moving from holding area to empty column; vary which one is
tried first, in case the key move we're looking for is hold[3] */
if (via->tableau[via->colSeq[7]].count == 0) {
for (i=0; i<4; i++) {
Card below;
moved = via->hold[(i+(depth>>2))&3];
if (moved == 0) continue;
if (via->location[cardToIndex[moved]] != HOLDING)
moved ^= 0x20;
/* don't move a card down if both cards that could
use it are already on foundations */
below = (moved-1) ^ 0x10;
if (via->location[cardToIndex[below]] != FOUNDATION
|| via->location[cardToIndex[below^0x20]]
!= FOUNDATION) {
moveSucc = TryMoveLoop(via, pos, moved,
(Loc) ((via->colSeq[7]<<5)+0));
if (moveSucc) {
via = pos;
goto begin_label;
}
}
}
}
/* try moving from singleton columns to holding area, but only if
there isn't another empty column already */
if (via->hold[3] == 0 && !HasSpace(via)) {
for (i=0; i<8; i++) if ((intab & (1<<i)) == 0) {
if (via->tableau[i].count != 1) continue;
moveSucc = TryMoveLoop(via, pos, Bottom(via, i), (Loc) HOLDING);
if (moveSucc) {
via = pos;
goto begin_label;
}
}
}
/* last, try non-forced moves to foundation, where we're moving
up the second of colormates and both lower cards are in play */
for (i=0; i<4; i++) {
int r = via->foundations[i]+1; /* next rank to go here */
if (r > 13) continue;
if (!(via->foundations[i^2] >= r && via->foundations[i^1]+2 < r
&& via->foundations[i^3]+2 < r)) continue;
moved = (i<<4) + r;
if (Available(via, moved)) {
moveSucc = TryMoveLoop(via, pos, moved, (Loc) FOUNDATION);
if (moveSucc) {
via = pos;
goto begin_label;
}
}
}
back_label:
via = via->via;
depth--;
newPos = false;
} while (depth > 0);
return false;
}
void main(int argc, char *argv[])
{
Position *pos0;
int i, reps = 1, make_random = false;
char *filename = NULL;
for (i=1; i<argc; i++) {
char *arg = argv[i];
if (*arg == '-') {
while (*++arg) switch (*arg) {
case 'i':
showorig = true;
break;
case 'm':
if (maxdepth > 99999) maxdepth = 300;
else maxdepth += 200;
break;
case 'r':
make_random = true;
break;
case 's':
show = true;
break;
case 'v':
if (verbosity++ < 3) break;
/* FALL THROUGH */
default:
printf("/
Usage: free_cell [-imrsvv] [file]/n/n/
-i: show initial layout/n/
-m: set max search depth of 300 (+200 per extra m)/n/
-r: if no 'file' arg, generate random winnable position/n/
else convert filename to int, generate and test that many positions/n/
-s: show solution if found (else just report whether one exists)/n/
-v: verbose; give some statistics when done/n/
-vv: very verbose; dump entire search tree as it is traversed/n/
-vvv: same as -vv but does fflush after each step/n");
exit(1);
}
}
else if (filename == NULL) filename = arg;
}
if (make_random) {
BuildOriginal();
if (filename != NULL) reps = atoi(filename);
if (reps <= 0) reps = 1;
}
else
ReadOriginal(filename);
tree = (Position **) calloc(HASH_SIZE, sizeof(Position*));
if (tree == NULL) {
printf("/nERROR: Out of memory!/n");
exit(2);
}
for (i=0; i<reps; i++) {
if (make_random) Shuffle();
pos0 = NewPosition();
InitialPosition(pos0);
pos0->mustTry = (-1);
if (showorig) ReadablePosition(pos0);
SortColumns(pos0);
(void) AddToTree(pos0);
if (1)
if (DFSLoop(pos0))
{
printf("Winnable./n");
}
else {
printf(distinct>=TRACTABLE? "Intractable./n"
: "Impossible./n");
if (make_random && filename==NULL) i--;
}
else
if (DFS(pos0, NewPosition()) == NULL) {
if (make_random && filename==NULL && !showorig)
ReadablePosition(pos0);
printf("Winnable./n");
}
else {
printf(distinct>=TRACTABLE? "Intractable./n"
: "Impossible./n");
if (make_random && filename==NULL) i--;
}
if (verbosity > 0) {
printf("/n");
if (windepth)
printf("Winning line of %d steps./n", windepth);
printf("/
Generated %d positions (%d distinct)./n/
Rejected %d due to repeated columns./n/
Pruned %d lines due to broken sequences./n/
Combined %d lines via pair-swapping./n",
generated, distinct, rejected, brokeSeq, swaps);
if (!windepth)
printf("Maximum freedom was %d./n", maxfree);
if (maxdepth < 100000) printf("/
Abandoned %d lines for exceeding %d moves./n", maxout, maxdepth);
else printf("/
Maximum search depth was %d./n", maxout);
printf("Used %d of %d hash table slots./n",
hashes, HASH_SIZE);
}
if (i < reps) {
/* clean up to prepare for next position */
generated = distinct = rejected = swaps = windepth =
maxout = maxfree = brokeSeq = hashes = depth = 0;
memset(tree, 0, HASH_SIZE * sizeof(Position*));
while (block->link != NULL) {
Block *link = block->link;
free(block);
block = link;
}
pos_avail = POS_BLOCK;
memset(block, 0, sizeof(*block));
fflush(stdout);
}
}
printf("done/n");
exit(0);
}
#endif