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>;