diff --git a/CMakeLists.txt b/CMakeLists.txt index 087d6f9..6d0ead3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,135 +1,142 @@ project(kpat) cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) set (QT_MIN_VERSION "5.7.0") set (KF5_MIN_VERSION "5.30.0") include(FindPkgConfig) pkg_check_modules(FC_SOLVE REQUIRED libfreecell-solver) option(WITH_BH_SOLVER "Make use of https://github.com/shlomif/black-hole-solitaire for solving Golf") if (WITH_BH_SOLVER) pkg_check_modules(BLACK_HOLE_SOLVER REQUIRED libblack-hole-solver) add_definitions(-DWITH_BH_SOLVER) endif() +try_compile(fcs_soft_suspend "${CMAKE_CURRENT_BINARY_DIR}" SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/fcs_soft_suspend_test.c" + COMPILE_DEFINITIONS ${FC_SOLVE_INCLUDE_DIRS} + LINK_LIBRARIES ${FC_SOLVE_LDFLAGS}) + +if (fcs_soft_suspend) + add_definitions("-DWITH_FCS_SOFT_SUSPEND=1") +endif() find_package(ECM ${KF5_MIN_VERSION} REQUIRED CONFIG) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Widgets Svg Test) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Config ConfigWidgets CoreAddons Crash DBusAddons DocTools GuiAddons I18n KIO NewStuff WidgetsAddons XmlGui ) find_package(KF5KDEGames 4.9.0 REQUIRED) include(FeatureSummary) include(ECMAddAppIcon) include(ECMInstallIcons) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMQtDeclareLoggingCategory) add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libkcardgame/include ${FC_SOLVE_INCLUDE_DIRS} ${BLACK_HOLE_SOLVER_INCLUDE_DIRS}) add_subdirectory(icons) add_subdirectory(libkcardgame) add_subdirectory(mimetypes) add_subdirectory(previews) add_subdirectory(sounds) add_subdirectory(themes) add_subdirectory(doc) add_subdirectory(autotests) set(kpat_SRCS ${libfcs_SRCS} main.cpp dealer.cpp dealerinfo.cpp gameselectionscene.cpp mainwindow.cpp messagebox.cpp numbereddealdialog.cpp patpile.cpp pileutils.cpp renderer.cpp soundengine.cpp statisticsdialog.cpp view.cpp patsolve/abstract_fc_solve_solver.cpp patsolve/memory.cpp patsolve/patsolve.cpp clock.cpp patsolve/clocksolver.cpp fortyeight.cpp patsolve/fortyeightsolver.cpp freecell.cpp patsolve/freecellsolver.cpp golf.cpp patsolve/golfsolver.cpp grandf.cpp patsolve/grandfsolver.cpp gypsy.cpp patsolve/gypsysolver.cpp idiot.cpp patsolve/idiotsolver.cpp klondike.cpp patsolve/klondikesolver.cpp mod3.cpp patsolve/mod3solver.cpp simon.cpp patsolve/simonsolver.cpp spider.cpp patsolve/spidersolver.cpp yukon.cpp patsolve/yukonsolver.cpp ) ki18n_wrap_ui( kpat_SRCS statisticsdialog.ui ) kconfig_add_kcfg_files( kpat_SRCS settings.kcfgc ) ecm_qt_declare_logging_category(kpat_SRCS HEADER kpat_debug.h IDENTIFIER KPAT_LOG CATEGORY_NAME org.kde.kpat ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kpat.png") ecm_add_app_icon(kpat_SRCS ICONS ${ICONS_SRCS}) add_executable(kpat ${kpat_SRCS}) target_link_libraries(kpat KF5::Crash KF5::DBusAddons KF5::I18n KF5::KIOCore KF5KDEGames kcardgame ${FC_SOLVE_LDFLAGS} ${BLACK_HOLE_SOLVER_LDFLAGS} ) install(TARGETS kpat ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(FILES kpat.categories DESTINATION ${KDE_INSTALL_CONFDIR}) install(PROGRAMS org.kde.kpat.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES org.kde.kpat.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES kpatui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kpat) install(FILES kpat.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/fcs_soft_suspend_test.c b/fcs_soft_suspend_test.c new file mode 100644 index 0000000..850e25b --- /dev/null +++ b/fcs_soft_suspend_test.c @@ -0,0 +1,16 @@ +/* + * fcs_soft_suspend_test.c + * Copyright (C) 2019 Shlomi Fish + * + * Distributed under terms of the Expat license. + */ + +#include +#include + +int main(void) +{ + printf("%d\n", (int)FCS_STATE_SOFT_SUSPEND_PROCESS); + + return 0; +} diff --git a/patsolve/abstract_fc_solve_solver.cpp b/patsolve/abstract_fc_solve_solver.cpp index 2e5db2a..f76daeb 100644 --- a/patsolve/abstract_fc_solve_solver.cpp +++ b/patsolve/abstract_fc_solve_solver.cpp @@ -1,257 +1,267 @@ /* * Copyright (C) 2006-2009 Stephan Kulow * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "freecell-solver/fcs_user.h" #include "freecell-solver/fcs_cl.h" #include "abstract_fc_solve_solver.h" const int CHUNKSIZE = 100; const long int MAX_ITERS_LIMIT = 200000; #define PRINT 0 /* These two routines make and unmake moves. */ void FcSolveSolver::make_move(MOVE *) { return; } void FcSolveSolver::undo_move(MOVE *) { return; } +#ifdef WITH_FCS_SOFT_SUSPEND +const auto SOFT_SUSPEND = FCS_STATE_SOFT_SUSPEND_PROCESS; +#define set_soft_limit(u, limit) freecell_solver_user_soft_limit_iterations_long(u, limit) +#else +const auto SOFT_SUSPEND = FCS_STATE_SUSPEND_PROCESS; +#define set_soft_limit(u, limit) freecell_solver_user_limit_iterations(u, limit) +#endif /* Get the possible moves from a position, and store them in Possible[]. */ SolverInterface::ExitStatus FcSolveSolver::patsolve( int _max_positions ) { int current_iters_count; max_positions = (_max_positions < 0) ? MAX_ITERS_LIMIT : _max_positions; init(); int no_use = 0; int num_moves = 0; get_possible_moves(&no_use, &num_moves); Q_ASSERT( m_firstMoves.count() == 0 ); for (int j = 0; j < num_moves; ++j) m_firstMoves.append( Possible[j] ); // Sometimes the solver is invoked with a small maximal iterations // quota/limit (e.g: when loading a saved game or checking for autodrop // moves) and it is done frequently, so we return prematurely without // invoking freecell_solver_user_alloc() and friends which incur extra // overhead. // // The m_firstMoves should be good enough in that case. if (max_positions < 20) { return Solver::UnableToDetermineSolvability; } if (!solver_instance) { { solver_instance = freecell_solver_user_alloc(); solver_ret = FCS_STATE_NOT_BEGAN_YET; char * error_string; int error_arg; const char * known_parameters[1] = {NULL}; /* A "char *" copy instead of "const char *". */ int parse_args_ret_code = freecell_solver_user_cmd_line_parse_args( solver_instance, get_cmd_line_arg_count() , get_cmd_line_args(), 0, known_parameters, NULL, NULL, &error_string, &error_arg ); Q_ASSERT(!parse_args_ret_code); } /* Not needed for Simple Simon because it's already specified in * freecell_solver_cmd_line_args. TODO : abstract . * * Shlomi Fish * */ setFcSolverGameParams(); current_iters_count = CHUNKSIZE; - freecell_solver_user_limit_iterations(solver_instance, current_iters_count); + set_soft_limit(solver_instance, current_iters_count); +#ifdef WITH_FCS_SOFT_SUSPEND + freecell_solver_user_limit_iterations(solver_instance, MAX_ITERS_LIMIT); +#endif } if (solver_instance) { bool continue_loop = true; while (continue_loop && ( (solver_ret == FCS_STATE_NOT_BEGAN_YET) - || (solver_ret == FCS_STATE_SUSPEND_PROCESS)) + || (solver_ret == SOFT_SUSPEND)) && (current_iters_count < MAX_ITERS_LIMIT) ) { current_iters_count += CHUNKSIZE; - freecell_solver_user_limit_iterations(solver_instance, current_iters_count); + set_soft_limit(solver_instance, current_iters_count); if (solver_ret == FCS_STATE_NOT_BEGAN_YET) { solver_ret = freecell_solver_user_solve_board( solver_instance, board_as_string ); } else { solver_ret = freecell_solver_user_resume_solution(solver_instance); } { // QMutexLocker lock( &endMutex ); if ( m_shouldEnd ) { continue_loop = false; } } } } const long reached_iters = freecell_solver_user_get_num_times_long(solver_instance); Q_ASSERT(reached_iters <= MAX_ITERS_LIMIT); #if 0 fprintf(stderr, "iters = %ld\n", reached_iters); #endif switch (solver_ret) { case FCS_STATE_IS_NOT_SOLVEABLE: if (solver_instance) { freecell_solver_user_free(solver_instance); solver_instance = NULL; } return Solver::NoSolutionExists; case FCS_STATE_WAS_SOLVED: { if (solver_instance) { m_winMoves.clear(); while (freecell_solver_user_get_moves_left(solver_instance)) { fcs_move_t move; MOVE new_move; const int verdict = !freecell_solver_user_get_next_move( solver_instance, &move) ; Q_ASSERT (verdict); new_move.is_fcs = true; new_move.fcs = move; m_winMoves.append( new_move ); } freecell_solver_user_free(solver_instance); solver_instance = NULL; } return Solver::SolutionExists; } case FCS_STATE_SUSPEND_PROCESS: return Solver::UnableToDetermineSolvability; default: if (solver_instance) { freecell_solver_user_free(solver_instance); solver_instance = NULL; } return Solver::NoSolutionExists; } } /* Get the possible moves from a position, and store them in Possible[]. */ bool FcSolveSolver::isWon() { return true; } int FcSolveSolver::getOuts() { return 0; } FcSolveSolver::FcSolveSolver() : Solver() , solver_instance(NULL) , solver_ret(FCS_STATE_NOT_BEGAN_YET) , board_as_string("") { } unsigned int FcSolveSolver::getClusterNumber() { return 0; } void FcSolveSolver::print_layout() { #if 0 int i, w, o; fprintf(stderr, "print-layout-begin\n"); for (w = 0; w < 10; ++w) { Q_ASSERT( Wp[w] == &W[w][Wlen[w]-1] ); fprintf( stderr, "Play%d: ", w ); for (i = 0; i < Wlen[w]; ++i) { printcard(W[w][i], stderr); } fputc('\n', stderr); } fprintf( stderr, "Off: " ); for (o = 0; o < 4; ++o) { if ( O[o] != -1 ) printcard( O[o] + PS_KING, stderr); } fprintf(stderr, "\nprint-layout-end\n"); #endif } void FcSolveSolver::unpack_cluster( unsigned int) { return; } FcSolveSolver::~FcSolveSolver() { if (solver_instance) { freecell_solver_user_free(solver_instance); solver_instance = NULL; } } diff --git a/patsolve/freecellsolver.cpp b/patsolve/freecellsolver.cpp index 02ad6ab..1815248 100644 --- a/patsolve/freecellsolver.cpp +++ b/patsolve/freecellsolver.cpp @@ -1,573 +1,573 @@ /* * Copyright (C) 1998-2002 Tom Holroyd * Copyright (C) 2006-2009 Stephan Kulow * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "freecell-solver/fcs_user.h" #include "freecell-solver/fcs_cl.h" #include "freecellsolver.h" #include "../freecell.h" const int CHUNKSIZE = 100; const long int MAX_ITERS_LIMIT = 200000; /* The following function implements (Same_suit ? (suit(a) == suit(b)) : (color(a) != color(b))) */ namespace { constexpr bool suitable(const card_t a, const card_t b) {return ((a ^ b) & PS_COLOR) == PS_COLOR; } } /* Statistics. */ #if 0 int FreecellSolver::Xparam[] = { 4, 1, 8, -1, 7, 11, 4, 2, 2, 1, 2 }; #endif /* These two routines make and unmake moves. */ #if 0 void FreecellSolver::make_move(MOVE *m) { int from, to; card_t card; from = m->from; to = m->to; /* Remove from pile. */ card = *Wp[from]--; Wlen[from]--; hashpile(from); /* Add to pile. */ if (m->totype == O_Type) { O[to]++; } else { *++Wp[to] = card; Wlen[to]++; hashpile(to); } } void FreecellSolver::undo_move(MOVE *m) { int from, to; card_t card; from = m->from; to = m->to; /* Remove from 'to' pile. */ if (m->totype == O_Type) { card = O[to] + Osuit[to]; O[to]--; } else { card = *Wp[to]--; Wlen[to]--; hashpile(to); } /* Add to 'from' pile. */ *++Wp[from] = card; Wlen[from]++; hashpile(from); } #endif #if 0 /* Move prioritization. Given legal, pruned moves, there are still some that are a waste of time, especially in the endgame where there are lots of possible moves, but few productive ones. Note that we also prioritize positions when they are added to the queue. */ namespace { constexpr auto NNEED = 8; } void FreecellSolver::prioritize(MOVE *mp0, int n) { int i, j, s, w, pile[NNEED], npile; card_t card, need[4]; MOVE *mp; /* There are 4 cards that we "need": the next cards to go out. We give higher priority to the moves that remove cards from the piles containing these cards. */ for (i = 0; i < NNEED; ++i) { pile[i] = -1; } npile = 0; for (s = 0; s < 4; ++s) { need[s] = NONE; if (O[s] == NONE) { need[s] = Osuit[s] + PS_ACE; } else if (O[s] != PS_KING) { need[s] = Osuit[s] + O[s] + 1; } } /* Locate the needed cards. There's room for optimization here, like maybe an array that keeps track of every card; if maintaining such an array is not too expensive. */ for (w = 0; w < Nwpiles; ++w) { j = Wlen[w]; for (i = 0; i < j; ++i) { card = W[w][i]; s = SUIT(card); /* Save the locations of the piles containing not only the card we need next, but the card after that as well. */ if (need[s] != NONE && (card == need[s] || card == need[s] + 1)) { pile[npile++] = w; if (npile == NNEED) { break; } } } if (npile == NNEED) { break; } } /* Now if any of the moves remove a card from any of the piles listed in pile[], bump their priority. Likewise, if a move covers a card we need, decrease its priority. These priority increments and decrements were determined empirically. */ for (i = 0, mp = mp0; i < n; ++i, ++mp) { if (mp->card_index != -1) { w = mp->from; for (j = 0; j < npile; ++j) { if (w == pile[j]) { mp->pri += Xparam[0]; } } if (Wlen[w] > 1) { card = W[w][Wlen[w] - 2]; for (s = 0; s < 4; ++s) { if (card == need[s]) { mp->pri += Xparam[1]; break; } } } if (mp->totype == W_Type) { for (j = 0; j < npile; ++j) { if (mp->to == pile[j]) { mp->pri -= Xparam[2]; } } } } } } #endif /* Automove logic. Freecell games must avoid certain types of automoves. */ #if 1 int FreecellSolver::good_automove(int o, int r) { if (r <= 2) { return true; } for (int foundation_idx = 0; foundation_idx < 4; ++foundation_idx) { KCard *c = deal->target[foundation_idx]->topCard(); if (c) { O[translateSuit( c->suit() ) >> 4] = c->rank(); } } /* Check the Out piles of opposite color. */ for (int i = 1 - (o & 1); i < 4; i += 2) { if (O[i] < r - 1) { #if 1 /* Raymond's Rule */ /* Not all the N-1's of opposite color are out yet. We can still make an automove if either both N-2's are out or the other same color N-3 is out (Raymond's rule). Note the re-use of the loop variable i. We return here and never make it back to the outer loop. */ for (i = 1 - (o & 1); i < 4; i += 2) { if (O[i] < r - 2) { return false; } } if (O[(o + 2) & 3] < r - 3) { return false; } return true; #else /* Horne's Rule */ return false; #endif } } return true; } int FreecellSolver::get_possible_moves(int *a, int *numout) { int w; card_t card; MOVE *mp; /* Check for moves from W to O. */ int n = 0; mp = Possible; for (w = 0; w < Nwpiles + Ntpiles; ++w) { if (Wlen[w] > 0) { card = *Wp[w]; int out_suit = SUIT(card); const bool empty = (O[out_suit] == NONE); if ((empty && (RANK(card) == PS_ACE)) || (!empty && (RANK(card) == O[out_suit] + 1))) { mp->card_index = 0; mp->from = w; mp->to = out_suit; mp->totype = O_Type; mp->turn_index = -1; mp->pri = 0; /* unused */ n++; mp++; /* If it's an automove, just do it. */ if (good_automove(out_suit, RANK(card))) { *a = true; mp[-1].pri = 127; if (n != 1) { Possible[0] = mp[-1]; return 1; } *numout = n; return n; } } } } *numout = 0; return 0; } #endif #define CMD_LINE_ARGS_NUM 2 static const char * freecell_solver_cmd_line_args[CMD_LINE_ARGS_NUM] = { -#if 0 +#ifdef WITH_FCS_SOFT_SUSPEND "--load-config", "video-editing" #else "--load-config", "slick-rock" #endif }; int FreecellSolver::get_cmd_line_arg_count() { return CMD_LINE_ARGS_NUM; } const char * * FreecellSolver::get_cmd_line_args() { return freecell_solver_cmd_line_args; } void FreecellSolver::setFcSolverGameParams() { /* * 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 * */ freecell_solver_user_set_num_freecells(solver_instance,4); freecell_solver_user_set_num_stacks(solver_instance,8); freecell_solver_user_set_num_decks(solver_instance,1); 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, 0); freecell_solver_user_set_empty_stacks_filled_by(solver_instance, FCS_ES_FILLED_BY_ANY_CARD); } #if 0 void FreecellSolver::unpack_cluster( unsigned int k ) { /* Get the Out cells from the cluster number. */ O[0] = k & 0xF; k >>= 4; O[1] = k & 0xF; k >>= 4; O[2] = k & 0xF; k >>= 4; O[3] = k & 0xF; } #endif FreecellSolver::FreecellSolver(const Freecell *dealer) : FcSolveSolver() { #if 0 Osuit[0] = PS_DIAMOND; Osuit[1] = PS_CLUB; Osuit[2] = PS_HEART; Osuit[3] = PS_SPADE; Nwpiles = 8; Ntpiles = 4; #endif deal = dealer; } MoveHint FreecellSolver::translateMove( const MOVE &m ) { if (m.is_fcs) { fcs_move_t move = m.fcs; int cards = fcs_move_get_num_cards_in_seq(move); PatPile *from = 0; PatPile *to = 0; switch(fcs_move_get_type(move)) { case FCS_MOVE_TYPE_STACK_TO_STACK: from = deal->store[fcs_move_get_src_stack(move)]; to = deal->store[fcs_move_get_dest_stack(move)]; break; case FCS_MOVE_TYPE_FREECELL_TO_STACK: from = deal->freecell[fcs_move_get_src_freecell(move)]; to = deal->store[fcs_move_get_dest_stack(move)]; cards = 1; break; case FCS_MOVE_TYPE_FREECELL_TO_FREECELL: from = deal->freecell[fcs_move_get_src_freecell(move)]; to = deal->freecell[fcs_move_get_dest_freecell(move)]; cards = 1; break; case FCS_MOVE_TYPE_STACK_TO_FREECELL: from = deal->store[fcs_move_get_src_stack(move)]; to = deal->freecell[fcs_move_get_dest_freecell(move)]; cards = 1; break; case FCS_MOVE_TYPE_STACK_TO_FOUNDATION: from = deal->store[fcs_move_get_src_stack(move)]; cards = 1; to = 0; break; case FCS_MOVE_TYPE_FREECELL_TO_FOUNDATION: from = deal->freecell[fcs_move_get_src_freecell(move)]; cards = 1; to = 0; } Q_ASSERT(from); Q_ASSERT(cards <= from->cards().count()); Q_ASSERT(to || cards == 1); KCard *card = from->cards()[from->cards().count() - cards]; if (!to) { PatPile *target = 0; PatPile *empty = 0; for (int i = 0; i < 4; ++i) { KCard *c = deal->target[i]->topCard(); if (c) { if ( c->suit() == card->suit() ) { target = deal->target[i]; break; } } else if ( !empty ) empty = deal->target[i]; } to = target ? target : empty; } Q_ASSERT(to); return MoveHint(card, to, 0); } else { // this is tricky as we need to want to build the "meta moves" PatPile *frompile = nullptr; if ( m.from < 8 ) frompile = deal->store[m.from]; else frompile = deal->freecell[m.from-8]; KCard *card = frompile->at( frompile->count() - m.card_index - 1); if ( m.totype == O_Type ) { PatPile *target = nullptr; PatPile *empty = nullptr; for (int i = 0; i < 4; ++i) { KCard *c = deal->target[i]->topCard(); if (c) { if ( c->suit() == card->suit() ) { target = deal->target[i]; break; } } else if ( !empty ) empty = deal->target[i]; } if ( !target ) target = empty; return MoveHint( card, target, m.pri ); } else { PatPile *target = nullptr; if ( m.to < 8 ) target = deal->store[m.to]; else target = deal->freecell[m.to-8]; return MoveHint( card, target, m.pri ); } } } void FreecellSolver::translate_layout() { strcpy(board_as_string, deal->solverFormat().toLatin1()); if (solver_instance) { freecell_solver_user_recycle(solver_instance); solver_ret = FCS_STATE_NOT_BEGAN_YET; } #if 0 /* Read the workspace. */ int total = 0; for ( int w = 0; w < 10; ++w ) { int i = translate_pile(deal->store[w], W[w], 52); Wp[w] = &W[w][i - 1]; Wlen[w] = i; total += i; } for (int i = 0; i < 4; ++i) { O[i] = -1; KCard *c = deal->target[i]->top(); if (c) { total += 13; O[i] = translateSuit( c->suit() ); } } #endif /* Read the workspace. */ int total = 0; for ( int w = 0; w < 8; ++w ) { int i = translate_pile(deal->store[w], W[w], 52); Wp[w] = &W[w][i - 1]; Wlen[w] = i; total += i; if (w == Nwpiles) { break; } } /* Temp cells may have some cards too. */ for (int w = 0; w < Ntpiles; ++w) { int i = translate_pile( deal->freecell[w], W[w+Nwpiles], 52 ); Wp[w+Nwpiles] = &W[w+Nwpiles][i-1]; Wlen[w+Nwpiles] = i; total += i; } /* Output piles, if any. */ for (int i = 0; i < 4; ++i) { O[i] = NONE; } if (total != 52) { for (int i = 0; i < 4; ++i) { KCard *c = deal->target[i]->topCard(); if (c) { O[translateSuit( c->suit() ) >> 4] = c->rank(); total += c->rank(); } } } } #if 0 unsigned int FreecellSolver::getClusterNumber() { int i = O[0] + (O[1] << 4); unsigned int k = i; i = O[2] + (O[3] << 4); k |= i << 8; return k; } #endif #if 0 void FreecellSolver::print_layout() { int i, t, w, o; fprintf(stderr, "print-layout-begin\n"); for (w = 0; w < Nwpiles; ++w) { fprintf(stderr, "W-Pile%d: ", w); for (i = 0; i < Wlen[w]; ++i) { printcard(W[w][i], stderr); } fputc('\n', stderr); } for (t = 0; t < Ntpiles; ++t) { fprintf(stderr, "T-Pile%d: ", t+Nwpiles); printcard(W[t+Nwpiles][Wlen[t+Nwpiles]], stderr); } fprintf( stderr, "\n" ); for (o = 0; o < 4; ++o) { printcard(O[o] + Osuit[o], stderr); } fprintf(stderr, "\nprint-layout-end\n"); } #endif