summaryrefslogtreecommitdiffstats
path: root/kpat/freecell.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kpat/freecell.cpp')
-rw-r--r--kpat/freecell.cpp854
1 files changed, 854 insertions, 0 deletions
diff --git a/kpat/freecell.cpp b/kpat/freecell.cpp
new file mode 100644
index 00000000..7a72c1fc
--- /dev/null
+++ b/kpat/freecell.cpp
@@ -0,0 +1,854 @@
+/*---------------------------------------------------------------------------
+
+ freecell.cpp implements a patience card game
+
+ Copyright (C) 1997 Rodolfo Borges
+ (C) 2000 Stephan Kulow
+
+ * 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.
+ *
+ * This file is provided AS IS with no warranties of any kind. The author
+ * shall have no liability with respect to the infringement of copyrights,
+ * trade secrets or any patents by this file or any part thereof. In no
+ * event will the author be liable for any lost revenue or profits or
+ * other special, indirect and consequential damages.
+
+---------------------------------------------------------------------------*/
+
+#include "freecell.h"
+#include <klocale.h>
+#include "deck.h"
+#include <assert.h>
+#include <kdebug.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <qtimer.h>
+#include "cardmaps.h"
+
+#include "freecell-solver/fcs_user.h"
+#include "freecell-solver/fcs_cl.h"
+
+const int CHUNKSIZE = 100;
+
+void FreecellPile::moveCards(CardList &c, Pile *to)
+{
+ if (c.count() == 1) {
+ Pile::moveCards(c, to);
+ return;
+ }
+ FreecellBase *b = dynamic_cast<FreecellBase*>(dealer());
+ if (b) {
+ b->moveCards(c, this, to);
+ }
+}
+
+//-------------------------------------------------------------------------//
+
+FreecellBase::FreecellBase( int decks, int stores, int freecells, int fill, bool unlimit,
+ KMainWindow* parent, const char* name)
+ : Dealer(parent,name),
+solver_instance(0), es_filling(fill), solver_ret(FCS_STATE_NOT_BEGAN_YET),
+unlimited_move(unlimit)
+{
+ deck = Deck::new_deck(this, decks);
+ deck->hide();
+
+ kdDebug(11111) << "cards " << deck->cards().count() << endl;
+ Pile *t;
+ for (int i = 0; i < stores; i++) {
+ FreecellPile *p = new FreecellPile(1 + i, this);
+ store.append(p);
+ p->setAddFlags(Pile::addSpread | Pile::several);
+ p->setRemoveFlags(Pile::several);
+ p->setCheckIndex(0);
+ }
+
+ for (int i = 0; i < freecells; i++)
+ {
+ t = new Pile (1 + stores +i, this);
+ freecell.append(t);
+ t->setType(Pile::FreeCell);
+ }
+
+ for (int i = 0; i < decks * 4; i++)
+ {
+ t = new Pile(1 + stores + freecells +i, this);
+ target.append(t);
+ t->setType(Pile::KlondikeTarget);
+ // COOLO: I'm still not too sure about that t->setRemoveFlags(Pile::Default);
+ }
+
+ setActions(Dealer::Demo | Dealer::Hint);
+}
+
+FreecellBase::~FreecellBase()
+{
+ if (solver_instance)
+ {
+ freecell_solver_user_free(solver_instance);
+ solver_instance = NULL;
+ }
+}
+//-------------------------------------------------------------------------//
+
+void FreecellBase::restart()
+{
+ freeSolution();
+ deck->collectAndShuffle();
+ deal();
+}
+
+QString suitToString(Card::Suit s) {
+ switch (s) {
+ case Card::Clubs:
+ return "C";
+ case Card::Hearts:
+ return "H";
+ case Card::Diamonds:
+ return "D";
+ case Card::Spades:
+ return "S";
+ }
+ return QString::null;
+}
+
+QString rankToString(Card::Rank r)
+{
+ switch (r) {
+ case Card::King:
+ return "K";
+ case Card::Ace:
+ return "A";
+ case Card::Jack:
+ return "J";
+ case Card::Queen:
+ return "Q";
+ default:
+ return QString::number(r);
+ }
+}
+
+int getDeck(Card::Suit suit)
+{
+ switch (suit) {
+ case Card::Hearts:
+ return 0;
+ case Card::Spades:
+ return 1;
+ case Card::Diamonds:
+ return 2;
+ case Card::Clubs:
+ return 3;
+ }
+ return 0;
+}
+
+static const char * freecell_solver_cmd_line_args[280] =
+{
+"--method", "soft-dfs", "-to", "0123456789", "-step",
+"500", "--st-name", "1", "-nst", "--method",
+"soft-dfs", "-to", "0123467", "-step", "500",
+"--st-name", "2", "-nst", "--method", "random-dfs",
+"-seed", "2", "-to", "0[01][23456789]", "-step",
+"500", "--st-name", "3", "-nst", "--method",
+"random-dfs", "-seed", "1", "-to", "0[0123456789]",
+"-step", "500", "--st-name", "4", "-nst", "--method",
+"random-dfs", "-seed", "3", "-to", "0[01][23467]",
+"-step", "500", "--st-name", "5", "-nst", "--method",
+"random-dfs", "-seed", "4", "-to", "0[0123467]",
+"-step", "500", "--st-name", "9", "-nst", "--method",
+"random-dfs", "-to", "[01][23456789]", "-seed", "8",
+"-step", "500", "--st-name", "10", "-nst",
+"--method", "random-dfs", "-to", "[01][23456789]",
+"-seed", "268", "-step", "500", "--st-name", "12",
+"-nst", "--method", "a-star", "-asw",
+"0.2,0.3,0.5,0,0", "-step", "500", "--st-name", "16",
+"-nst", "--method", "a-star", "-to", "0123467",
+"-asw", "0.5,0,0.3,0,0", "-step", "500", "--st-name",
+"18", "-nst", "--method", "soft-dfs", "-to",
+"0126394875", "-step", "500", "--st-name", "19",
+"--prelude",
+"350@2,350@5,350@9,350@12,350@2,350@10,350@3,350@9,350@5,350@18,350@2,350@5,350@4,350@10,350@4,350@12,1050@9,700@18,350@10,350@5,350@2,350@10,1050@16,350@2,700@4,350@10,1050@2,1400@3,350@18,1750@5,350@16,350@18,700@4,1050@12,2450@5,1400@18,1050@2,1400@10,6300@1,4900@12,8050@18",
+"-ni", "--method", "soft-dfs", "-to", "01ABCDE",
+"-step", "500", "--st-name", "0", "-nst", "--method",
+"random-dfs", "-to", "[01][ABCDE]", "-seed", "1",
+"-step", "500", "--st-name", "1", "-nst", "--method",
+"random-dfs", "-to", "[01][ABCDE]", "-seed", "2",
+"-step", "500", "--st-name", "2", "-nst", "--method",
+"random-dfs", "-to", "[01][ABCDE]", "-seed", "3",
+"-step", "500", "--st-name", "3", "-nst", "--method",
+"random-dfs", "-to", "01[ABCDE]", "-seed", "268",
+"-step", "500", "--st-name", "4", "-nst", "--method",
+"a-star", "-to", "01ABCDE", "-step", "500",
+"--st-name", "5", "-nst", "--method", "a-star",
+"-to", "01ABCDE", "-asw", "0.2,0.3,0.5,0,0", "-step",
+"500", "--st-name", "6", "-nst", "--method",
+"a-star", "-to", "01ABCDE", "-asw", "0.5,0,0.5,0,0",
+"-step", "500", "--st-name", "7", "-nst", "--method",
+"random-dfs", "-to", "[01][ABD][CE]", "-seed", "1900",
+"-step", "500", "--st-name", "8", "-nst", "--method",
+"random-dfs", "-to", "[01][ABCDE]", "-seed", "192",
+"-step", "500", "--st-name", "9", "-nst", "--method",
+"random-dfs", "-to", "[01ABCDE]", "-seed", "1977",
+"-step", "500", "--st-name", "10", "-nst",
+"--method", "random-dfs", "-to", "[01ABCDE]", "-seed",
+"24", "-step", "500", "--st-name", "11", "-nst",
+"--method", "soft-dfs", "-to", "01ABDCE", "-step",
+"500", "--st-name", "12", "-nst", "--method",
+"soft-dfs", "-to", "ABC01DE", "-step", "500",
+"--st-name", "13", "-nst", "--method", "soft-dfs",
+"-to", "01EABCD", "-step", "500", "--st-name", "14",
+"-nst", "--method", "soft-dfs", "-to", "01BDAEC",
+"-step", "500", "--st-name", "15", "--prelude",
+"1000@0,1000@3,1000@0,1000@9,1000@4,1000@9,1000@3,1000@4,2000@2,1000@0,2000@1,1000@14,2000@11,1000@14,1000@3,1000@11,1000@2,1000@0,2000@4,2000@10,1000@0,1000@2,2000@10,1000@0,2000@11,2000@1,1000@10,1000@2,1000@10,2000@0,1000@9,1000@1,1000@2,1000@14,3000@8,1000@2,1000@14,1000@1,1000@10,3000@6,2000@4,1000@2,2000@0,1000@2,1000@11,2000@6,1000@0,5000@1,1000@0,2000@1,1000@2,3000@3,1000@10,1000@14,2000@6,1000@0,1000@2,2000@11,6000@8,8000@9,3000@1,2000@10,2000@14,3000@15,4000@0,1000@8,1000@10,1000@14,7000@0,14000@2,6000@3,7000@4,1000@8,4000@9,2000@15,2000@6,4000@3,2000@4,3000@15,2000@0,6000@1,2000@4,4000@6,4000@9,4000@14,7000@8,3000@0,3000@1,5000@2,3000@3,4000@9,8000@10,9000@3,5000@8,7000@11,11000@12,12000@0,8000@3,11000@9,9000@15,7000@2,12000@8,16000@5,8000@13,18000@0,9000@15,12000@10,16000@0,14000@3,16000@9,26000@4,23000@3,42000@6,22000@8,27000@10,38000@7,41000@0,42000@3,84000@13,61000@15,159000@5,90000@9"
+};
+
+void FreecellBase::findSolution()
+{
+ kdDebug(11111) << "findSolution\n";
+
+ QString output = solverFormat();
+ kdDebug(11111) << output << endl;
+
+ int ret;
+
+ /* If solver_instance was not initialized yet - initialize it */
+ if (! solver_instance)
+ {
+ solver_instance = freecell_solver_user_alloc();
+
+ char * error_string;
+ int error_arg;
+ char * known_parameters[1] = {NULL};
+
+
+ ret = freecell_solver_user_cmd_line_parse_args(
+ solver_instance,
+ sizeof(freecell_solver_cmd_line_args)/sizeof(freecell_solver_cmd_line_args[0]),
+ freecell_solver_cmd_line_args,
+ 0,
+ known_parameters,
+ NULL,
+ NULL,
+ &error_string,
+ &error_arg
+ );
+
+
+ assert(!ret);
+ }
+ /*
+ * I'm using the more standard interface instead of the depracated
+ * user_set_game one. I'd like that each function will have its
+ * own dedicated purpose.
+ *
+ * Shlomi Fish
+ * */
+#if 0
+ ret = freecell_solver_user_set_game(solver_instance,
+ freecell.count(),
+ store.count(),
+ deck->decksNum(),
+ FCS_SEQ_BUILT_BY_ALTERNATE_COLOR,
+ unlimited_move,
+ es_filling);
+ assert(!ret);
+#else
+ freecell_solver_user_set_num_freecells(solver_instance,freecell.count());
+ freecell_solver_user_set_num_stacks(solver_instance,store.count());
+ freecell_solver_user_set_num_decks(solver_instance,deck->decksNum());
+ freecell_solver_user_set_sequences_are_built_by_type(solver_instance, FCS_SEQ_BUILT_BY_ALTERNATE_COLOR);
+ freecell_solver_user_set_sequence_move(solver_instance, unlimited_move);
+ freecell_solver_user_set_empty_stacks_filled_by(solver_instance, es_filling);
+
+#endif
+
+ freecell_solver_user_limit_iterations(solver_instance, CHUNKSIZE);
+
+ solver_ret = freecell_solver_user_solve_board(solver_instance,
+ output.latin1());
+ resumeSolution();
+}
+
+void FreecellBase::resumeSolution()
+{
+ if (!solver_instance)
+ return;
+
+ emit gameInfo(i18n("%1 tries - depth %2")
+ .arg(freecell_solver_user_get_num_times(solver_instance))
+ .arg(freecell_solver_user_get_current_depth(solver_instance)));
+
+ if (solver_ret == FCS_STATE_WAS_SOLVED)
+ {
+ emit gameInfo(i18n("solved after %1 tries").
+ arg(freecell_solver_user_get_num_times(
+ solver_instance)));
+ kdDebug(11111) << "solved\n";
+ Dealer::demo();
+ return;
+ }
+ if (solver_ret == FCS_STATE_IS_NOT_SOLVEABLE) {
+ int moves = freecell_solver_user_get_num_times(solver_instance);
+ freeSolution();
+ emit gameInfo(i18n("unsolved after %1 moves")
+ .arg(moves));
+ stopDemo();
+ return;
+ }
+
+ unsigned int max_iters = freecell_solver_user_get_limit_iterations(
+ solver_instance) + CHUNKSIZE;
+ freecell_solver_user_limit_iterations(solver_instance,
+ max_iters);
+
+ if (max_iters > 120000) {
+ solver_ret = FCS_STATE_IS_NOT_SOLVEABLE;
+ resumeSolution();
+ return;
+ }
+
+ solver_ret = freecell_solver_user_resume_solution(solver_instance);
+ QTimer::singleShot(0, this, SLOT(resumeSolution()));
+
+}
+MoveHint *FreecellBase::translateMove(void *m) {
+ fcs_move_t move = *(static_cast<fcs_move_t *>(m));
+ uint cards = fcs_move_get_num_cards_in_seq(move);
+ Pile *from = 0;
+ Pile *to = 0;
+
+ switch(fcs_move_get_type(move))
+ {
+ case FCS_MOVE_TYPE_STACK_TO_STACK:
+ from = store[fcs_move_get_src_stack(move)];
+ to = store[fcs_move_get_dest_stack(move)];
+ break;
+
+ case FCS_MOVE_TYPE_FREECELL_TO_STACK:
+ from = freecell[fcs_move_get_src_freecell(move)];
+ to = store[fcs_move_get_dest_stack(move)];
+ cards = 1;
+ break;
+
+ case FCS_MOVE_TYPE_FREECELL_TO_FREECELL:
+ from = freecell[fcs_move_get_src_freecell(move)];
+ to = freecell[fcs_move_get_dest_freecell(move)];
+ cards = 1;
+ break;
+
+ case FCS_MOVE_TYPE_STACK_TO_FREECELL:
+ from = store[fcs_move_get_src_stack(move)];
+ to = freecell[fcs_move_get_dest_freecell(move)];
+ cards = 1;
+ break;
+
+ case FCS_MOVE_TYPE_STACK_TO_FOUNDATION:
+ from = store[fcs_move_get_src_stack(move)];
+ cards = 1;
+ to = 0;
+ break;
+
+ case FCS_MOVE_TYPE_FREECELL_TO_FOUNDATION:
+ from = freecell[fcs_move_get_src_freecell(move)];
+ cards = 1;
+ to = 0;
+ }
+ assert(from);
+ assert(cards <= from->cards().count());
+ assert(to || cards == 1);
+ Card *c = from->cards()[from->cards().count() - cards];
+
+ if (!to)
+ to = findTarget(c);
+ assert(to);
+ return new MoveHint(c, to);
+}
+
+QString FreecellBase::solverFormat() const
+{
+ QString output;
+ QString tmp;
+ for (uint i = 0; i < target.count(); i++) {
+ if (target[i]->isEmpty())
+ continue;
+ tmp += suitToString(target[i]->top()->suit()) + "-" + rankToString(target[i]->top()->rank()) + " ";
+ }
+ if (!tmp.isEmpty())
+ output += QString::fromLatin1("Foundations: %1\n").arg(tmp);
+
+ tmp.truncate(0);
+ for (uint i = 0; i < freecell.count(); i++) {
+ if (freecell[i]->isEmpty())
+ tmp += "- ";
+ else
+ tmp += rankToString(freecell[i]->top()->rank()) + suitToString(freecell[i]->top()->suit()) + " ";
+ }
+ if (!tmp.isEmpty())
+ output += QString::fromLatin1("Freecells: %1\n").arg(tmp);
+
+ for (uint i = 0; i < store.count(); i++)
+ {
+ CardList cards = store[i]->cards();
+ for (CardList::ConstIterator it = cards.begin(); it != cards.end(); ++it)
+ output += rankToString((*it)->rank()) + suitToString((*it)->suit()) + " ";
+ output += "\n";
+ }
+ return output;
+}
+
+// Idea stolen from klondike.cpp
+//
+// This function returns true when it is certain that the card t is no longer
+// needed on any of the play piles.
+//
+// To determine wether a card is no longer needed on any of the play piles we
+// obviously must know what a card can be used for there. According to the
+// rules a card can be used to store another card with 1 less unit of value
+// and opposite color. This is the only thing that a card can be used for
+// there. Therefore the cards with lowest value (1) are useless there (base
+// case). The other cards each have 2 cards that can be stored on them, let us
+// call those 2 cards *depending cards*.
+//
+// The object of the game is to put all cards on the target piles. Therefore
+// cards that are no longer needed on any of the play piles should be put on
+// the target piles if possible. Cards on the target piles can not be moved
+// and they can not store any of its depending cards. Let us call this that
+// the cards on the target piles are *out of play*.
+//
+// The simple and obvious rule is:
+// A card is no longer needed when both of its depending cards are out of
+// play.
+//
+// More complex:
+// Assume card t is red. Now, if the lowest unplayed black card is
+// t.value()-2, then t may be needed to hold that black t.value()-1 card.
+// If the lowest unplayed black card is t.value()-1, it will be playable
+// to the target, unless it is needed for a red card of value t.value()-2.
+//
+// So, t is not needed if the lowest unplayed red card is t.value()-2 and the
+// lowest unplayed black card is t.value()-1, OR if the lowest unplayed black
+// card is t.value(). So, no recursion needed - we did it ahead of time.
+
+bool FreecellBase::noLongerNeeded(const Card & t)
+{
+
+ if (t.rank() <= Card::Two) return true; // Base case.
+
+ bool cardIsRed = t.isRed();
+
+ uint numSame = 0, numDiff = 0;
+ Card::Rank lowSame = Card::King, lowDiff = Card::King;
+ for (PileList::Iterator it = target.begin(); it != target.end(); ++it)
+ {
+ if ((*it)->isEmpty())
+ continue;
+ if ((*it)->top()->isRed() == cardIsRed) {
+ numSame++;
+ if ((*it)->top()->rank() < lowSame)
+ lowSame = static_cast<Card::Rank>((*it)->top()->rank()+1);
+ } else {
+ numDiff++;
+ if ((*it)->top()->rank() < lowDiff)
+ lowDiff = static_cast<Card::Rank>((*it)->top()->rank()+1);
+ }
+ }
+ if (numSame < target.count()/2) lowSame = Card::Ace;
+ if (numDiff < target.count()/2) lowDiff = Card::Ace;
+
+ return (lowDiff >= t.rank() ||
+ (lowDiff >= t.rank()-1 && lowSame >= t.rank()-2));
+}
+
+// This is the getHints() from dealer.cpp with one line changed
+// to use noLongerNeeded() to decide if the card should be
+// dropped or not.
+//
+// I would recommend adding a virtual bool noLongerNeeded(const Card &t)
+// to the base class (Dealer) that just returns true, and then calling
+// it like is done here. That would preserve current functionality
+// but eliminate this code duplication
+void FreecellBase::getHints()
+{
+ for (PileList::Iterator it = piles.begin(); it != piles.end(); ++it)
+ {
+ if (!takeTargetForHints() && (*it)->target())
+ continue;
+
+ Pile *store = *it;
+ if (store->isEmpty())
+ continue;
+// kdDebug(11111) << "trying " << store->top()->name() << endl;
+
+ CardList cards = store->cards();
+ while (cards.count() && !cards.first()->realFace()) cards.remove(cards.begin());
+
+ CardList::Iterator iti = cards.begin();
+ while (iti != cards.end())
+ {
+ if (store->legalRemove(*iti)) {
+// kdDebug(11111) << "could remove " << (*iti)->name() << endl;
+ for (PileList::Iterator pit = piles.begin(); pit != piles.end(); ++pit)
+ {
+ Pile *dest = *pit;
+ if (dest == store)
+ continue;
+ if (store->indexOf(*iti) == 0 && dest->isEmpty() && !dest->target())
+ continue;
+ if (!dest->legalAdd(cards))
+ continue;
+
+ bool old_prefer = checkPrefering( dest->checkIndex(), dest, cards );
+ if (!takeTargetForHints() && dest->target())
+ newHint(new MoveHint(*iti, dest, noLongerNeeded(*(*iti))));
+ else {
+ store->hideCards(cards);
+ // if it could be here as well, then it's no use
+ if ((store->isEmpty() && !dest->isEmpty()) || !store->legalAdd(cards))
+ newHint(new MoveHint(*iti, dest));
+ else {
+ if (old_prefer && !checkPrefering( store->checkIndex(),
+ store, cards ))
+ { // if checkPrefers says so, we add it nonetheless
+ newHint(new MoveHint(*iti, dest));
+ }
+ }
+ store->unhideCards(cards);
+ }
+ }
+ }
+ cards.remove(iti);
+ iti = cards.begin();
+ }
+ }
+}
+
+void FreecellBase::demo()
+{
+ if (solver_instance && solver_ret == FCS_STATE_WAS_SOLVED) {
+ Dealer::demo();
+ return;
+ }
+ towait = (Card*)-1;
+ unmarkAll();
+ kdDebug(11111) << "demo " << (solver_ret != FCS_STATE_IS_NOT_SOLVEABLE) << endl;
+ if (solver_ret != FCS_STATE_IS_NOT_SOLVEABLE)
+ findSolution();
+}
+
+MoveHint *FreecellBase::chooseHint()
+{
+ if (solver_instance && freecell_solver_user_get_moves_left(solver_instance)) {
+
+ emit gameInfo(i18n("%1 moves before finish").arg(freecell_solver_user_get_moves_left(solver_instance)));
+
+ fcs_move_t move;
+ if (!freecell_solver_user_get_next_move(solver_instance, &move)) {
+ MoveHint *mh = translateMove(&move);
+ oldmoves.append(mh);
+ return mh;
+ } else
+ return 0;
+ } else
+ return Dealer::chooseHint();
+}
+
+void FreecellBase::countFreeCells(int &free_cells, int &free_stores) const
+{
+ free_cells = 0;
+ free_stores = 0;
+
+ for (uint i = 0; i < freecell.count(); i++)
+ if (freecell[i]->isEmpty()) free_cells++;
+ if (es_filling == FCS_ES_FILLED_BY_ANY_CARD)
+ for (uint i = 0; i < store.count(); i++)
+ if (store[i]->isEmpty()) free_stores++;
+}
+
+void FreecellBase::freeSolution()
+{
+ for (HintList::Iterator it = oldmoves.begin(); it != oldmoves.end(); ++it)
+ delete *it;
+ oldmoves.clear();
+
+ if (!solver_instance)
+ return;
+ freecell_solver_user_recycle(solver_instance);
+ solver_ret = FCS_STATE_NOT_BEGAN_YET;
+}
+
+void FreecellBase::stopDemo()
+{
+ Dealer::stopDemo();
+ freeSolution();
+}
+
+void FreecellBase::moveCards(CardList &c, FreecellPile *from, Pile *to)
+{
+ if (!demoActive() && solver_instance) {
+ freeSolution();
+ }
+
+ assert(c.count() > 1);
+ if (unlimited_move) {
+ from->Pile::moveCards(c, to);
+ return;
+ }
+ setWaiting(true);
+
+ from->moveCardsBack(c);
+ waitfor = c.first();
+ connect(waitfor, SIGNAL(stoped(Card*)), SLOT(waitForMoving(Card*)));
+
+ PileList fcs;
+
+ for (uint i = 0; i < freecell.count(); i++)
+ if (freecell[i]->isEmpty()) fcs.append(freecell[i]);
+
+ PileList fss;
+
+ if (es_filling == FCS_ES_FILLED_BY_ANY_CARD)
+ for (uint i = 0; i < store.count(); i++)
+ if (store[i]->isEmpty() && to != store[i]) fss.append(store[i]);
+
+ if (fcs.count() == 0) {
+ assert(fss.count());
+ fcs.append(fss.last());
+ fss.remove(fss.fromLast());
+ }
+ while (moves.count()) { delete moves.first(); moves.remove(moves.begin()); }
+
+ movePileToPile(c, to, fss, fcs, 0, c.count(), 0);
+
+ if (!waitfor->animated())
+ QTimer::singleShot(0, this, SLOT(startMoving()));
+}
+
+struct MoveAway {
+ Pile *firstfree;
+ int start;
+ int count;
+};
+
+void FreecellBase::movePileToPile(CardList &c, Pile *to, PileList fss, PileList &fcs, uint start, uint count, int debug_level)
+{
+ kdDebug(11111) << debug_level << " movePileToPile" << c.count() << " " << start << " " << count << endl;
+ uint moveaway = 0;
+ if (count > fcs.count() + 1) {
+ moveaway = (fcs.count() + 1);
+ while (moveaway * 2 < count)
+ moveaway <<= 1;
+ }
+ kdDebug(11111) << debug_level << " moveaway " << moveaway << endl;
+
+ QValueList<MoveAway> moves_away;
+
+ if (count - moveaway < (fcs.count() + 1) && (count <= 2 * (fcs.count() + 1))) {
+ moveaway = count - (fcs.count() + 1);
+ }
+ while (count > fcs.count() + 1) {
+ assert(fss.count());
+ MoveAway ma;
+ ma.firstfree = fss[0];
+ ma.start = start;
+ ma.count = moveaway;
+ moves_away.append(ma);
+ fss.remove(fss.begin());
+ movePileToPile(c, ma.firstfree, fss, fcs, start, moveaway, debug_level + 1);
+ start += moveaway;
+ count -= moveaway;
+ moveaway >>= 1;
+ if ((count > (fcs.count() + 1)) && (count <= 2 * (fcs.count() + 1)))
+ moveaway = count - (fcs.count() + 1);
+ }
+ uint moving = QMIN(count, QMIN(c.count() - start, fcs.count() + 1));
+ assert(moving);
+
+ for (uint i = 0; i < moving - 1; i++) {
+ moves.append(new MoveHint(c[c.count() - i - 1 - start], fcs[i]));
+ }
+ moves.append(new MoveHint(c[c.count() - start - 1 - (moving - 1)], to));
+
+ for (int i = moving - 2; i >= 0; --i)
+ moves.append(new MoveHint(c[c.count() - i - 1 - start], to));
+
+ while (moves_away.count())
+ {
+ MoveAway ma = moves_away.last();
+ moves_away.remove(moves_away.fromLast());
+ movePileToPile(c, to, fss, fcs, ma.start, ma.count, debug_level + 1);
+ fss.append(ma.firstfree);
+ }
+}
+
+void FreecellBase::startMoving()
+{
+ kdDebug(11111) << "startMoving\n";
+ if (moves.isEmpty()) {
+ if (demoActive() && towait) {
+ waitForDemo(towait);
+ }
+ setWaiting(false);
+ takeState();
+ return;
+ }
+
+ MoveHint *mh = moves.first();
+ moves.remove(moves.begin());
+ CardList empty;
+ empty.append(mh->card());
+ assert(mh->card() == mh->card()->source()->top());
+ assert(mh->pile()->legalAdd(empty));
+ mh->pile()->add(mh->card());
+ mh->pile()->moveCardsBack(empty, true);
+ waitfor = mh->card();
+ kdDebug(11111) << "wait for moving end " << mh->card()->name() << endl;
+ connect(mh->card(), SIGNAL(stoped(Card*)), SLOT(waitForMoving(Card*)));
+ delete mh;
+}
+
+void FreecellBase::newDemoMove(Card *m)
+{
+ Dealer::newDemoMove(m);
+ if (m != m->source()->top())
+ m->disconnect();
+}
+
+void FreecellBase::waitForMoving(Card *c)
+{
+ if (waitfor != c)
+ return;
+ c->disconnect();
+ startMoving();
+}
+
+bool FreecellBase::cardDblClicked(Card *c)
+{
+ // target move
+ if (Dealer::cardDblClicked(c))
+ return true;
+
+ if (c->animated())
+ return false;
+
+ if (c == c->source()->top() && c->realFace())
+ for (uint i = 0; i < freecell.count(); i++)
+ if (freecell[i]->isEmpty()) {
+ CardList empty;
+ empty.append(c);
+ c->source()->moveCards(empty, freecell[i]);
+ canvas()->update();
+ return true;
+ }
+ return false;
+}
+
+bool FreecellBase::CanPutStore(const Pile *c1, const CardList &c2) const
+{
+ int fcs, fss;
+ countFreeCells(fcs, fss);
+
+ if (c1->isEmpty()) // destination is empty
+ fss--;
+
+ if (!unlimited_move && int(c2.count()) > ((fcs)+1)<<fss)
+ return false;
+
+ // ok if the target is empty
+ if (c1->isEmpty())
+ return true;
+
+ Card *c = c2.first(); // we assume there are only valid sequences
+
+ // ok if in sequence, alternate colors
+ return ((c1->top()->rank() == (c->rank()+1))
+ && (c1->top()->isRed() != c->isRed()));
+}
+
+bool FreecellBase::checkAdd(int, const Pile *c1, const CardList &c2) const
+{
+ return CanPutStore(c1, c2);
+}
+
+//-------------------------------------------------------------------------//
+
+bool FreecellBase::checkRemove(int checkIndex, const Pile *p, const Card *c) const
+{
+ if (checkIndex != 0)
+ return false;
+
+ // ok if just one card
+ if (c == p->top())
+ return true;
+
+ // Now we're trying to move two or more cards.
+
+ // First, let's check if the column is in valid
+ // (that is, in sequence, alternated colors).
+ int index = p->indexOf(c) + 1;
+ const Card *before = c;
+ while (true)
+ {
+ c = p->at(index++);
+
+ if (!((c->rank() == (before->rank()-1))
+ && (c->isRed() != before->isRed())))
+ {
+ return false;
+ }
+ if (c == p->top())
+ return true;
+ before = c;
+ }
+
+ return true;
+}
+
+//-------------------------------------------------------------------------//
+
+class Freecell : public FreecellBase
+{
+public:
+ Freecell( KMainWindow* parent=0, const char* name=0);
+ virtual void deal();
+};
+
+Freecell::Freecell( KMainWindow* parent, const char* name)
+ : FreecellBase(1, 8, 4, FCS_ES_FILLED_BY_ANY_CARD, false, parent, name)
+{
+ for (int i = 0; i < 8; i++)
+ store[i]->move(8 + ( cardMap::CARDX() * 11 / 10 + 1 ) * i, 8 + cardMap::CARDY() * 11 / 10);
+
+ const int right = 8 + ( cardMap::CARDX() * 11 / 10 + 1 ) * 7 + cardMap::CARDX();
+
+ for (int i = 0; i < 4; i++)
+ freecell[i]->move(8 + ( cardMap::CARDX() * 13 / 12 ) * i, 8);
+
+ for (int i = 0; i < 4; i++)
+ target[i]->move(right - (3-i) * ( cardMap::CARDX() * 13 / 12 ) -cardMap::CARDX() , 8);
+}
+
+void Freecell::deal()
+{
+ int column = 0;
+ while (!deck->isEmpty())
+ {
+ store[column]->add (deck->nextCard(), false, true);
+ column = (column + 1) % 8;
+ }
+}
+
+static class LocalDealerInfo3 : public DealerInfo
+{
+public:
+ LocalDealerInfo3() : DealerInfo(I18N_NOOP("&Freecell"), 3) {}
+ virtual Dealer *createGame(KMainWindow *parent) { return new Freecell(parent); }
+} ldi8;
+
+//-------------------------------------------------------------------------//
+
+#include"freecell.moc"