File indexing completed on 2024-06-09 04:03:38

0001 /* Copyright (C) 2023 Stephan Kulow <coolo@kde.org>
0002    SPDX-License-Identifier: GPL-2.0-or-later
0003 */
0004 
0005 #include "spidersolver2.h"
0006 #include "../spider.h"
0007 #include <QDebug>
0008 #include <algorithm>
0009 #include <cstdlib>
0010 #include <cstring>
0011 #include <iostream>
0012 // #include <time.h>
0013 #include <unordered_map>
0014 #include <unordered_set>
0015 
0016 namespace spidersolver2
0017 {
0018 static volatile bool stop_exec = false;
0019 
0020 enum Suit { Clubs = 0, Diamonds, Hearts, Spades };
0021 enum Rank { None = 0, Ace = 1, Two = 2, Three = 3, Four = 4, Five = 5, Six = 6, Seven = 7, Eight = 8, Nine = 9, Ten = 10, Jack = 11, Queen = 12, King = 13 };
0022 
0023 // give magic numbers a name
0024 const int REDEALS_NR = 6;
0025 const int MAX_MOVES = 230;
0026 
0027 struct Card {
0028     // 4 bits rank
0029     // 2 bits suit
0030     // 1 bit faceup
0031     // 1 bit unknown
0032     unsigned char value;
0033 
0034     Card()
0035     {
0036         value = 0;
0037     }
0038 
0039     Card(unsigned char v)
0040     {
0041         value = v;
0042     }
0043 
0044     Card(KCard *c)
0045     {
0046         value = 0;
0047         set_faceup(c->isFaceUp());
0048         set_rank(Rank(c->rank()));
0049         set_suit(Suit(c->suit()));
0050     }
0051 
0052     inline bool is_faceup() const
0053     {
0054         return (value & (1 << 6)) > 0;
0055     }
0056 
0057     void set_faceup(bool face)
0058     {
0059         if (face) {
0060             value = value | (1 << 6);
0061         } else {
0062             value = value & ~(1 << 6);
0063         }
0064     }
0065 
0066     inline bool is_unknown() const
0067     {
0068         return (value & (1 << 7)) > 0;
0069     }
0070 
0071     void set_unknown(bool unknown)
0072     {
0073         if (unknown) {
0074             value = value | (1 << 7);
0075         } else {
0076             value = value & ~(1 << 7);
0077         }
0078     }
0079 
0080     inline Rank rank() const
0081     {
0082         return Rank(value & 15);
0083     }
0084 
0085     void set_rank(Rank rank)
0086     {
0087         value = (value & ~15) + rank;
0088     }
0089 
0090     inline Suit suit() const
0091     {
0092         return Suit((value >> 4) & 3);
0093     }
0094 
0095     void set_suit(Suit suit)
0096     {
0097         Rank _rank = rank();
0098         value = value >> 4;
0099         value = (value & ~3) + suit;
0100         value = (value << 4) + _rank;
0101     }
0102 
0103     bool inSequenceTo(const Card &other) const;
0104     std::string toString() const;
0105     unsigned char raw_value() const
0106     {
0107         return value;
0108     }
0109     bool operator==(const Card &rhs) const;
0110 };
0111 
0112 inline bool Card::inSequenceTo(const Card &other) const
0113 {
0114     return other.is_faceup() && other.suit() == suit() && other.rank() == rank() + 1;
0115 }
0116 
0117 std::string Card::toString() const
0118 {
0119     if (is_unknown() && is_faceup())
0120         return "XX";
0121     if (is_unknown())
0122         return "|XX";
0123 
0124     std::string ret;
0125     switch (rank()) {
0126     case Ace:
0127         ret += 'A';
0128         break;
0129     case Jack:
0130         ret += 'J';
0131         break;
0132     case Queen:
0133         ret += 'Q';
0134         break;
0135     case King:
0136         ret += 'K';
0137         break;
0138     case Ten:
0139         ret += 'T';
0140         break;
0141     case None:
0142         ret += 'N';
0143         break;
0144     default:
0145         if (rank() < 2 || rank() > 9) {
0146             printf("rank is out of range\n");
0147             exit(1);
0148         }
0149         ret += ('0' + rank());
0150         break;
0151     }
0152     switch (suit()) {
0153     case Spades:
0154         ret += "S";
0155         break;
0156     case Hearts:
0157         ret += "H";
0158         break;
0159     case Diamonds:
0160         ret += "D";
0161         break;
0162     case Clubs:
0163         ret += "C";
0164         break;
0165     default:
0166         std::cerr << "Invalid suit " << suit() << std::endl;
0167         exit(1);
0168     }
0169     if (!is_faceup())
0170         ret = "|" + ret;
0171     return ret;
0172 }
0173 
0174 // to remove known cards from partly unknown decks. We don't care for faceup and unknown
0175 bool Card::operator==(const Card &rhs) const
0176 {
0177     return suit() == rhs.suit() && rank() == rhs.rank();
0178 }
0179 
0180 struct Move {
0181     bool off;
0182     bool talon;
0183     unsigned char from;
0184     unsigned char to;
0185     unsigned char index;
0186     Move()
0187     {
0188         talon = false;
0189         off = false;
0190         from = -1;
0191         to = -1;
0192         index = 0;
0193     }
0194     Move(bool _off, bool _talon, int _from, int _to, int _index)
0195     {
0196         off = _off;
0197         talon = _talon;
0198         from = _from;
0199         to = _to;
0200         index = _index;
0201     }
0202     static Move fromTalon(int talon)
0203     {
0204         return Move(false, true, talon, 0, 0);
0205     }
0206     static Move toOff(int from, int index)
0207     {
0208         return Move(true, false, from, 0, index);
0209     }
0210     static Move regular(int from, int to, int index)
0211     {
0212         return Move(false, false, from, to, index);
0213     }
0214     MOVE to_MOVE() const;
0215 };
0216 
0217 MOVE Move::to_MOVE() const
0218 {
0219     MOVE ret;
0220     ret.card_index = index;
0221     ret.from = from;
0222     if (talon) {
0223         ret.from = 11;
0224     }
0225     ret.to = to;
0226     if (off) {
0227         ret.totype = O_Type;
0228         ret.pri = 127;
0229     } else {
0230         ret.totype = W_Type;
0231     }
0232     return ret;
0233 }
0234 
0235 const int MAX_CARDS = 104;
0236 
0237 class Pile;
0238 class Pile
0239 {
0240     friend class MemoryManager;
0241 
0242 public:
0243     Pile()
0244     {
0245         count = 0;
0246     }
0247     const Pile *addCard(const Card &c) const;
0248     std::string toString() const;
0249     bool empty() const
0250     {
0251         return count == 0;
0252     }
0253     const Card at(int index) const
0254     {
0255         return Card(cards[index]);
0256     }
0257 
0258     inline size_t cardCount() const
0259     {
0260         return count;
0261     }
0262     const Pile *remove(int index) const;
0263     const Pile *copyFrom(const Pile *from, int index) const;
0264     const Pile *replaceAt(int index, const Card &c) const;
0265     int chaos() const
0266     {
0267         return m_chaos;
0268     }
0269     void calculateChaos();
0270     int sequenceOf(Suit suit) const
0271     {
0272         return m_seqs[suit];
0273     }
0274     int playableCards() const;
0275     uint64_t hash() const
0276     {
0277         return m_hash;
0278     }
0279 
0280 private:
0281     int m_chaos;
0282     unsigned char cards[MAX_CARDS];
0283     size_t count;
0284     uint64_t m_hash;
0285     int m_seqs[4];
0286     int sequenceOf_(Suit suit) const;
0287     void setAt(int index, const Card &c)
0288     {
0289         cards[index] = c.raw_value();
0290     }
0291 };
0292 
0293 class Deck
0294 {
0295 private:
0296     // Deck(const Deck &other);
0297 
0298 public:
0299     Deck()
0300     {
0301         // leaving the pointers stale on purpose
0302         // for performance
0303     }
0304 
0305     std::string toString() const;
0306     void update(const Deck *);
0307     void translate(const Spider *dealer);
0308     void getMoves(std::vector<Move> &moves) const;
0309     void applyMove(const Move &m, Deck &newdeck) const;
0310     uint64_t id() const;
0311     int leftTalons() const;
0312     int chaos() const;
0313     SolverInterface::ExitStatus shortestPath();
0314     int playableCards() const;
0315     int inOff() const;
0316     int freePlays() const;
0317     void makeEmpty();
0318     std::vector<Card> parse(int game_type, const std::string &filename);
0319     std::vector<Move> getWinMoves() const;
0320 
0321 private:
0322     const Pile *play[10];
0323     const Pile *talon[5];
0324     const Pile *off;
0325     Move moves[MAX_MOVES];
0326     int moves_index;
0327 };
0328 
0329 class MemoryManager
0330 {
0331 public:
0332     MemoryManager();
0333     ~MemoryManager();
0334     const Pile *createEmptyPile();
0335     const Pile *translatePile(const KCardPile *pile, bool reverse);
0336     const Pile *createPile(const unsigned char *cards, size_t count);
0337     Deck &deckAt(size_t index)
0338     {
0339         return deckstore[index];
0340     }
0341     void clean();
0342     static MemoryManager *self()
0343     {
0344         return m_self;
0345     }
0346 
0347 private:
0348     static MemoryManager *m_self;
0349     std::unordered_map<uint64_t, Pile *> pilemap;
0350     Deck *deckstore;
0351 };
0352 
0353 const Pile *MemoryManager::createPile(const unsigned char *cards, size_t count)
0354 {
0355     std::hash<unsigned char> hasher;
0356     std::size_t seed = 0;
0357     for (size_t i = 0; i < count; i++) {
0358         seed ^= hasher(cards[i]) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
0359     }
0360     auto search = pilemap.find(seed);
0361     if (search != pilemap.end())
0362         return search->second;
0363     Pile *p = new Pile;
0364     memcpy(p->cards, cards, count);
0365     p->count = count;
0366     p->calculateChaos();
0367     p->m_hash = seed;
0368     p->m_seqs[Hearts] = p->sequenceOf_(Hearts);
0369     p->m_seqs[Spades] = p->sequenceOf_(Spades);
0370     p->m_seqs[Clubs] = p->sequenceOf_(Clubs);
0371     p->m_seqs[Diamonds] = p->sequenceOf_(Diamonds);
0372     pilemap.insert({seed, p});
0373     return p;
0374 }
0375 
0376 std::string Pile::toString() const
0377 {
0378     std::string ret;
0379     for (size_t i = 0; i < count; i++) {
0380         ret += " " + Card(cards[i]).toString();
0381     }
0382     return ret;
0383 }
0384 
0385 const Pile *Pile::remove(int index) const
0386 {
0387     if (index > 0) {
0388         Card c = at(index - 1);
0389         if (!c.is_faceup()) {
0390             static unsigned char newcards[MAX_CARDS];
0391             memcpy(newcards, cards, MAX_CARDS);
0392             c.set_faceup(true);
0393             newcards[index - 1] = c.raw_value();
0394             return MemoryManager::self()->createPile(newcards, index);
0395         } else {
0396             return MemoryManager::self()->createPile(cards, index);
0397         }
0398     } else {
0399         return MemoryManager::self()->createPile(cards, 0);
0400     }
0401 }
0402 
0403 const Pile *Pile::copyFrom(const Pile *from, int index) const
0404 {
0405     unsigned char newcards[MAX_CARDS];
0406     memcpy(newcards, cards, count);
0407     size_t newcount = count;
0408     for (size_t i = index; i < from->count; i++) {
0409         newcards[newcount++] = from->cards[i];
0410     }
0411     return MemoryManager::self()->createPile(newcards, newcount);
0412 }
0413 
0414 const Pile *Pile::addCard(const Card &c) const
0415 {
0416     unsigned char newcards[MAX_CARDS];
0417     memcpy(newcards, cards, MAX_CARDS);
0418     newcards[count] = c.raw_value();
0419     return MemoryManager::self()->createPile(newcards, count + 1);
0420 }
0421 
0422 void Pile::calculateChaos()
0423 {
0424     m_chaos = 0;
0425     Card lastcard;
0426     for (size_t i = 0; i < cardCount(); i++) {
0427         Card current = at(i);
0428 
0429         // first in stack
0430         if (lastcard.raw_value() == 0) {
0431             m_chaos++;
0432         } else {
0433             if (!current.inSequenceTo(lastcard)) {
0434                 m_chaos++;
0435             }
0436         }
0437         lastcard = current;
0438     }
0439 }
0440 
0441 const Pile *Pile::replaceAt(int index, const Card &c) const
0442 {
0443     unsigned char newcards[MAX_CARDS];
0444     memcpy(newcards, cards, MAX_CARDS);
0445     newcards[index] = c.raw_value();
0446     return MemoryManager::self()->createPile(newcards, count);
0447 }
0448 
0449 int Pile::sequenceOf_(Suit suit) const
0450 {
0451     int index = cardCount();
0452     if (index == 0) {
0453         return index;
0454     }
0455     index--;
0456     Card top_card = at(index);
0457     if (top_card.suit() != suit) {
0458         return 0;
0459     }
0460     while (index > 0 && top_card.inSequenceTo(at(index - 1))) {
0461         index--;
0462         top_card = at(index);
0463     }
0464     return cardCount() - index;
0465 }
0466 
0467 int Pile::playableCards() const
0468 {
0469     if (count < 2) {
0470         return count;
0471     }
0472     return sequenceOf(at(count - 1).suit());
0473 }
0474 
0475 struct WeightedDeck {
0476     unsigned char left_talons;
0477     unsigned char in_off;
0478     unsigned char free_plays;
0479     unsigned char playable;
0480     int32_t chaos;
0481     int64_t id;
0482     int32_t index;
0483 
0484     bool operator<(const WeightedDeck &rhs) const;
0485     void update(uint64_t hash);
0486 };
0487 
0488 void WeightedDeck::update(uint64_t hash)
0489 {
0490     Deck &deck = MemoryManager::self()->deckAt(index);
0491     left_talons = deck.leftTalons();
0492     id = hash;
0493     in_off = deck.inOff();
0494     free_plays = deck.freePlays();
0495     playable = deck.playableCards();
0496     chaos = deck.chaos();
0497 }
0498 
0499 // smaller is better!
0500 bool WeightedDeck::operator<(const WeightedDeck &rhs) const
0501 {
0502     if (chaos != rhs.chaos) {
0503         // smaller chaos is better
0504         return chaos < rhs.chaos;
0505     }
0506     int ready1 = playable + in_off + free_plays;
0507     int ready2 = rhs.playable + rhs.in_off + rhs.free_plays;
0508     if (ready1 != ready2) {
0509         // larger values are better
0510         return ready1 > ready2;
0511     }
0512 
0513     // once we are in straight win mode, we go differently
0514     if (chaos == 0) {
0515         int free1 = free_plays;
0516         int free2 = rhs.free_plays;
0517 
0518         if (free1 != free2) {
0519             // more free is better
0520             return free1 > free2;
0521         }
0522         // if the number of empty plays is equal, less in the off
0523         // is actually a benefit (more strongly ordered)
0524         int off1 = in_off;
0525         int off2 = rhs.in_off;
0526         if (off1 != off2) {
0527             return off1 < off2;
0528         }
0529     }
0530     // give a reproducible sort order, but std::sort doesn't give
0531     // guarantess for equal items, so prefer them being different
0532     return id < rhs.id;
0533 }
0534 
0535 std::string Deck::toString() const
0536 {
0537     std::string ret;
0538     char buffer[200];
0539     int counter = 0;
0540     for (int i = 0; i < 10; i++) {
0541         snprintf(buffer, sizeof(buffer), "Play%d:", i);
0542         ret += buffer;
0543         ret += play[i]->toString();
0544         ret += "\n";
0545     }
0546 
0547     for (int i = 0; i < 5; i++) {
0548         snprintf(buffer, sizeof(buffer), "Deal%d:", i);
0549         ret += buffer;
0550 
0551         ret += talon[i]->toString();
0552         ret += "\n";
0553         counter++;
0554     }
0555 
0556     ret += "Off:";
0557     ret += off->toString();
0558     ret += "\n";
0559 
0560     return ret;
0561 }
0562 
0563 void Deck::getMoves(std::vector<Move> &moves) const
0564 {
0565     moves.clear();
0566     if (moves_index >= MAX_MOVES - 1) {
0567         return;
0568     }
0569 
0570     int next_talon = -1;
0571     for (int i = 0; i < 5; i++) {
0572         if (!talon[i]->empty()) {
0573             next_talon = i;
0574             break;
0575         }
0576     }
0577 
0578     int from = 0;
0579     bool one_is_empty = false;
0580     for (; from < 10; from++) {
0581         if (play[from]->empty()) {
0582             one_is_empty = true;
0583             continue;
0584         }
0585 
0586         int index = play[from]->cardCount() - 1;
0587         Suit top_suit = play[from]->at(index).suit();
0588         int top_rank = int(play[from]->at(index).rank()) - 1;
0589 
0590         while (index >= 0) {
0591             Card current = play[from]->at(index);
0592             if (!current.is_faceup())
0593                 break;
0594             if (current.suit() != top_suit)
0595                 break;
0596             if (top_rank + 1 != current.rank())
0597                 break;
0598             top_rank = current.rank();
0599 
0600             if (play[from]->cardCount() - index == 13) {
0601                 moves.clear();
0602                 moves.push_back(Move::toOff(from, index));
0603                 return;
0604             }
0605             int broken_sequence = 0;
0606             if (index > 0) {
0607                 Card next_card = play[from]->at(index - 1);
0608                 if (current.inSequenceTo(next_card)) {
0609                     broken_sequence = play[from]->cardCount() - index;
0610                 }
0611             }
0612             bool moved_to_empty = false;
0613             for (int to = 0; to < 10; to++) {
0614                 if (to == from)
0615                     continue;
0616                 int to_count = play[to]->cardCount();
0617                 if (to_count > 0) {
0618                     Card top_card = play[to]->at(to_count - 1);
0619                     if (top_card.rank() != top_rank + 1)
0620                         continue;
0621                     // make sure we only enlarge sequences
0622                     if (broken_sequence > 0) {
0623                         if (play[to]->sequenceOf(top_suit) + broken_sequence <= play[from]->sequenceOf(top_suit)) {
0624                             continue;
0625                         }
0626                     }
0627                 } else if (moved_to_empty) {
0628                     if (next_talon < 0)
0629                         continue;
0630                 } else {
0631                     // while talons are there, optimisations are evil
0632                     // but in end game we have more options
0633                     if (next_talon < 0) {
0634                         if (index == 0) {
0635                             // forbid moves between empty cells once the talons are gone
0636                             continue;
0637                         }
0638                         // there is no plausible reason to split up sequences in end game
0639                         if (broken_sequence > 0) {
0640                             continue;
0641                         }
0642                     }
0643                     moved_to_empty = true;
0644                 }
0645 
0646                 moves.push_back(Move::regular(from, to, index));
0647             }
0648             index--;
0649         }
0650     }
0651 
0652     if (!one_is_empty && next_talon >= 0) {
0653         moves.push_back(Move::fromTalon(next_talon));
0654     }
0655 }
0656 
0657 void Deck::update(const Deck *other)
0658 {
0659     if (this == other)
0660         return;
0661     memcpy(moves, other->moves, sizeof(Move) * other->moves_index);
0662     moves_index = other->moves_index;
0663     for (int i = 0; i < 10; i++)
0664         play[i] = other->play[i];
0665     for (int i = 0; i < 5; i++)
0666         talon[i] = other->talon[i];
0667     off = other->off;
0668 }
0669 
0670 std::vector<Move> Deck::getWinMoves() const
0671 {
0672     std::vector<Move> res;
0673     for (int i = 0; i < moves_index; i++) {
0674         res.emplace_back(moves[i]);
0675     }
0676     return res;
0677 }
0678 
0679 void Deck::applyMove(const Move &m, Deck &newdeck) const
0680 {
0681     newdeck.update(this);
0682     newdeck.moves[moves_index] = m;
0683     newdeck.moves_index = moves_index + 1;
0684 
0685     if (m.talon) {
0686         for (int to = 0; to < 10; to++) {
0687             Card c = newdeck.talon[m.from]->at(to);
0688             c.set_faceup(true);
0689             newdeck.play[to] = newdeck.play[to]->addCard(c);
0690         }
0691         // empty pile
0692         newdeck.talon[m.from] = MemoryManager::self()->createEmptyPile();
0693     } else if (m.off) {
0694         Card c = newdeck.play[m.from]->at(newdeck.play[m.from]->cardCount() - 13);
0695         newdeck.off = newdeck.off->addCard(c);
0696         newdeck.play[m.from] = newdeck.play[m.from]->remove(m.index);
0697     } else {
0698         newdeck.play[m.to] = newdeck.play[m.to]->copyFrom(newdeck.play[m.from], m.index);
0699         newdeck.play[m.from] = newdeck.play[m.from]->remove(m.index);
0700     }
0701 }
0702 
0703 template<class T>
0704 inline void hash_combine(std::size_t &seed, const T &v)
0705 {
0706     std::hash<T> hasher;
0707     seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
0708 }
0709 
0710 uint64_t Deck::id() const
0711 {
0712     std::size_t hash = 0;
0713     for (int i = 0; i < 10; i++) {
0714         hash_combine(hash, play[i]->hash());
0715     }
0716     for (int i = 0; i < 5; i++) {
0717         hash_combine(hash, talon[i]->hash());
0718     }
0719     return hash;
0720 }
0721 
0722 int Deck::leftTalons() const
0723 {
0724     int talons = 0;
0725     for (int i = 0; i < 5; i++) {
0726         if (!talon[i]->empty()) {
0727             talons++;
0728         }
0729     }
0730     return talons;
0731 }
0732 
0733 int Deck::chaos() const
0734 {
0735     int chaos = 0;
0736     for (int i = 0; i < 10; i++) {
0737         chaos += play[i]->chaos();
0738     }
0739     // per non-empty pile the chaos is at minimum 1
0740     // but if the pile is connected, we substract one
0741     // obvious wins are chaos 0
0742     for (int i = 0; i < 10; i++) {
0743         if (play[i]->cardCount() == 0) {
0744             continue;
0745         }
0746         Card c1 = play[i]->at(0);
0747 
0748         if (c1.rank() == 13) {
0749             chaos -= 1;
0750             continue;
0751         }
0752 
0753         for (int j = 0; j < 10; j++) {
0754             if (j == i) {
0755                 continue;
0756             }
0757             if (play[j]->empty()) {
0758                 continue;
0759             }
0760             // we don't need the suit here
0761             if (c1.rank() == play[j]->at(play[j]->cardCount() - 1).rank() - 1) {
0762                 chaos--;
0763                 break;
0764             }
0765         }
0766     }
0767     int fp = freePlays();
0768     while (fp > 0 && chaos > 0) {
0769         fp--;
0770         chaos--;
0771     }
0772     return chaos;
0773 }
0774 
0775 SolverInterface::ExitStatus Deck::shortestPath()
0776 {
0777     int depth = 1;
0778     bool discarded_siblings = false;
0779 
0780     Deck *unvisited = new Deck[REDEALS_NR * LEVEL_CAP];
0781     int unvisited_count[6] = {0, 0, 0, 0, 0, 0};
0782     int unvisited_count_total = 0;
0783     unvisited[unvisited_count_total++].update(this);
0784 
0785     std::unordered_set<uint64_t> seen;
0786     const int max_new_unvisited = LEVEL_CAP * REDEALS_NR * AVG_OPTIONS;
0787     WeightedDeck *new_unvisited = new WeightedDeck[max_new_unvisited];
0788     for (int i = 0; i < max_new_unvisited; i++) {
0789         new_unvisited[i].index = i;
0790     }
0791     int new_unvisited_counter = 0;
0792     std::vector<Move> current_moves;
0793     while (!stop_exec) {
0794         for (int i = 0; i < unvisited_count_total; i++) {
0795             unvisited[i].getMoves(current_moves);
0796             for (const Move &m : current_moves) {
0797                 size_t deck_index = new_unvisited[new_unvisited_counter].index;
0798                 Deck &deck = MemoryManager::self()->deckAt(deck_index);
0799                 unvisited[i].applyMove(m, deck);
0800                 uint64_t hash = deck.id();
0801 
0802                 if (seen.find(hash) != seen.end())
0803                     continue;
0804 
0805                 new_unvisited[new_unvisited_counter++].update(hash);
0806                 seen.insert(hash);
0807                 if (max_new_unvisited == new_unvisited_counter) {
0808                     std::cerr << "Too many unvisted " << new_unvisited_counter << " AVG too small" << std::endl;
0809                     return SolverInterface::MemoryLimitReached;
0810                 }
0811             }
0812         }
0813         for (int lt = 0; lt <= 5; lt++)
0814             unvisited_count[lt] = 0;
0815 
0816         unvisited_count_total = 0;
0817 
0818         if (new_unvisited_counter == 0)
0819             break;
0820 
0821         bool printed = false;
0822         std::sort(new_unvisited, new_unvisited + new_unvisited_counter);
0823         for (int i = 0; i < new_unvisited_counter; i++) {
0824             if (!printed) {
0825 #if 0
0826                 std::cout << "DEPTH " << depth << " " << new_unvisited_counter << " chaos: " << new_unvisited[i].chaos << " " << int(new_unvisited[i].playable)
0827                           << std::endl;
0828 #endif
0829                 printed = true;
0830             }
0831             if (new_unvisited[i].in_off == 104) {
0832                 Deck &deck = MemoryManager::self()->deckAt(new_unvisited[i].index);
0833                 memcpy(moves, deck.moves, sizeof(Move) * deck.moves_index);
0834                 moves_index = deck.moves_index;
0835 
0836                 delete[] unvisited;
0837                 delete[] new_unvisited;
0838                 return SolverInterface::SolutionExists;
0839             }
0840             int lt = new_unvisited[i].left_talons;
0841             if (unvisited_count[lt] < LEVEL_CAP) {
0842                 Deck &deck = MemoryManager::self()->deckAt(new_unvisited[i].index);
0843                 unvisited[unvisited_count_total++].update(&deck);
0844                 unvisited_count[lt]++;
0845             } else {
0846                 discarded_siblings = true;
0847             }
0848         }
0849 
0850         new_unvisited_counter = 0;
0851         depth += 1;
0852     }
0853     delete[] unvisited;
0854     delete[] new_unvisited;
0855     if (new_unvisited_counter == 0 && !discarded_siblings) {
0856         return SolverInterface::NoSolutionExists;
0857     }
0858     return SolverInterface::UnableToDetermineSolvability;
0859 }
0860 
0861 int Deck::playableCards() const
0862 {
0863     int result = 0;
0864     for (int i = 0; i < 10; i++)
0865         result += play[i]->playableCards();
0866     return result;
0867 }
0868 
0869 int Deck::inOff() const
0870 {
0871     return off->cardCount() * 13;
0872 }
0873 
0874 int Deck::freePlays() const
0875 {
0876     int result = 0;
0877     for (int i = 0; i < 10; i++) {
0878         if (play[i]->empty()) {
0879             result++;
0880         }
0881     }
0882     return result;
0883 }
0884 
0885 void Deck::makeEmpty()
0886 {
0887     off = MemoryManager::self()->createEmptyPile();
0888     for (int i = 0; i < 10; i++)
0889         play[i] = MemoryManager::self()->createEmptyPile();
0890     for (int i = 0; i < 5; i++)
0891         talon[i] = MemoryManager::self()->createEmptyPile();
0892     moves_index = 0;
0893 }
0894 
0895 void Deck::translate(const Spider *dealer)
0896 {
0897     makeEmpty();
0898     for (int i = 0; i < 5; i++) {
0899         talon[i] = MemoryManager::self()->translatePile(dealer->getRedeal(i), true);
0900     }
0901     for (int i = 0; i < 10; i++) {
0902         play[i] = MemoryManager::self()->translatePile(dealer->getStack(i), false);
0903     }
0904     for (int i = 0; i < 8; i++) {
0905         if (dealer->getLeg(i)->isEmpty())
0906             continue;
0907         off = off->addCard(Card(dealer->getLeg(i)->topCard()));
0908     }
0909     // std::cout << toString();
0910 }
0911 
0912 SpiderSolver2::SpiderSolver2(Spider *dealer)
0913 {
0914     m_dealer = dealer;
0915     orig = new Deck();
0916     // singleton
0917     memManager = new MemoryManager();
0918 }
0919 
0920 SpiderSolver2::~SpiderSolver2()
0921 {
0922     delete orig;
0923     delete memManager;
0924 }
0925 
0926 SolverInterface::ExitStatus SpiderSolver2::patsolve(int max_positions)
0927 {
0928     m_winMoves.clear();
0929     if (max_positions > 0) {
0930         // rely on brute force hints - they are good enough
0931         return UnableToDetermineSolvability;
0932     }
0933     stop_exec = false;
0934 
0935     ExitStatus ret = orig->shortestPath();
0936     if (ret == SolutionExists) {
0937         for (auto &move : orig->getWinMoves())
0938             m_winMoves.push_back(move.to_MOVE());
0939     }
0940     return ret;
0941 }
0942 
0943 void SpiderSolver2::translate_layout()
0944 {
0945     MemoryManager::self()->clean();
0946     orig->translate(m_dealer);
0947 }
0948 
0949 MoveHint SpiderSolver2::translateMove(const MOVE &m)
0950 {
0951     const PatPile *frompile = nullptr;
0952     if (m.from < 10)
0953         frompile = m_dealer->getStack(m.from);
0954     else
0955         return MoveHint();
0956 
0957     KCard *card = frompile->at(m.card_index);
0958     Q_ASSERT(card);
0959     Q_ASSERT(m.to < 10);
0960     if (m.totype == W_Type)
0961         return MoveHint(card, m_dealer->getStack(m.to), m.pri);
0962 
0963     // auto move to the off
0964     return MoveHint();
0965 }
0966 
0967 void SpiderSolver2::stopExecution()
0968 {
0969     stop_exec = true;
0970 }
0971 
0972 MemoryManager *MemoryManager::m_self = nullptr;
0973 MemoryManager::MemoryManager()
0974 {
0975     Q_ASSERT(!m_self);
0976     m_self = this;
0977     size_t deckstore_size = LEVEL_CAP * REDEALS_NR * AVG_OPTIONS;
0978     // do not call constructors!!
0979     deckstore = (Deck *)malloc(sizeof(Deck) * deckstore_size);
0980 }
0981 
0982 void MemoryManager::clean()
0983 {
0984     for (auto &it : pilemap) {
0985         delete it.second;
0986     }
0987     pilemap.clear();
0988 }
0989 
0990 MemoryManager::~MemoryManager()
0991 {
0992     clean();
0993     free(deckstore);
0994     m_self = nullptr;
0995 }
0996 
0997 const Pile *MemoryManager::createEmptyPile()
0998 {
0999     unsigned char newcards[2] = {0, 0};
1000     return createPile(newcards, 0);
1001 }
1002 
1003 const Pile *MemoryManager::translatePile(const KCardPile *pile, bool reverse)
1004 {
1005     unsigned char newcards[MAX_CARDS];
1006     size_t count = 0;
1007     size_t len = pile->cards().length();
1008     for (auto &card : pile->cards()) {
1009         size_t index = count;
1010         if (reverse) {
1011             index = len - count - 1;
1012         }
1013         newcards[index] = Card(card).raw_value();
1014         count++;
1015     }
1016     return createPile(newcards, count);
1017 }
1018 
1019 }