|
|
|
/*---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
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 <tqtimer.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,
|
|
|
|
TDEMainWindow* 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();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString 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 TQString();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString 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 TQString::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";
|
|
|
|
|
|
|
|
TQString 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);
|
|
|
|
TQTimer::singleShot(0, this, TQT_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString FreecellBase::solverFormat() const
|
|
|
|
{
|
|
|
|
TQString output;
|
|
|
|
TQString 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 += TQString::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 += TQString::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, TQT_SIGNAL(stoped(Card*)), TQT_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())
|
|
|
|
TQTimer::singleShot(0, this, TQT_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;
|
|
|
|
|
|
|
|
TQValueList<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 = TQMIN(count, TQMIN(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(), TQT_SIGNAL(stoped(Card*)), TQT_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( TDEMainWindow* parent=0, const char* name=0);
|
|
|
|
virtual void deal();
|
|
|
|
};
|
|
|
|
|
|
|
|
Freecell::Freecell( TDEMainWindow* 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(TDEMainWindow *parent) { return new Freecell(parent); }
|
|
|
|
} ldi8;
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|
|
|
|
|
|
#include"freecell.moc"
|