File indexing completed on 2024-05-26 04:08:32

0001 /*
0002  * Copyright (C) 1998-2002 Tom Holroyd <tomh@kurage.nimh.nih.gov>
0003  * Copyright (C) 2006-2009 Stephan Kulow <coolo@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU General Public License as
0007  * published by the Free Software Foundation; either version 2 of
0008  * the License, or (at your option) any later version.
0009  *
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License
0016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017  */
0018 
0019 #include "patsolve.h"
0020 
0021 // own
0022 #include "../kpat_debug.h"
0023 #include "../patpile.h"
0024 // Std
0025 #include <cassert>
0026 #include <cctype>
0027 #include <cmath>
0028 #include <cstdarg>
0029 #include <cstdlib>
0030 #include <cstring>
0031 #include <sys/types.h>
0032 
0033 #ifdef ERR
0034 #undef ERR
0035 #endif
0036 
0037 long all_moves = 0;
0038 
0039 /* This is a 32 bit FNV hash.  For more information, see
0040 http://www.isthe.com/chongo/tech/comp/fnv/index.html */
0041 
0042 namespace
0043 {
0044 constexpr qint32 FNV_32_PRIME = 0x01000193; // FIXME: move into fnv_hash once we depend on C++14
0045 constexpr qint32 fnv_hash(qint32 x, qint32 hash)
0046 {
0047     return (hash * FNV_32_PRIME) ^ x;
0048 }
0049 /* Hash a 0 terminated string. */
0050 
0051 quint32 fnv_hash_str(const quint8 *s)
0052 {
0053     constexpr qint32 FNV1_32_INIT = 0x811C9DC5;
0054     quint32 h;
0055 
0056     h = FNV1_32_INIT;
0057     while (*s) {
0058         h = fnv_hash(*s++, h);
0059     }
0060 
0061     return h;
0062 }
0063 }
0064 
0065 /* Hash a pile. */
0066 
0067 template<size_t NumberPiles>
0068 void Solver<NumberPiles>::hashpile(int w)
0069 {
0070     W[w][Wlen[w]] = 0;
0071     Whash[w] = fnv_hash_str(W[w]);
0072 
0073     /* Invalidate this pile's id.  We'll calculate it later. */
0074 
0075     Wpilenum[w] = -1;
0076 }
0077 
0078 /* Generate an array of the moves we can make from this position. */
0079 
0080 template<size_t NumberPiles>
0081 MOVE *Solver<NumberPiles>::get_moves(int *nmoves)
0082 {
0083     int i, n, alln, a = 0, numout = 0;
0084     MOVE *mp, *mp0;
0085 
0086     /* Fill in the Possible array. */
0087 
0088     alln = n = get_possible_moves(&a, &numout);
0089 #ifdef GET_MOVES_DEBUG
0090     {
0091         print_layout();
0092         fprintf(stderr, "moves %d\n", n);
0093         for (int j = 0; j < n; j++) {
0094             fprintf(stderr, "  ");
0095             if (Possible[j].totype == O_Type)
0096                 fprintf(stderr, "move from %d out (at %d) Prio: %d\n", Possible[j].from, Possible[j].turn_index, Possible[j].pri);
0097             else
0098                 fprintf(stderr,
0099                         "move %d from %d to %d (%d) Prio: %d\n",
0100                         Possible[j].card_index,
0101                         Possible[j].from,
0102                         Possible[j].to,
0103                         Possible[j].turn_index,
0104                         Possible[j].pri);
0105         }
0106     }
0107 #endif
0108 
0109     /* No moves?  Maybe we won. */
0110 
0111     if (n == 0) {
0112         /* No more moves - won or lost */
0113         // print_layout();
0114         return nullptr;
0115     }
0116 
0117     /* Prioritize these moves.  Automoves don't get queued, so they
0118     don't need a priority. */
0119 
0120     if (!a) {
0121         prioritize(Possible, alln);
0122     }
0123 
0124     /* Now copy to safe storage and return.  Non-auto moves out get put
0125     at the end.  Queueing them isn't a good idea because they are still
0126     good moves, even if they didn't pass the automove test.  So we still
0127     do the recursive solve() on them, but only after queueing the other
0128     moves. */
0129 
0130     mp = mp0 = mm_new_array<MOVE>(n);
0131     if (mp == nullptr) {
0132         return nullptr;
0133     }
0134     *nmoves = n;
0135     i = 0;
0136     if (a || numout == 0) {
0137         for (i = 0; i < alln; ++i) {
0138             if (Possible[i].card_index != -1) {
0139                 *mp = Possible[i]; /* struct copy */
0140                 mp++;
0141             }
0142         }
0143     } else {
0144         for (i = numout; i < alln; ++i) {
0145             if (Possible[i].card_index != -1) {
0146                 *mp = Possible[i]; /* struct copy */
0147                 mp++;
0148             }
0149         }
0150         for (i = 0; i < numout; ++i) {
0151             if (Possible[i].card_index != -1) {
0152                 *mp = Possible[i]; /* struct copy */
0153                 mp++;
0154             }
0155         }
0156     }
0157 
0158     return mp0;
0159 }
0160 
0161 /* Test the current position to see if it's new (or better).  If it is, save
0162 it, along with the pointer to its parent and the move we used to get here. */
0163 
0164 int Posbytes;
0165 
0166 template<size_t NumberPiles>
0167 void Solver<NumberPiles>::pilesort(void)
0168 {
0169     /* Make sure all the piles have id numbers. */
0170 
0171     for (size_t w = 0; w < NumberPiles; w++) {
0172         if (Wpilenum[w] < 0) {
0173             Wpilenum[w] = get_pilenum(w);
0174             if (Wpilenum[w] < 0) {
0175                 return;
0176             }
0177         }
0178         // fprintf( stderr, "%d ", Wpilenum[w] );
0179     }
0180     // fprintf( stderr, "\n" );
0181 }
0182 
0183 #define NBUCKETS 65521 /* the largest 16 bit prime */
0184 #define NPILES 65536 /* a 16 bit code */
0185 
0186 typedef struct bucketlist {
0187     quint8 *pile; /* 0 terminated copy of the pile */
0188     quint32 hash; /* the pile's hash code */
0189     int pilenum; /* the unique id for this pile */
0190     struct bucketlist *next;
0191 } BUCKETLIST;
0192 
0193 BUCKETLIST *Bucketlist[NBUCKETS];
0194 int Pilenum; /* the next pile number to be assigned */
0195 
0196 BUCKETLIST *Pilebucket[NPILES]; /* reverse lookup for unpack to get the bucket
0197                    from the pile */
0198 
0199 /* Compact position representation.  The position is stored as an
0200 array with the following format:
0201     pile0# pile1# ... pileN# (N = Nwpiles)
0202 where each pile number is packed into 16 bits (so a pile take 2 bytes).
0203 Positions in this format are unique can be compared with memcmp().  The O
0204 cells are encoded as a cluster number: no two positions with different
0205 cluster numbers can ever be the same, so we store different clusters in
0206 different trees.  */
0207 
0208 int Treebytes;
0209 
0210 template<size_t NumberPiles>
0211 TREE *Solver<NumberPiles>::pack_position(void)
0212 {
0213     int j;
0214     quint8 *p;
0215     TREE *node;
0216 
0217     /* Allocate space and store the pile numbers.  The tree node
0218     will get filled in later, by insert_node(). */
0219 
0220     p = mm->new_from_block(Treebytes);
0221     if (p == nullptr) {
0222         Status = UnableToDetermineSolvability;
0223         return nullptr;
0224     }
0225     node = (TREE *)p;
0226     p += sizeof(TREE);
0227 
0228     /* Pack the pile numers j into bytes p.
0229                j             j
0230         +--------+----:----+--------+
0231         |76543210|7654:3210|76543210|
0232         +--------+----:----+--------+
0233             p         p         p
0234     */
0235 
0236     quint16 *p2 = (quint16 *)p;
0237     for (size_t w = 0; w < NumberPiles; ++w) {
0238         j = Wpilenum[w];
0239         if (j < 0) {
0240             mm->give_back_block(p);
0241             return nullptr;
0242         }
0243         *p2++ = j;
0244     }
0245 
0246     return node;
0247 }
0248 
0249 /* Like strcpy() but return the length of the string. */
0250 
0251 static inline int strecpy(unsigned char *d, unsigned char *s)
0252 {
0253     int i;
0254 
0255     i = 0;
0256     while ((*d++ = *s++) != '\0') {
0257         i++;
0258     }
0259 
0260     return i;
0261 }
0262 
0263 /* Unpack a compact position rep.  T cells must be restored from the
0264 array following the POSITION struct. */
0265 
0266 template<size_t NumberPiles>
0267 void Solver<NumberPiles>::unpack_position(POSITION *pos)
0268 {
0269     int i = 0;
0270     BUCKETLIST *l;
0271 
0272     unpack_cluster(pos->cluster);
0273 
0274     /* Unpack bytes p into pile numbers j.
0275             p         p         p
0276         +--------+----:----+--------+
0277         |76543210|7654:3210|76543210|
0278         +--------+----:----+--------+
0279                j             j
0280     */
0281 
0282     size_t w = 0;
0283     quint16 *p2 = (quint16 *)((quint8 *)(pos->node) + sizeof(TREE));
0284     while (w < NumberPiles) {
0285         i = *p2++;
0286         Wpilenum[w] = i;
0287         l = Pilebucket[i];
0288         i = strecpy(W[w], l->pile);
0289         Wp[w] = &W[w][i - 1];
0290         Wlen[w] = i;
0291         Whash[w] = l->hash;
0292         w++;
0293     }
0294 }
0295 
0296 template<size_t NumberPiles>
0297 void Solver<NumberPiles>::printcard(card_t card, FILE *outfile)
0298 {
0299     static char Rank[] = " A23456789TJQK";
0300     static char Suit[] = "DCHS";
0301 
0302     if (RANK(card) == NONE) {
0303         fprintf(outfile, "   ");
0304     } else {
0305         if (DOWN(card))
0306             fprintf(outfile, "|%c%c ", Rank[RANK(card)], Suit[SUIT(card)]);
0307         else
0308             fprintf(outfile, "%c%c ", Rank[RANK(card)], Suit[SUIT(card)]);
0309     }
0310 }
0311 
0312 /* Win.  Print out the move stack. */
0313 
0314 template<size_t NumberPiles>
0315 void Solver<NumberPiles>::win(POSITION *pos)
0316 {
0317     int i, nmoves;
0318     POSITION *p;
0319     MOVE **mpp, **mpp0;
0320 
0321     /* Go back up the chain of parents and store the moves
0322        in reverse order. */
0323 
0324     i = 0;
0325     for (p = pos; p->parent; p = p->parent) {
0326         i++;
0327     }
0328     nmoves = i;
0329 
0330     // printf("Winning in %d moves.\n", nmoves);
0331 
0332     mpp0 = mm_new_array<MOVE *>(nmoves);
0333     if (mpp0 == nullptr) {
0334         Status = UnableToDetermineSolvability;
0335         return; /* how sad, so close... */
0336     }
0337     mpp = mpp0 + nmoves - 1;
0338     for (p = pos; p->parent; p = p->parent) {
0339         *mpp-- = &p->move;
0340     }
0341 
0342     for (i = 0, mpp = mpp0; i < nmoves; ++i, ++mpp)
0343         m_winMoves.append(**mpp);
0344 
0345     MemoryManager::free_array(mpp0, nmoves);
0346 }
0347 
0348 /* Initialize the hash buckets. */
0349 
0350 template<size_t NumberPiles>
0351 void Solver<NumberPiles>::init_buckets(void)
0352 {
0353     int i;
0354 
0355     /* Packed positions need 3 bytes for every 2 piles. */
0356 
0357     i = (NumberPiles) * sizeof(quint16);
0358     i += (NumberPiles)&0x1;
0359 
0360     mm->Pilebytes = i;
0361 
0362     memset(Bucketlist, 0, sizeof(Bucketlist));
0363     Pilenum = 0;
0364     Treebytes = sizeof(TREE) + mm->Pilebytes;
0365 
0366     /* In order to keep the TREE structure aligned, we need to add
0367     up to 7 bytes on Alpha or 3 bytes on Intel -- but this is still
0368     better than storing the TREE nodes and keys separately, as that
0369     requires a pointer.  On Intel for -f Treebytes winds up being
0370     a multiple of 8 currently anyway so it doesn't matter. */
0371 
0372 #define ALIGN_BITS 0x7
0373     if (Treebytes & ALIGN_BITS) {
0374         Treebytes |= ALIGN_BITS;
0375         Treebytes++;
0376     }
0377     Posbytes = sizeof(POSITION);
0378     if (Posbytes & ALIGN_BITS) {
0379         Posbytes |= ALIGN_BITS;
0380         Posbytes++;
0381     }
0382 }
0383 
0384 /* For each pile, return a unique identifier.  Although there are a
0385 large number of possible piles, generally fewer than 1000 different
0386 piles appear in any given game.  We'll use the pile's hash to find
0387 a hash bucket that contains a short list of piles, along with their
0388 identifiers. */
0389 
0390 template<size_t NumberPiles>
0391 int Solver<NumberPiles>::get_pilenum(int w)
0392 {
0393     int bucket, pilenum;
0394     BUCKETLIST *l, *last;
0395 
0396     /* For a given pile, get its unique pile id.  If it doesn't have
0397     one, add it to the appropriate list and give it one.  First, get
0398     the hash bucket. */
0399 
0400     bucket = Whash[w] % NBUCKETS;
0401 
0402     /* Look for the pile in this bucket. */
0403 
0404     last = nullptr;
0405     for (l = Bucketlist[bucket]; l; l = l->next) {
0406         if (l->hash == Whash[w] && strncmp((char *)l->pile, (char *)W[w], Wlen[w]) == 0) {
0407             break;
0408         }
0409         last = l;
0410     }
0411 
0412     /* If we didn't find it, make a new one and add it to the list. */
0413 
0414     if (l == nullptr) {
0415         if (Pilenum >= NPILES) {
0416             Status = UnableToDetermineSolvability;
0417             // qCDebug(KPAT_LOG) << "out of piles";
0418             return -1;
0419         }
0420         l = mm_allocate<BUCKETLIST>();
0421         if (l == nullptr) {
0422             Status = UnableToDetermineSolvability;
0423             // qCDebug(KPAT_LOG) << "out of buckets";
0424             return -1;
0425         }
0426         l->pile = mm_new_array<quint8>(Wlen[w] + 1);
0427         if (l->pile == nullptr) {
0428             Status = UnableToDetermineSolvability;
0429             MemoryManager::free_ptr(l);
0430             // qCDebug(KPAT_LOG) << "out of memory";
0431             return -1;
0432         }
0433 
0434         /* Store the new pile along with its hash.  Maintain
0435         a reverse mapping so we can unpack the piles swiftly. */
0436 
0437         strncpy((char *)l->pile, (char *)W[w], Wlen[w] + 1);
0438         l->hash = Whash[w];
0439         l->pilenum = pilenum = Pilenum++;
0440         l->next = nullptr;
0441         if (last == nullptr) {
0442             Bucketlist[bucket] = l;
0443         } else {
0444             last->next = l;
0445         }
0446         Pilebucket[pilenum] = l;
0447     }
0448 
0449 #if 0
0450 if (w < 4) {
0451         fprintf( stderr, "get_pile_num %d ", l->pilenum );
0452         for (int i = 0; i < Wlen[w]; ++i) {
0453             printcard(W[w][i], stderr);
0454         }
0455         fprintf( stderr, "\n" );
0456 }
0457 #endif
0458     return l->pilenum;
0459 }
0460 
0461 template<size_t NumberPiles>
0462 void Solver<NumberPiles>::free_buckets(void)
0463 {
0464     int i, j;
0465     BUCKETLIST *l, *n;
0466 
0467     for (i = 0; i < NBUCKETS; i++) {
0468         l = Bucketlist[i];
0469         while (l) {
0470             n = l->next;
0471             j = strlen((char *)l->pile); /* @@@ use block? */
0472             MemoryManager::free_array(l->pile, j + 1);
0473             MemoryManager::free_ptr(l);
0474             l = n;
0475         }
0476     }
0477 }
0478 
0479 /* Solve patience games.  Prioritized breadth-first search.  Simple breadth-
0480 first uses exponential memory.  Here the work queue is kept sorted to give
0481 priority to positions with more cards out, so the solution found is not
0482 guaranteed to be the shortest, but it'll be better than with a depth-first
0483 search. */
0484 
0485 template<size_t NumberPiles>
0486 void Solver<NumberPiles>::doit()
0487 {
0488     int i, q;
0489     POSITION *pos;
0490     MOVE m;
0491 
0492     /* Init the queues. */
0493 
0494     for (i = 0; i < NQUEUES; ++i) {
0495         Qhead[i] = nullptr;
0496     }
0497     Maxq = 0;
0498 
0499     /* Queue the initial position to get started. */
0500 
0501     hash_layout();
0502     pilesort();
0503     m.card_index = -1;
0504     m.turn_index = -1;
0505     pos = new_position(nullptr, &m);
0506     if (pos == nullptr) {
0507         Status = UnableToDetermineSolvability;
0508         return;
0509     }
0510     queue_position(pos, 0);
0511 
0512     /* Solve it. */
0513 
0514     while ((pos = dequeue_position()) != nullptr) {
0515         q = solve(pos);
0516         if (!q) {
0517             free_position(pos, true);
0518         }
0519     }
0520 }
0521 
0522 /* Generate all the successors to a position and either queue them or
0523 recursively solve them.  Return whether any of the child nodes, or their
0524 descendents, were queued or not (if not, the position can be freed). */
0525 
0526 template<size_t NumberPiles>
0527 bool Solver<NumberPiles>::solve(POSITION *parent)
0528 {
0529     int i, nmoves, qq;
0530     MOVE *mp, *mp0;
0531     POSITION *pos;
0532 
0533     bool q;
0534     all_moves++;
0535 
0536     /* If we've won already (or failed), we just go through the motions
0537     but always return false from any position.  This enables the cleanup
0538     of the move stack and eventual destruction of the position store. */
0539 
0540     if (Status != NoSolutionExists) {
0541         return false;
0542     }
0543 
0544     if (m_shouldEnd.load()) {
0545         Status = SearchAborted;
0546         return false;
0547     }
0548 
0549     if (max_positions != -1 && Total_positions > (unsigned long)max_positions) {
0550         Status = MemoryLimitReached;
0551         return false;
0552     }
0553 
0554     /* Generate an array of all the moves we can make. */
0555 
0556     if ((mp0 = get_moves(&nmoves)) == nullptr) {
0557         if (isWon()) {
0558             Status = SolutionExists;
0559             win(parent);
0560         }
0561         return false;
0562     }
0563 
0564     if (parent->depth == 0) {
0565         Q_ASSERT(m_firstMoves.isEmpty());
0566         for (int j = 0; j < nmoves; ++j)
0567             m_firstMoves.append(Possible[j]);
0568     }
0569 
0570     parent->nchild = nmoves;
0571 
0572     /* Make each move and either solve or queue the result. */
0573 
0574     q = false;
0575     for (i = 0, mp = mp0; i < nmoves; ++i, ++mp) {
0576         make_move(mp);
0577 
0578         /* Calculate indices for the new piles. */
0579 
0580         pilesort();
0581 
0582         /* See if this is a new position. */
0583 
0584         if ((pos = new_position(parent, mp)) == nullptr) {
0585             undo_move(mp);
0586             parent->nchild--;
0587             continue;
0588         }
0589 
0590         /* If this position is in a new cluster, a card went out.
0591         Don't queue it, just keep going.  A larger cutoff can also
0592         force a recursive call, which can help speed things up (but
0593         reduces the quality of solutions).  Otherwise, save it for
0594         later. */
0595 
0596         if (pos->cluster != parent->cluster || !nmoves) {
0597             qq = solve(pos);
0598             undo_move(mp);
0599             if (!qq) {
0600                 free_position(pos, false);
0601             }
0602             q |= (bool)qq;
0603         } else {
0604             queue_position(pos, mp->pri);
0605             undo_move(mp);
0606             q = true;
0607         }
0608     }
0609     MemoryManager::free_array(mp0, nmoves);
0610 
0611     /* Return true if this position needs to be kept around. */
0612     return q;
0613 }
0614 
0615 /* We can't free the stored piles in the trees, but we can free some of the
0616 POSITION structs.  We have to be careful, though, because there are many
0617 threads running through the game tree starting from the queued positions.
0618 The nchild element keeps track of descendents, and when there are none left
0619 in the parent we can free it too after solve() returns and we get called
0620 recursively (rec == true). */
0621 
0622 template<size_t NumberPiles>
0623 void Solver<NumberPiles>::free_position(POSITION *pos, int rec)
0624 {
0625     /* We don't really free anything here, we just push it onto a
0626        freelist (using the queue member), so we can use it again later. */
0627 
0628     if (!rec) {
0629         pos->queue = Freepos;
0630         Freepos = pos;
0631         pos->parent->nchild--;
0632     } else {
0633         do {
0634             pos->queue = Freepos;
0635             Freepos = pos;
0636             pos = pos->parent;
0637             if (pos == nullptr) {
0638                 return;
0639             }
0640             pos->nchild--;
0641         } while (pos->nchild == 0);
0642     }
0643 }
0644 
0645 /* Save positions for consideration later.  pri is the priority of the move
0646 that got us here.  The work queue is kept sorted by priority (simply by
0647 having separate queues). */
0648 
0649 template<size_t NumberPiles>
0650 void Solver<NumberPiles>::queue_position(POSITION *pos, int pri)
0651 {
0652     /* In addition to the priority of a move, a position gets an
0653     additional priority depending on the number of cards out.  We use a
0654     "queue squashing function" to map nout to priority.  */
0655 
0656     int nout = getOuts();
0657 
0658     static qreal Yparam[] = {0.0032, 0.32, -3.0};
0659     qreal x = (Yparam[0] * nout + Yparam[1]) * nout + Yparam[2];
0660     pri += (int)floor(x + .5);
0661 
0662     if (pri < 0) {
0663         pri = 0;
0664     } else if (pri >= NQUEUES) {
0665         pri = NQUEUES - 1;
0666     }
0667     if (pri > Maxq) {
0668         Maxq = pri;
0669     }
0670 
0671     /* We always dequeue from the head.  Here we either stick the move
0672     at the head or tail of the queue, depending on whether we're
0673     pretending it's a stack or a queue. */
0674 
0675     pos->queue = nullptr;
0676     if (Qhead[pri] == nullptr) {
0677         Qhead[pri] = pos;
0678     } else {
0679         pos->queue = Qhead[pri];
0680         Qhead[pri] = pos;
0681     }
0682 }
0683 
0684 /* Return the position on the head of the queue, or NULL if there isn't one. */
0685 
0686 template<size_t NumberPiles>
0687 POSITION *Solver<NumberPiles>::dequeue_position()
0688 {
0689     int last;
0690     POSITION *pos;
0691     static int qpos = 0;
0692     static int minpos = 0;
0693 
0694     /* This is a kind of prioritized round robin.  We make sweeps
0695     through the queues, starting at the highest priority and
0696     working downwards; each time through the sweeps get longer.
0697     That way the highest priority queues get serviced the most,
0698     but we still get lots of low priority action (instead of
0699     ignoring it completely). */
0700 
0701     last = false;
0702     do {
0703         qpos--;
0704         if (qpos < minpos) {
0705             if (last) {
0706                 return nullptr;
0707             }
0708             qpos = Maxq;
0709             minpos--;
0710             if (minpos < 0) {
0711                 minpos = Maxq;
0712             }
0713             if (minpos == 0) {
0714                 last = true;
0715             }
0716         }
0717     } while (Qhead[qpos] == nullptr);
0718 
0719     pos = Qhead[qpos];
0720     Qhead[qpos] = pos->queue;
0721 
0722     /* Decrease Maxq if that queue emptied. */
0723 
0724     while (Qhead[qpos] == nullptr && qpos == Maxq && Maxq > 0) {
0725         Maxq--;
0726         qpos--;
0727         if (qpos < minpos) {
0728             minpos = qpos;
0729         }
0730     }
0731 
0732     /* Unpack the position into the work arrays. */
0733 
0734     unpack_position(pos);
0735 
0736     return pos;
0737 }
0738 
0739 template<size_t NumberPiles>
0740 Solver<NumberPiles>::Solver()
0741     : mm(new MemoryManager)
0742 {
0743     /* Initialize work arrays. */
0744     for (auto &workspace : W) {
0745         workspace = new card_t[84];
0746         memset(workspace, 0, sizeof(card_t) * 84);
0747     }
0748 }
0749 
0750 template<size_t NumberPiles>
0751 Solver<NumberPiles>::~Solver()
0752 {
0753     for (size_t i = 0; i < NumberPiles; ++i) {
0754         delete[] W[i];
0755     }
0756 }
0757 
0758 template<size_t NumberPiles>
0759 void Solver<NumberPiles>::init()
0760 {
0761     m_shouldEnd.store(false);
0762     init_buckets();
0763     mm->init_clusters();
0764 
0765     m_winMoves.clear();
0766     m_firstMoves.clear();
0767 
0768     /* Reset stats. */
0769 
0770     Status = NoSolutionExists;
0771     Total_positions = 0;
0772     Total_generated = 0;
0773     depth_sum = 0;
0774 }
0775 
0776 template<size_t NumberPiles>
0777 void Solver<NumberPiles>::free()
0778 {
0779     free_buckets();
0780     mm->free_clusters();
0781     mm->free_blocks();
0782     Freepos = nullptr;
0783 }
0784 
0785 template<size_t NumberPiles>
0786 SolverInterface::ExitStatus Solver<NumberPiles>::patsolve(int _max_positions)
0787 {
0788     max_positions = _max_positions;
0789 
0790     /* Initialize the suitable() macro variables. */
0791     init();
0792 
0793     /* Go to it. */
0794     doit();
0795 
0796     if (Status == SearchAborted) // thread quit
0797     {
0798         m_firstMoves.clear();
0799         m_winMoves.clear();
0800     }
0801 #if 0
0802     printf("%ld positions generated (%f).\n", Total_generated, depth_sum / Total_positions);
0803     printf("%ld unique positions.\n", Total_positions);
0804     printf("Mem_remain = %ld\n", ( long int )mm->Mem_remain);
0805 #endif
0806     free();
0807     return Status;
0808 }
0809 
0810 template<size_t NumberPiles>
0811 void Solver<NumberPiles>::stopExecution()
0812 {
0813     m_shouldEnd.store(true);
0814 }
0815 
0816 template<size_t NumberPiles>
0817 QList<MOVE> Solver<NumberPiles>::winMoves() const
0818 {
0819     return m_winMoves;
0820 }
0821 
0822 template<size_t NumberPiles>
0823 QList<MOVE> Solver<NumberPiles>::firstMoves() const
0824 {
0825     return m_firstMoves;
0826 }
0827 
0828 template<size_t NumberPiles>
0829 void Solver<NumberPiles>::print_layout()
0830 {
0831 }
0832 
0833 template<size_t NumberPiles>
0834 int Solver<NumberPiles>::translateSuit(int s)
0835 {
0836     int suit = s * 0x10;
0837     if (suit == PS_DIAMOND)
0838         return PS_CLUB;
0839     else if (suit == PS_CLUB)
0840         return PS_DIAMOND;
0841     return suit;
0842 }
0843 
0844 template<size_t NumberPiles>
0845 int Solver<NumberPiles>::translate_pile(const KCardPile *pile, card_t *w, int size)
0846 {
0847     Q_UNUSED(size);
0848     Q_ASSERT(pile->count() <= size);
0849 
0850     for (int i = 0; i < pile->count(); ++i) {
0851         KCard *c = pile->at(i);
0852         *w = +translateSuit(c->suit()) + c->rank();
0853         if (!c->isFaceUp())
0854             *w += 1 << 7;
0855         w++;
0856     }
0857     return pile->count();
0858 }
0859 
0860 /* Insert key into the tree unless it's already there.  Return true if
0861 it was new. */
0862 
0863 template<size_t NumberPiles>
0864 MemoryManager::inscode Solver<NumberPiles>::insert(unsigned int *cluster, int d, TREE **node)
0865 {
0866     /* Get the cluster number from the Out cell contents. */
0867 
0868     unsigned int k = getClusterNumber();
0869     *cluster = k;
0870 
0871     /* Get the tree for this cluster. */
0872 
0873     TREELIST *tl = mm->cluster_tree(k);
0874     if (tl == nullptr) {
0875         return MemoryManager::ERR;
0876     }
0877 
0878     /* Create a compact position representation. */
0879 
0880     TREE *newtree = pack_position();
0881     if (newtree == nullptr) {
0882         return MemoryManager::ERR;
0883     }
0884     Total_generated++;
0885 
0886     MemoryManager::inscode i2 = mm->insert_node(newtree, d, &tl->tree, node);
0887 
0888     if (i2 != MemoryManager::NEW) {
0889         mm->give_back_block((quint8 *)newtree);
0890     }
0891 
0892     return i2;
0893 }
0894 
0895 template<size_t NumberPiles>
0896 POSITION *Solver<NumberPiles>::new_position(POSITION *parent, MOVE *m)
0897 {
0898     unsigned int depth, cluster;
0899     quint8 *p;
0900     POSITION *pos;
0901     TREE *node;
0902 
0903     /* Search the list of stored positions.  If this position is found,
0904     then ignore it and return (unless this position is better). */
0905 
0906     if (parent == nullptr) {
0907         depth = 0;
0908     } else {
0909         depth = parent->depth + 1;
0910     }
0911     MemoryManager::inscode i = insert(&cluster, depth, &node);
0912     if (i == MemoryManager::NEW) {
0913         Total_positions++;
0914         depth_sum += depth;
0915     } else
0916         return nullptr;
0917 
0918     /* A new or better position.  insert() already stashed it in the
0919     tree, we just have to wrap a POSITION struct around it, and link it
0920     into the move stack.  Store the temp cells after the POSITION. */
0921 
0922     if (Freepos) {
0923         p = (quint8 *)Freepos;
0924         Freepos = Freepos->queue;
0925     } else {
0926         p = mm->new_from_block(Posbytes);
0927         if (p == nullptr) {
0928             Status = UnableToDetermineSolvability;
0929             return nullptr;
0930         }
0931     }
0932 
0933     pos = (POSITION *)p;
0934     pos->queue = nullptr;
0935     pos->parent = parent;
0936     pos->node = node;
0937     pos->move = *m; /* struct copy */
0938     pos->cluster = cluster;
0939     pos->depth = depth;
0940     pos->nchild = 0;
0941 #if 0
0942         QString dummy;
0943         quint16 *t = ( quint16* )( ( char* )node + sizeof( TREE ) );
0944         for ( int i = 0; i < NumberPiles; ++i )
0945         {
0946             QString s = "      " + QString( "%1" ).arg( ( int )t[i] );
0947             dummy += s.right( 5 );
0948         }
0949         if ( Total_positions % 1000 == 1000 )
0950             print_layout();
0951         //qCDebug(KPAT_LOG) << "new" << dummy;
0952 #endif
0953     p += sizeof(POSITION);
0954     return pos;
0955 }
0956 
0957 /* Hash the whole layout.  This is called once, at the start. */
0958 
0959 template<size_t NumberPiles>
0960 void Solver<NumberPiles>::hash_layout(void)
0961 {
0962     for (size_t w = 0; w < NumberPiles; w++) {
0963         hashpile(w);
0964     }
0965 }
0966 
0967 template<size_t NumberPiles>
0968 void Solver<NumberPiles>::prioritize(MOVE *, int)
0969 {
0970 }
0971 
0972 constexpr auto Nwpiles = 8;
0973 constexpr auto Ntpiles = 4;
0974 
0975 template class Solver<9>;
0976 template class Solver<10>;
0977 template class Solver<7 * 3 + 1>;
0978 template class Solver<8 + 1 + 8>;
0979 template class Solver<6>;
0980 template class Solver<34>;
0981 template class Solver<15>;
0982 template class Solver<7>;
0983 template class Solver<13>;
0984 template class Solver<20>;
0985 template class Solver<Nwpiles + Ntpiles>;