weissman%pianoforte.esd.sgi.com@SGI.COM (Terry Weissman) (02/13/91)
Submitted-by: Terry Weissman <weissman%pianoforte.esd.sgi.com@SGI.COM> Posting-number: Volume 11, Issue 61 Archive-name: seahaven/part01 #!/bin/sh # to extract, remove the header and type "sh filename" if `test ! -s ./README` then echo "writing ./README" cat > ./README << '\Rogue\Monster\' This is Seahaven Towers, a solitaire card game written for X11R4 and C++. If you don't have X11R4 and C++, you might as well give up now. One of the files (the one containing the bitmaps for the cards) is so huge that it won't conveniently fit in a 64K message. However, it compresses real well, so I compressed and uuencoded it before putting it in the shar file. (Don't worry, you're not missing much by not being able to read it directly; it's just a bunch of numbers.) So, after unsharing, you need to execute the following three commands: uudecode < cardbitmaps.uue uncompress cardbitmaps.C.Z rm cardbitmaps.uue After that, you should be able to just run xmkmf and make. (If you don't have imake, rename Makefile.noimake to be Makefile, and run make.) Of course, I can't guarantee that my C++ code is portable; I haven't been able to try it on a lot of platforms. But it shouldn't be too hard to fix if it doesn't compile. Terry Weissman weissman@sgi.com \Rogue\Monster\ else echo "will not over write ./README" fi if `test ! -s ./seahaven.6` then echo "writing ./seahaven.6" cat > ./seahaven.6 << '\Rogue\Monster\' .\"Man page for seahaven, by Terry Weissman. .TH seahaven 6 "07 Feb 1991" .SH NAME \fISeahaven Towers\fR - A solitaire game .SH SYNOPSIS .B seahaven [-display display:number] [-speedup num] .SH DESCRIPTION .I seahaven is an X implementation of a solitaire game sometimes known as .I Seahaven Towers, which I originally saw as a shareware game for the Macintosh. .I seahaven is a fairly blatent rip-off of that game. .SH RULES FOR SEAHAVEN TOWERS The game is played using an ordinary deck of cards. The cards are all face-up; you always know where all of the cards are. At any time, each card is in one of three kinds of stacks: - Playing stacks. There are ten of these, each initially having five cards. - Working stacks. There are four of these, two of them initially having a card in them. Each working stack is allowed to contain at most one card. - Ace stacks. There are four of these, one for each suit. They are initially empty. Cards must be placed in these stacks in ascending order, starting with the ace. The object of the game is to get all the cards in the ace stacks. The rules are simple. You may only move one card at a time; only a card in a working stack or on the top of a playing stack may be moved. A card may be moved to the top of a playing stack only if it is the same suit that was on top there and the next lower card. (In other words, you may only place the seven of spades on top of the eight of spades.) A card may be moved to any empty working stack. And a card may be moved to an ace stack if it is an ace or if it is the next higher card than the one that is already there. .SH PLAYING SEAHAVEN To move a card, just drag it with the left mouse button. When you let go, it will be placed on the stack that the card was moved closest to, if such a move is legal. If the move is not legal, the card will spring back to its original location. Since it is always to your advantage to move cards to the ace stacks as soon as possible, cards will be automatically moved there for you. There is also a convenient shortcut: you may move several cards at once from one playing stack to another, providing that such a move would be possible using available empty work stacks. To help you locate cards, if you press the middle button on a card, the next lower card of the same suit will be highlighted. If you press the right button, the next higher card will be highlighted. Since there is no hidden information in the game, it's not quite cheating to provide undo commands. There are Undo and Redo buttons at the bottom of the window; you may also use the U and R keys. The Restart button will restore you back to the original set-up. If you give up, you can press the Autoplay button. This counts as a loss (unless you've already won the hand). The computer will figure out whether there's a solution. If there is, you can review the computer's solution by using the Restart and Redo buttons. .SH SCORING If you get all the cards into the ace piles, you will be scored a win. If you press the New Game button or the Autoplay button without having won, you will be scored a loss. Your wins and losses will be remembered across invocations of .I seahaven. .SH COMMAND LINE OPTIONS The -speedup flag changes the speed of the animation. The higher the number, the faster cards will move when automatically transferred to the ace piles. The default value is 6. .SH BUGS Needs an icon. Does not look at any resources at all. That is, your .Xdefaults file will be ignored. Lots of the code is horrid. This man page is poorly written. .SH COPYRIGHT Copyright 1991 by Terry Weissman and Charles Haynes. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the names of Terry Weissman or Charles Haynes or their employers not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. Terry Weissman and Charles Haynes make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. TERRY WEISSMAN AND CHARLES HAYNES AND THEIR EMPLOYERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THEY BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. So there. .SH AUTHORS Terry Weissman .br Silicon Graphics, Incorporated .br weissman@sgi.com Auto-play code by Charles Haynes .br Western Software Laboratory .br Digital Equipment Corporation .br haynes@wsl.dec.com \Rogue\Monster\ else echo "will not over write ./seahaven.6" fi if `test ! -s ./Imakefile` then echo "writing ./Imakefile" cat > ./Imakefile << '\Rogue\Monster\' OBJS = main.o util.o card.o cardbitmaps.o stack.o score.o auto.o SRCS = main.C util.C card.C cardbitmaps.C stack.C score.C auto.C LDLIBS = $(XLIB) /* Make C++ work correctly. This works for AT&T cfront; it will need to be */ /* tweaked to work with g++. */ CPLUSPLUS = CC CC = $(CPLUSPLUS) /* So that linking works right */ INCLUDES = -I/usr/include/CC /* So that make depend works right */ .SUFFIXES: $(.SUFFIXES) .C .C.o: $*.C $(CPLUSPLUS) -c $(CFLAGS) $*.C ComplexProgramTarget(seahaven) \Rogue\Monster\ else echo "will not over write ./Imakefile" fi if `test ! -s ./Makefile.noimake` then echo "writing ./Makefile.noimake" cat > ./Makefile.noimake << '\Rogue\Monster\' # This is a simple Makefile that should work if you can't get imake to work. INCLUDES = LIBX = -lX11 DEFINES = CPLUSPLUS = CC OBJS = main.o util.o card.o cardbitmaps.o stack.o score.o auto.o .SUFFIXES: $(.SUFFIXES) .C .C.o: $*.C $(CPLUSPLUS) -c -g $(INCLUDES) $(DEFINES) $*.C all: cleanup seahaven cleanup: -rm -f *.c seahaven: $(OBJS) rm -f seahaven $(CPLUSPLUS) -o seahaven $(OBJS) $(LIBX) \Rogue\Monster\ else echo "will not over write ./Makefile.noimake" fi if `test ! -s ./auto.C` then echo "writing ./auto.C" cat > ./auto.C << '\Rogue\Monster\' #include <stream.h> #include <strings.h> #include <stdlib.h> #include <sys/types.h> #include <sys/time.h> #include <fcntl.h> //#include <osfcn.h> //#include <stdlib.h> // for getenv() #include "auto.h" #undef BATCH #undef INTERACTIVE #undef VERBOSE #undef DYNAMIC #undef AUTO #define AUTO #ifdef BATCH int solved_count = 0; int failed_count = 0; int solved_moves = 0; int solved_moves_max = 0; int failed_moves = 0; int failed_moves_max = 0; int solved_time = 0; int failed_time = 0; #endif struct timezone tz; struct timeval start_time; struct timeval end_time; typedef int Boolean; typedef enum Suit { SuitFirst, Hearts=SuitFirst, Diamonds, Clubs, Spades, SuitLast=Spades } Suit; const int NumSuits = SuitLast+1; typedef enum Rank { RankFirst, Ace=RankFirst, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King, RankLast=King } Rank; const int NumRanks = RankLast+1; void Error(char *s1 = "", char *s2 = "", char *s3 = "") { cerr << "\n" << s1 << s2 << s3 << "\n"; exit(-1); } class Tableaux; inline unsigned short Fold(long l) { return ((l>>16)^(l&0xffff)); } class Hash { long l1; long l2; long l3; public: Hash(Tableaux&); Hash() { l1 = l2 = l3 = 0; } Hash(int i) { l1 = l2 = l3 = i; } // semi-private Boolean operator== (Hash& h) { return ((l1 == h.l1) && (l2 == h.l2) && (l3 == h.l3)); } unsigned short Short() { return Fold(l1)^Fold(l2)^Fold(l3); } Boolean EmptyP() { return (l1 == 0) && (l2 == 0) && (l3 == 0); } Boolean BadP() { return (l3 < 0); } void Print(ostream &os) { os << hex(l1, 8) << hex(l2, 8) << hex(l3,8); } }; ostream& operator<< (ostream &os, Hash& h) { h.Print(os); return os; } class HashList; class HashTable { int size; int count; int hits; int maxDepth; HashList *ht; public: HashTable(int n); ~HashTable(); int Count() { return count; } void Clear(); Boolean CheckAndAdd(Hash& h); /* { const unsigned short s = h.Short(); Boolean ret = ht[s].InP(h); if (!ret) { int depth = ht[s].Add(h); if (depth > maxDepth) maxDepth = depth; if (depth) hits++; count++; // if (count%1000 == 0) PrintStats(); #ifdef INTERACTIVE if (count%1000 == 0) cerr << "."; #endif } return ret; } */ void Print(ostream &os); void PrintStats(ostream& os) { #ifdef INTERACTIVE os << "\nHash table stats - " << "size " << size << " count " << count << " hits " << hits << " max depth " << maxDepth; #endif } }; class HashList { HashList *next; Hash hash; int depth; friend void HashTable::Clear(); public: HashList(Hash& h, HashList* n) { hash = h; next = n; depth++; } HashList() { hash = Hash(); next = NULL; depth = 0; } ~HashList(); Boolean InP(Hash& h); int Add(Hash &h); // returns depth Hash HashVal() { return hash; } int Depth() { return depth; } void Print(ostream& os); Boolean EmptyP() { return hash.EmptyP(); } }; HashTable::HashTable(int n) { ht = new HashList[n]; size = n; count = maxDepth = hits = 0; } HashTable::~HashTable() { delete[size] ht; } HashList::~HashList() { if (next) delete next; } void HashList::Print(ostream &os) { if (EmptyP()) os << "NULL"; else { char *sep = ""; for (HashList* hlt = this; hlt != NULL; hlt = hlt->next) { os << sep << hlt->hash; sep = " "; } } } ostream& operator<< (ostream &os, HashList& hl) { hl.Print(os); return os; } Boolean HashList::InP(Hash& h) { for (HashList *hlt = this; hlt != NULL; hlt = hlt->next) { if (hlt->HashVal() == h) return 1; } return 0; } int HashList::Add(Hash& h) { if (!this) Error("Add to NULL HashList."); if (HashVal().EmptyP()) hash = h; else next = new HashList(h, next); return depth++; } ostream& operator<< (ostream& os, HashTable& ht) { ht.Print(os); return os; } void HashTable::Clear() { for (int i=0; i<size; i++) { if (ht[i].next) delete ht[i].next; ht[i] = HashList(); } count = maxDepth = hits = 0; } Boolean HashTable::CheckAndAdd(Hash& h) { const unsigned short s = h.Short(); Boolean ret = ht[s].InP(h); if (!ret) { int depth = ht[s].Add(h); if (depth > maxDepth) maxDepth = depth; if (depth) hits++; count++; #ifdef INTERACTIVE if (count%1000 == 0) cerr << "."; #endif } return ret; } void HashTable::Print(ostream &os) { HashList* hll = &ht[size]; for (HashList* hlt = ht; hlt != hll; hlt++) if (! hlt->EmptyP()) os << "\n" << dec(hlt-ht,5) << ": " << *hlt; } static HashTable hashTable(65536); // typedef unsigned char CardVal; typedef int CardVal; class Card; class CardRange; class Dest { public: void MoveTo(Card&); void MoveTo(CardRange&); }; void Dest::MoveTo(Card&) { Error("Dest::MoveTo(Card&)"); } void Dest::MoveTo(CardRange&) { Error("Dest::MoveTo(CardRange&)"); } typedef void (Dest::* MoveCardToMemberFunction)(Card&); typedef void (Dest::* MoveCardRangeToMemberFunction)(CardRange&); class Card : public Dest { Suit thisSuit; Rank thisRank; Boolean empty; public: Card(Suit suit, Rank rank) { if ((suit < SuitFirst) || (suit > SuitLast)) Error("Bad suit ", dec(suit), " in Card constructor."); if ((rank < RankFirst) || (rank > RankLast)) Error("Bad rank ", dec(rank), " in Card constructor."); thisSuit = suit; thisRank = rank; empty = 0; } Card(char *st); Card() { // create an "empty" card thisSuit = SuitFirst; thisRank = RankFirst; empty = 1; } Suit suit() const { return thisSuit; } Rank rank() const { return thisRank; } Card Next(int delta = 1) const { return Card(suit(), Rank(rank()+delta)); } Card Prev(int delta = 1) const { return Card(suit(), Rank(rank()-delta)); } Boolean NextP(Card c) const { return ((suit() == c.suit()) && rank()+1 == c.rank()); } Boolean operator== (Card &c) { return (EmptyP() ? c.EmptyP() : ((! c.EmptyP()) && (suit() == c.suit()) && (rank() == c.rank()))); } Boolean operator!= (Card &c) { return (! operator==(c)); } Boolean EmptyP() const { return empty; } CardVal Value() const { return thisSuit*NumRanks+thisRank; }//semi-private. void Print(ostream& os) const; }; static char *rankName = "A23456789TJQK"; static char *suitName = "HDCS"; void Card::Print(ostream &os) const { if (EmptyP()) os << "--"; else os << chr(rankName[rank()]) << chr(suitName[suit()]); } Card::Card(char* st) { char *c; c = index(suitName, st[1]); if (! c) Error("Bad suit ", chr(st[1]), " in Card string constructor."); thisSuit = Suit(c-suitName); c = index(rankName, st[0]); if (! c) Error("Bad rank ", chr(st[0]), " in Card string constructor."); thisRank = Rank(c - rankName); empty = 0; } ostream& operator<< (ostream& os, Card c) { c.Print(os); return os; } typedef Card Deck[NumSuits*NumRanks]; // typedef unsigned char Count; typedef int Count; class CardRange : public Dest { Card thisCard; Count thisCount; void Init(Card c, Count count) { thisCard = c; thisCount = count; } public: CardRange(Suit s, Rank rank1, Rank rank2) { if (rank2 < rank1) Error("rank2 < rank1 in CardRange constructor."); Init(Card(s, rank1), rank2-rank1+1); } CardRange(Suit s, Rank rank, Count count = 1) { Init(Card(s, rank), count); } CardRange(Card c, Count count = 1) { Init(c, count); } CardRange() { Init(Card(), 0); } // CardRange(char *st) { Init(Card(st), 1); } Suit suit() const { return thisCard.suit(); } Card First() const { return thisCard; } Card Last() const { return thisCard.Next(thisCount-1); } Card Next() const { return thisCard.Next(thisCount); } Count Count() const { return thisCount; } void Prepend(const CardRange& cr) { if (!cr.NextP(First())) Error("CardRange::Prepend, not next."); thisCount += cr.Count(); thisCard = cr.First(); } Boolean NextP(Card c) const { return ((suit() == c.suit()) && (First().rank()+Count() == c.rank())); } Boolean EmptyP() const { return thisCard.EmptyP(); } void Print(ostream& os) const; }; void CardRange::Print(ostream &os) const { if (EmptyP()) os << "-- "; else { os << First(); if (Count() == 1) os << " "; else os << "-" << Last(); } } ostream& operator<< (ostream& os, CardRange c) { c.Print(os); return os; } #ifdef AUTO SolutionLog solutionhead = NULL; overload LogSolution(); static void LogOneSolution(Card card, int dest) { SolutionLog temp = new SolutionLogRec; temp->rank = card.rank(); temp->suit = card.suit(); temp->dest = dest; temp->next = solutionhead; solutionhead = temp; } static void LogBoundary() { SolutionLog temp = new SolutionLogRec; temp->rank = 0; temp->suit = 0; temp->dest = -999; temp->next = solutionhead; solutionhead = temp; } static void LogSolution(Card card, int dest) { LogBoundary(); LogOneSolution(card, dest); } static void LogSolution(CardRange range, int dest) { if (range.EmptyP()) return; LogBoundary(); Suit suit = range.suit(); Rank first = range.First().rank(); Rank last = range.Last().rank(); Rank i; if (dest != -99) { for (i=first ; i<last ; i++) LogOneSolution(Card(suit, i), dest); } LogOneSolution(Card(suit, last), dest); for (i=Rank(last-1) ; i>=first ; i--) LogOneSolution(Card(suit, i), -99); } #endif const int NumCardRangesInPile = 5; class Pile : public Dest { CardRange data[NumCardRangesInPile]; int ti; public: Pile() { ti = 4; } CardRange& operator[] (int i) { return data[i]; } void Set(int i, const CardRange cr) { data[i] = cr; } CardRange& top() { return data[ti]; } int topIndex() { return ti; } CardRange& pop() { return data[ti--]; } Boolean EmptyP() { return (ti < 0); } void Test(CardRange& from); void MoveToPile(CardRange& from) { top().Prepend(from); from = CardRange(); } void MoveToPile(Card& from) { top().Prepend(from); from = Card(); } void MoveCardRangeToPile(CardRange& from); void MoveTo(CardRange& from); void MoveTo(Card& from); }; void Pile::MoveTo(CardRange& from) { MoveToPile(from); } void Pile::MoveCardRangeToPile(CardRange& from) { MoveToPile(from); } void (Pile::* foo)(CardRange&) = &Pile::MoveCardRangeToPile; //MoveCardRangeToMemberFunction foo = &Pile::MoveCardRangeToPile; const int NumSpaces = 4; class Spaces : public Dest { Card data[NumSpaces]; public: Card& operator[] (int i) { return data[i]; } Card* FirstFree() { return (data[0].EmptyP() ? &data[0] : data[1].EmptyP() ? &data[1] : data[2].EmptyP() ? &data[2] : data[3].EmptyP() ? &data[3] : NULL); } void Spaces::MoveToSpace(CardRange& from) { // move all of the cards in range to spaces for (int i = 0 ; i < from.Count(); i++) { *FirstFree() = from.First().Next(i); } from = CardRange(); } void Spaces::MoveTo(CardRange& from); void Spaces::MoveTo(Card&); }; void Spaces::MoveTo(CardRange& from) { MoveToSpace(from); } void Spaces::MoveTo(Card&) { Error("Spaces::MoveTo(Card&)"); }; const int NumPiles = 10; class Aces : public Dest { Card data[NumSuits]; public: Card& operator[] (int i) { return data[i]; } void MoveToAce(Card& from) { data[from.suit()] = Card(from); from = Card(); } void MoveToAce(CardRange& from) { data[from.suit()] = from.Last(); from = CardRange(); } void MoveTo(Card& from) { MoveToAce(from); } void MoveTo(CardRange& from) { MoveToAce(from); } }; class Kings : public Dest { Card data[NumSuits]; public: Card& operator[] (int i) { return data[i]; } void MoveToKing(Card& from) { data[from.suit()] = Card(from); from = Card(); } void MoveToKing(CardRange& from) { data[from.suit()] = from.First(); from = CardRange(); } void MoveTo(Card& from) { MoveToKing(from); } void MoveTo(CardRange& from) { MoveToKing(from); } }; class Tableaux { private: Hash hashVal; void CanonicalForm(); public: Pile piles[NumPiles]; Spaces spaces; Aces aces; Kings kings; Boolean Solve(); Hash HashVal() { return hashVal; } Boolean WonP(); void Print(ostream& os); void Dump(); void Read(istream& is); Tableaux(Card *deck); Tableaux(char *st); Tableaux(istream& is) { Read(is); } Boolean CheckAces(Card &c) { return (((c.rank() == Ace) || (c.rank() == aces[c.suit()].Next().rank())) ? FillAces() : 0); } Boolean FillAces(); // move all cards to aces that can be }; class Move { protected: Dest& dest; public: Move(Dest& d) : dest(d) { } virtual void DoIt() { Error("Move::DoIt()"); }; }; class MoveCard : public Move { Card& from; MoveCardToMemberFunction mcmf; public: MoveCard(Card& f, Dest& d, MoveCardToMemberFunction mf) : (d), from(f), mcmf(mf) { } void MoveCard::DoIt() { dest.MoveTo(from); } // void MoveCard::DoIt() { dest.*mcmf(from); } }; class MoveCardRange : public Move { CardRange& from; public: MoveCardRange(CardRange& f, Dest& d) : (d), from(f) { } void DoIt() {dest.MoveTo(from);} }; ostream& operator<< (ostream& os, Tableaux &t) { t.Print(os); return os; } istream& operator>> (istream& is, Tableaux &t) { t.Read(is); return is; } void Tableaux::Dump() { Print(cout); } Hash::Hash(Tableaux& t) { // there are 5 possible tops, and 13 possible values for the number of // cards in the top "group". These two values uniquely determine what's in // that pile. Each pile starts with five groups, that number never grows. // You can only move a group by either removing it (to a space or an ace) // or moving it to the end of another group (which merely lengthens that // group). So the number of groups in a pile is monotone non-increasing. // An implication of this is that the *suit* of each position in the pile // stays fixed, and so is derivable from the initial position (i.e. adds // no information) thus we can ignore the suit in computing the hash. This // means that there are 66 possible hash values for each pile. 5*13 // possible top+count values + empty. // 66^5 < 2^32 so we can store the hash for 10 piles in two 32 bit numbers // We also need to store information about the kings. I'm lazy so I just // store it in one long. // Note: Hash 0 would imply all empty piles, which will never happen! l3 = (((((t.kings[0].Value() << 8) + t.kings[1].Value()) << 8) + t.kings[2].Value()) << 8) + t.kings[3].Value(); l1 = 0; Pile *p; for (p=t.piles; p < t.piles+5; p++) { const int count = p->top().Count(); if (count >= 6) { const Suit s = p->top().suit(); const Rank r = p->top().First().rank(); for (CardRange* cr = &(p->top()); cr >= &((*p)[0]); cr--) { if (cr->suit() != s) continue; if (cr->First().rank() < r) { l1 = l2 = l3 = -1; return; } } } l1 = l1*66 + (p->EmptyP() ? 0 : (p->topIndex()*13+count-1)+1); } l2 = 0; for (p = t.piles+5 ; p < t.piles+NumPiles; p++) { const int count = p->top().Count(); if (count >= 6) { const Suit s = p->top().suit(); const Rank r = p->top().First().rank(); for (CardRange* cr = &(p->top()); cr >= &((*p)[0]); cr--) { if (cr->suit() != s) continue; if (cr->First().rank() < r) { l1 = l2 = l3 = -1; return; } } } l2 = l2*66 + (p->EmptyP() ? 0 : (p->topIndex()*13+count-1)+1); } if ((l1==0)&&(l2==0)&&(l3==0)) Error("Tableaux hashes to NULL."); } Boolean Tableaux::WonP() { return ((aces[0].rank() == King) && (aces[1].rank() == King) && (aces[2].rank() == King) && (aces[3].rank() == King)); } Boolean Tableaux::FillAces() { int found; do { found = 0; int dests[NumSuits]; int numFull = 0; for (Suit s = SuitFirst; s <= SuitLast; s++) { if (aces[s].EmptyP()) dests[s] = Ace; else { numFull += (aces[s].rank() == King); dests[s] = aces[s].rank()+1; } } if (numFull == NumSuits) return 1; // check piles for (Pile* p = piles; p < piles+NumPiles; p++) { if (p->EmptyP()) continue; Card& first = p->top().First(); if (dests[first.suit()] == first.rank()) { aces.MoveToAce(p->pop()); found = 1; #ifdef VERBOSE cerr << "+"; #endif } } // check spaces for (Card *c = &spaces[0]; c < &spaces[NumSpaces]; c++) { if (c->EmptyP()) continue; if (dests[c->suit()] == c->rank()) { aces.MoveToAce(*c); found = 1; #ifdef VERBOSE cerr << "+"; #endif } } // check kings for (Suit s1 = SuitFirst; s1 <= SuitLast; s1++) { if (kings[s1].EmptyP()) continue; if (dests[s1] == kings[s1].rank()) { Card tempKing = Card(s1, King); aces.MoveToAce(tempKing); kings[s1] = Card(); found = 1; #ifdef VERBOSE cerr << "!"; #endif } } } while (found); return 0; } Boolean Tableaux::Solve() { hashVal = Hash(*this); if (hashTable.CheckAndAdd(HashVal())) return 0; Tableaux savedTab = *this; #ifdef VERBOSE cerr << "."; #endif #ifdef DYNAMIC cout << "[;H" << *this; #endif // pre-calculate useful bits of info - how many empty spaces are there // how many empty piles there are int empty_space_count = 0; Card *c; for (c = &spaces[0]; c < &spaces[NumSpaces]; c++) empty_space_count += c->EmptyP(); int empty_pile_count = 0; Pile* p; for (p = piles; p < piles+NumPiles; p++) empty_pile_count += p->EmptyP(); Suit s; for (s = SuitFirst; s <= SuitLast; s++) empty_pile_count -= !kings[s].EmptyP(); // generate legal moves, check each one // all legal moves are either // 1) an entire top sequence onto the top of a pile (shortcut 3+2) // 2) a card from a space onto the top of a pile // 3) an entire top sequence onto a space or spaces // 4) an entire king based sequence onto an empty pile (shortcut 3+5+2) // 5) a king from a space onto an empty pile // another way of looking at it is that all moves are onto one of // tops of piles // empty piles (kings) // spaces int destTable[NumSuits*NumRanks]; memset((char*)destTable,0,sizeof(destTable)); // enumerate destination piles for (p = piles; p < piles+NumPiles; p++) { if (p->EmptyP()) continue; destTable[p->top().First().Prev().Value()] = (p-piles)+1; } // enumerate destination kings for (s = SuitFirst; s <= SuitLast; s++) { if (kings[s].EmptyP()) { if (empty_pile_count) destTable[Card(s,King).Value()] = s+11; } else { destTable[kings[s].Prev().Value()] = s+11; } } Move* movesMakingEmptyPiles[10]; int movesMakingEmptyPilesCount = 0; Move* movesMakingUnmovableRange[10]; int movesMakingUnMovableRange= 0; // look for moves from spaces for (Card *from = &spaces[0]; from < &spaces[NumSpaces]; from++) { if (from->EmptyP()) continue; const int dest = destTable[from->Value()]; if (!dest) continue; if (dest < 11) piles[dest-1].MoveToPile(*from); else kings.MoveToKing(*from); if (Solve()) { *this = savedTab; #ifdef AUTO LogSolution(*from, dest); #endif #ifdef INTERACTIVE cout << "\n" << *from << "(S) -> "; if (dest < 11) cout << piles[dest-1].top(); else cout << "(K)" << kings[dest-11]; #endif return 1; } else { *this = savedTab; if (dest < 11) return 0; // It never costs to move something from // a space to a pile (unless it's a king) } } // look for moves from piles for (p = piles; p < piles+NumPiles; p++) { if (p->EmptyP()) continue; CardRange& from = p->top(); if (from.Count()-1 > empty_space_count) continue; int const dest = destTable[from.Last().Value()]; if (!dest) continue; // legal move! /* if (p->topIndex() == 0) { if (dest < 11) { movesMakingEmptyPiles[movesMakingEmptyPilesCount++] = new Move(); } else { } } else { */ if (dest < 11) { piles[dest-1].MoveToPile(p->pop()); // did we uncover a king? if ((p->topIndex() == 0) && (p->top().Last().rank() == King)) kings.MoveToKing(p->pop()); } else { kings.MoveToKing(p->pop()); } /* } */ if ((!p->EmptyP()) && CheckAces(p->top().First()) || Solve()) { #ifdef AUTO *this = savedTab; LogSolution(from, dest); #endif #ifdef INTERACTIVE *this = savedTab; cout << "\n" << from << " -> "; if (dest < 11) cout << piles[dest-1].top(); else cout << "(K)" << kings[dest-11]; #endif return 1; } *this = savedTab; } // look for moves into spaces for (p = piles; p < piles+NumPiles; p++) { if (p->EmptyP()) continue; CardRange& tp = p->top(); if (tp.Count() > empty_space_count) continue; spaces.MoveToSpace(p->pop()); if (((!p->EmptyP()) && CheckAces(p->top().First())) || Solve()) { #ifdef AUTO *this = savedTab; LogSolution(tp, -99); #endif #ifdef INTERACTIVE *this = savedTab; cout << "\n" << tp << " -> space"; #endif return 1; } *this = savedTab; } return 0; }; void Tableaux::CanonicalForm() { // find all sequences and compress them for (int i = 0; i < NumPiles; i++) { Pile& p = piles[i]; int k = 0; for (int j = 1; j < NumCardRangesInPile; j++) if (p[j].NextP(p[k].Last())) p[k].Prepend(p[j].First()); else p.Set(++k, p[j]); for (k++; k < NumCardRangesInPile; k++) { p.Set(k,CardRange()); p.pop(); } } /* for (Pile* p = piles; p < piles+NumPiles; p++) { CardRange *nextPos = &(*p)[0]; for (CardRange *c = &(*p)[1]; c < &(*p)[NumCardRangesInPile]; c++) { if (c->NextP(nextPos->Last())) nextPos->Prepend(*c); else *(++nextPos) = *c; } for (nextPos++; nextPos < &(*p)[NumCardRangesInPile]; nextPos++) *nextPos = CardRange(); } */ FillAces(); } Tableaux::Tableaux(char* st) { static int deck[NumSuits][NumRanks]; Card c; c = st; deck[c.suit()][c.rank()]++; spaces[1] = c; st += 2; c = st; deck[c.suit()][c.rank()]++; spaces[2] = c; st += 2; for (Pile* p = piles; p < piles+NumPiles; p++) for (CardRange* cr = &(*p)[0]; cr < &(*p)[NumCardRangesInPile]; cr++) { c = st; deck[c.suit()][c.rank()]++; *cr = c; st += 2; } for (Suit s = SuitFirst; s <= SuitLast; s++) for (Rank r = RankFirst; r <= RankLast; r++) if (deck[s][r] != 1) { cerr << "\n" << Card(s, r) << " count is " << deck[s][r]; Error(); } CanonicalForm(); } void Tableaux::Print(ostream& os) { os << "\n"; os << aces[0] << " " << aces[1] << " " << spaces[0] << " " << spaces[1] << " " << spaces[2] << " " << spaces[3] << " " << aces[2] << " " << aces[3]; os << "\n"; for (int j = 0; j < NumCardRangesInPile; j++) { char *sep = "\n"; for (int i = 0; i < NumPiles; i++) { os << sep << piles[i][j]; sep = " "; } } os << "\n"; os << kings[0] << " " << kings[1] << " " << kings[2] << " " << kings[3]; os << "\n"; } void Tableaux::Read(istream& is) { int won, lost, streak, longest_won, longest_lost, dummy; is >> won >> lost >> streak >> longest_won >> longest_lost >> dummy; for (int i = 0; i < NumCardRangesInPile; i++) for (int j = 0; j < NumPiles; j++) { int s, r; is >> s >> r; piles[j].Set(i, CardRange(Card(Suit(s),Rank(r)))); } int s, r; is >> s >> r; spaces[1] = Card(Suit(s),Rank(r)); is >> s >> r; spaces[2] = Card(Suit(s),Rank(r)); CanonicalForm(); } Tableaux::Tableaux(Card *deck) { for (int i = 0; i < NumCardRangesInPile; i++) for (int j = 0; j < NumPiles; j++) { piles[j].Set(i, CardRange(*(deck++))); } spaces[1] = *(deck++); spaces[2] = *(deck++); CanonicalForm(); } #ifdef AUTO int AutoPlay() { char savefile[500]; sprintf(savefile, "%s/.seahavensave", getenv("HOME")); istream in = istream(open(savefile, O_RDONLY)); Tableaux t(in); solutionhead = NULL; int result = t.Solve(); hashTable.Clear(); return result; } #endif \Rogue\Monster\ else echo "will not over write ./auto.C" fi echo "Finished archive 1 of 2" exit -- Dan Heller ------------------------------------------------ O'Reilly && Associates Zyrcom Inc Senior Writer President argv@ora.com argv@zipcode.com