diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index 5dffff3f7..a7dd01702 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -27,6 +27,8 @@ add_library( libcockatrice/deck_list/tree/inner_deck_list_node.cpp libcockatrice/deck_list/deck_list.cpp libcockatrice/deck_list/deck_list_history_manager.cpp + libcockatrice/deck_list/deck_list_node_tree.cpp + libcockatrice/deck_list/deck_list_node_tree.h ) add_dependencies(libcockatrice_deck_list libcockatrice_protocol) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 09326e619..54eaa3096 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -83,26 +83,23 @@ bool DeckList::Metadata::isEmpty() const } DeckList::DeckList() -{ - root = new InnerDecklistNode; -} - -DeckList::DeckList(const DeckList &other) - : metadata(other.metadata), sideboardPlans(other.sideboardPlans), root(new InnerDecklistNode(other.getRoot())), - cachedDeckHash(other.cachedDeckHash) { } DeckList::DeckList(const QString &nativeString) { - root = new InnerDecklistNode; loadFromString_Native(nativeString); } +DeckList::DeckList(const Metadata &metadata, + const DecklistNodeTree &tree, + const QMap &sideboardPlans) + : metadata(metadata), sideboardPlans(sideboardPlans), tree(tree) +{ +} + DeckList::~DeckList() { - delete root; - QMapIterator i(sideboardPlans); while (i.hasNext()) delete i.next().value(); @@ -152,8 +149,7 @@ bool DeckList::readElement(QXmlStreamReader *xml) } } } else if (childName == "zone") { - InnerDecklistNode *newZone = getZoneObjFromName(xml->attributes().value("name").toString()); - newZone->readElement(xml); + tree.readZoneElement(xml); } else if (childName == "sideboard_plan") { SideboardPlan *newSideboardPlan = new SideboardPlan; if (newSideboardPlan->readElement(xml)) { @@ -195,9 +191,7 @@ void DeckList::write(QXmlStreamWriter *xml) const writeMetadata(xml, metadata); // Write zones - for (int i = 0; i < root->size(); i++) { - root->at(i)->writeElement(xml); - } + tree.write(xml); // Write sideboard plans QMapIterator i(sideboardPlans); @@ -456,25 +450,13 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; // make new entry in decklist - new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), -1, setCode, collectorNumber); + tree.addCard(cardName, amount, zoneName, -1, setCode, collectorNumber); } refreshDeckHash(); return true; } -InnerDecklistNode *DeckList::getZoneObjFromName(const QString &zoneName) -{ - for (int i = 0; i < root->size(); i++) { - auto *node = dynamic_cast(root->at(i)); - if (node->getName() == zoneName) { - return node; - } - } - - return new InnerDecklistNode(zoneName, root); -} - bool DeckList::loadFromFile_Plain(QIODevice *device) { QTextStream in(device); @@ -519,7 +501,7 @@ QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappe */ void DeckList::cleanList(bool preserveMetadata) { - root->clearTree(); + tree.clear(); if (!preserveMetadata) { metadata = {}; } @@ -528,7 +510,7 @@ void DeckList::cleanList(bool preserveMetadata) QStringList DeckList::getCardList() const { - auto nodes = getCardNodes(); + auto nodes = tree.getCardNodes(); QStringList result; std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), [](auto node) { return node->getName(); }); @@ -538,7 +520,7 @@ QStringList DeckList::getCardList() const QList DeckList::getCardRefList() const { - auto nodes = getCardNodes(); + auto nodes = tree.getCardNodes(); QList result; std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), @@ -547,42 +529,19 @@ QList DeckList::getCardRefList() const return result; } -QList DeckList::getCardNodes(const QStringList &restrictToZones) const +QList DeckList::getCardNodes(const QSet &restrictToZones) const { - QList result; - - auto zoneNodes = getZoneNodes(); - for (auto *zoneNode : zoneNodes) { - if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) { - continue; - } - for (auto *cardNode : *zoneNode) { - auto *cardCardNode = dynamic_cast(cardNode); - if (cardCardNode != nullptr) { - result.append(cardCardNode); - } - } - } - - return result; + return tree.getCardNodes(restrictToZones); } QList DeckList::getZoneNodes() const { - QList zones; - for (auto *node : *root) { - InnerDecklistNode *currentZone = dynamic_cast(node); - if (!currentZone) - continue; - zones.append(currentZone); - } - - return zones; + return tree.getZoneNodes(); } int DeckList::getSideboardSize() const { - auto cards = getCardNodes({DECK_ZONE_SIDE}); + auto cards = tree.getCardNodes({DECK_ZONE_SIDE}); int size = 0; for (auto card : cards) { @@ -594,93 +553,18 @@ int DeckList::getSideboardSize() const DecklistCardNode *DeckList::addCard(const QString &cardName, const QString &zoneName, - const int position, + int position, const QString &cardSetName, const QString &cardSetCollectorNumber, const QString &cardProviderId, - const bool formatLegal) + bool formatLegal) { - auto *zoneNode = dynamic_cast(root->findChild(zoneName)); - if (zoneNode == nullptr) { - zoneNode = new InnerDecklistNode(zoneName, root); - } - - auto *node = new DecklistCardNode(cardName, 1, zoneNode, position, cardSetName, cardSetCollectorNumber, - cardProviderId, formatLegal); + auto node = + tree.addCard(cardName, 1, zoneName, position, cardSetName, cardSetCollectorNumber, cardProviderId, formatLegal); refreshDeckHash(); - return node; } -bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode) -{ - if (node == root) { - return true; - } - - bool updateHash = false; - if (rootNode == nullptr) { - rootNode = root; - updateHash = true; - } - - int index = rootNode->indexOf(node); - if (index != -1) { - delete rootNode->takeAt(index); - - if (rootNode->empty()) { - deleteNode(rootNode, rootNode->getParent()); - } - - if (updateHash) { - refreshDeckHash(); - } - - return true; - } - - for (int i = 0; i < rootNode->size(); i++) { - auto *inner = dynamic_cast(rootNode->at(i)); - if (inner) { - if (deleteNode(node, inner)) { - if (updateHash) { - refreshDeckHash(); - } - - return true; - } - } - } - - return false; -} - -static QString computeDeckHash(const DeckList &deckList) -{ - auto mainDeckNodes = deckList.getCardNodes({DECK_ZONE_MAIN}); - auto sideDeckNodes = deckList.getCardNodes({DECK_ZONE_SIDE}); - - static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { - QStringList result; - for (auto node : nodes) { - for (int i = 0; i < node->getNumber(); ++i) { - result.append(prefix + node->getName().toLower()); - } - } - return result; - }; - - QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); - - cardList.sort(); - QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); - quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + - (((quint64)(unsigned char)deckHashArray[1]) << 24) + - (((quint64)(unsigned char)deckHashArray[2] << 16)) + - (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; - return QString::number(number, 32).rightJustified(8, '0'); -} - /** * Gets the deck hash. * The hash is computed on the first call to this method, and is cached until the decklist is modified. @@ -693,7 +577,7 @@ QString DeckList::getDeckHash() const return cachedDeckHash; } - cachedDeckHash = computeDeckHash(*this); + cachedDeckHash = tree.computeDeckHash(); return cachedDeckHash; } @@ -710,15 +594,7 @@ void DeckList::refreshDeckHash() */ void DeckList::forEachCard(const std::function &func) const { - // Support for this is only possible if the internal structure - // doesn't get more complicated. - for (int i = 0; i < root->size(); i++) { - InnerDecklistNode *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - DecklistCardNode *card = dynamic_cast(node->at(j)); - func(node, card); - } - } + tree.forEachCard(func); } DeckListMemento DeckList::createMemento(const QString &reason) const diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 0325b029a..e8c57be67 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -11,6 +11,7 @@ #define DECKLIST_H #include "deck_list_memento.h" +#include "deck_list_node_tree.h" #include "tree/inner_deck_list_node.h" #include @@ -107,14 +108,14 @@ public: * - Provide hashing for deck identity (deck hash). * * ### Ownership: - * - Owns the root `InnerDecklistNode` tree. + * - Owns the `DecklistNodeTree`. * - Owns `SideboardPlan` instances stored in `sideboardPlans`. * * ### Example workflow: * ``` * DeckList deck; * deck.setName("Mono Red Aggro"); - * deck.addCard("Lightning Bolt", "main", -1); + * deck.addCard("Lightning Bolt", "main"); * deck.addTag("Aggro"); * deck.saveToFile_Native(device); * ``` @@ -140,7 +141,7 @@ public: private: Metadata metadata; ///< Deck metadata that is stored in the deck file QMap sideboardPlans; ///< Named sideboard plans. - InnerDecklistNode *root; ///< Root of the deck tree (zones + cards). + DecklistNodeTree tree; ///< The deck tree (zones + cards). /** * @brief Cached deck hash, recalculated lazily. @@ -148,9 +149,6 @@ private: */ mutable QString cachedDeckHash; - // Helpers for traversing the tree - InnerDecklistNode *getZoneObjFromName(const QString &zoneName); - public: /// @name Metadata setters ///@{ @@ -190,12 +188,24 @@ public: /// @brief Construct an empty deck. explicit DeckList(); - /// @brief Copy constructor (deep copies the node tree) - DeckList(const DeckList &other); /// @brief Construct from a serialized native-format string. explicit DeckList(const QString &nativeString); + /// @brief Construct from components + DeckList(const Metadata &metadata, + const DecklistNodeTree &tree, + const QMap &sideboardPlans = {}); virtual ~DeckList(); + /** + * @brief Gets a pointer to the underlying node tree. + * Note: DO NOT call this method unless the object needs to have access to the underlying model. + * For now, only the DeckListModel should be calling this. + */ + DecklistNodeTree *getTree() + { + return &tree; + } + /// @name Metadata getters /// The individual metadata getters still exist for backwards compatibility. ///@{ @@ -270,25 +280,21 @@ public: void cleanList(bool preserveMetadata = false); bool isEmpty() const { - return root->isEmpty() && metadata.isEmpty() && sideboardPlans.isEmpty(); + return tree.isEmpty() && metadata.isEmpty() && sideboardPlans.isEmpty(); } QStringList getCardList() const; QList getCardRefList() const; - QList getCardNodes(const QStringList &restrictToZones = QStringList()) const; + QList getCardNodes(const QSet &restrictToZones = {}) const; QList getZoneNodes() const; int getSideboardSize() const; - InnerDecklistNode *getRoot() const - { - return root; - } + DecklistCardNode *addCard(const QString &cardName, const QString &zoneName, - int position, + int position = -1, const QString &cardSetName = QString(), const QString &cardSetCollectorNumber = QString(), const QString &cardProviderId = QString(), const bool formatLegal = true); - bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); ///@} /// @name Deck identity diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp new file mode 100644 index 000000000..8f651a061 --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp @@ -0,0 +1,186 @@ +#include "deck_list_node_tree.h" + +#include "tree/deck_list_card_node.h" + +#include +#include + +DecklistNodeTree::DecklistNodeTree() : root(new InnerDecklistNode()) +{ +} + +DecklistNodeTree::DecklistNodeTree(const DecklistNodeTree &other) : root(new InnerDecklistNode(other.root)) +{ +} + +DecklistNodeTree &DecklistNodeTree::operator=(const DecklistNodeTree &other) +{ + if (this != &other) { + delete root; + root = new InnerDecklistNode(other.root); + } + return *this; +} + +DecklistNodeTree::~DecklistNodeTree() +{ + delete root; +} + +bool DecklistNodeTree::isEmpty() const +{ + return root->isEmpty(); +} + +void DecklistNodeTree::clear() +{ + root->clearTree(); +} + +QList DecklistNodeTree::getCardNodes(const QSet &restrictToZones) const +{ + QList result; + + for (auto *zoneNode : getZoneNodes()) { + if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) { + continue; + } + for (auto *cardNode : *zoneNode) { + auto *cardCardNode = dynamic_cast(cardNode); + if (cardCardNode) { + result.append(cardCardNode); + } + } + } + + return result; +} + +QList DecklistNodeTree::getZoneNodes() const +{ + QList zones; + for (auto *node : *root) { + InnerDecklistNode *currentZone = dynamic_cast(node); + if (!currentZone) + continue; + zones.append(currentZone); + } + + return zones; +} + +QString DecklistNodeTree::computeDeckHash() const +{ + auto mainDeckNodes = getCardNodes({DECK_ZONE_MAIN}); + auto sideDeckNodes = getCardNodes({DECK_ZONE_SIDE}); + + static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { + QStringList result; + for (auto node : nodes) { + for (int i = 0; i < node->getNumber(); ++i) { + result.append(prefix + node->getName().toLower()); + } + } + return result; + }; + + QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); + + cardList.sort(); + QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); + quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + + (((quint64)(unsigned char)deckHashArray[1]) << 24) + + (((quint64)(unsigned char)deckHashArray[2] << 16)) + + (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; + return QString::number(number, 32).rightJustified(8, '0'); +} + +void DecklistNodeTree::write(QXmlStreamWriter *xml) const +{ + for (int i = 0; i < root->size(); i++) { + root->at(i)->writeElement(xml); + } +} + +void DecklistNodeTree::readZoneElement(QXmlStreamReader *xml) +{ + QString zoneName = xml->attributes().value("name").toString(); + InnerDecklistNode *newZone = getZoneObjFromName(zoneName); + newZone->readElement(xml); +} + +DecklistCardNode *DecklistNodeTree::addCard(const QString &cardName, + int amount, + const QString &zoneName, + int position, + const QString &cardSetName, + const QString &cardSetCollectorNumber, + const QString &cardProviderId, + const bool formatLegal) +{ + auto *zoneNode = getZoneObjFromName(zoneName); + auto *node = new DecklistCardNode(cardName, amount, zoneNode, position, cardSetName, cardSetCollectorNumber, + cardProviderId, formatLegal); + return node; +} + +bool DecklistNodeTree::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode) +{ + if (node == root) { + return true; + } + + if (rootNode == nullptr) { + rootNode = root; + } + + int index = rootNode->indexOf(node); + if (index != -1) { + delete rootNode->takeAt(index); + + if (rootNode->empty()) { + deleteNode(rootNode, rootNode->getParent()); + } + + return true; + } + + for (int i = 0; i < rootNode->size(); i++) { + auto *inner = dynamic_cast(rootNode->at(i)); + if (inner) { + if (deleteNode(node, inner)) { + return true; + } + } + } + + return false; +} + +void DecklistNodeTree::forEachCard(const std::function &func) const +{ + // Support for this is only possible if the internal structure + // doesn't get more complicated. + for (int i = 0; i < root->size(); i++) { + InnerDecklistNode *node = dynamic_cast(root->at(i)); + for (int j = 0; j < node->size(); j++) { + DecklistCardNode *card = dynamic_cast(node->at(j)); + func(node, card); + } + } +} + +/** + * Gets the InnerDecklistNode that is the root node for the given zone, creating a new node if it doesn't exist. + */ +InnerDecklistNode *DecklistNodeTree::getZoneObjFromName(const QString &zoneName) const +{ + for (int i = 0; i < root->size(); i++) { + auto *node = dynamic_cast(root->at(i)); + if (node->getName() == zoneName) { + return node; + } + } + + return new InnerDecklistNode(zoneName, root); +} diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h new file mode 100644 index 000000000..5cfd4944d --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h @@ -0,0 +1,87 @@ +#ifndef COCKATRICE_DECKLIST_NODE_TREE_H +#define COCKATRICE_DECKLIST_NODE_TREE_H + +#include "libcockatrice/utility/card_ref.h" +#include "tree/deck_list_card_node.h" +#include "tree/inner_deck_list_node.h" + +#include + +class DecklistNodeTree +{ + InnerDecklistNode *root; ///< Root of the deck tree (zones + cards). + +public: + /// @brief Constructs an empty DecklistNodeTree + explicit DecklistNodeTree(); + /// @brief Copy constructor. Deep copies the tree + explicit DecklistNodeTree(const DecklistNodeTree &other); + /// @brief Copy-assignment operator. Deep copies the tree + DecklistNodeTree &operator=(const DecklistNodeTree &other); + + virtual ~DecklistNodeTree(); + + /** + * @brief Gets a pointer to the underlying root node. + * Note: DO NOT call this method unless the object needs to have access to the underlying model. + * For now, only the DeckListModel should be calling this. + */ + InnerDecklistNode *getRoot() const + { + return root; + } + + bool isEmpty() const; + + /** + * @brief Deletes all nodes except the root. + */ + void clear(); + + /** + * Gets all card nodes in the tree + * @param restrictToZones Only get the nodes in these zones + * @return A QList containing all the card nodes in the zone. + */ + QList getCardNodes(const QSet &restrictToZones = {}) const; + + QList getZoneNodes() const; + + /** + * @brief Computes the deck hash + */ + QString computeDeckHash() const; + + /** + *@brief Writes the contents of the deck to xml + */ + void write(QXmlStreamWriter *xml) const; + + /** + * @brief Reads a "zone" section of the xml to this tree + */ + void readZoneElement(QXmlStreamReader *xml); + + DecklistCardNode *addCard(const QString &cardName, + int amount, + const QString &zoneName, + int position, + const QString &cardSetName = QString(), + const QString &cardSetCollectorNumber = QString(), + const QString &cardProviderId = QString(), + const bool formatLegal = true); + bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); + + /** + * @brief Apply a function to every card in the deck tree. This can modify the cards. + * + * @param func Function taking (zone node, card node). + */ + void forEachCard(const std::function &func) const; + +private: + // Helpers for traversing the tree + InnerDecklistNode *getZoneObjFromName(const QString &zoneName) const; +}; + +#endif // COCKATRICE_DECKLIST_NODE_TREE_H diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp index 05ee480a5..c06fb8268 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -41,7 +41,7 @@ void DeckListModel::rebuildTree() beginResetModel(); root->clearTree(); - InnerDecklistNode *listRoot = deckList->getRoot(); + InnerDecklistNode *listRoot = deckList->getTree()->getRoot(); for (int i = 0; i < listRoot->size(); i++) { auto *currentZone = dynamic_cast(listRoot->at(i)); @@ -313,7 +313,7 @@ bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent) for (int i = 0; i < count; i++) { AbstractDecklistNode *toDelete = node->takeAt(row); if (auto *temp = dynamic_cast(toDelete)) { - deckList->deleteNode(temp->getDataNode()); + deckList->getTree()->deleteNode(temp->getDataNode()); } delete toDelete; } @@ -668,7 +668,7 @@ bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardIn void DeckListModel::refreshCardFormatLegalities() { - InnerDecklistNode *listRoot = deckList->getRoot(); + InnerDecklistNode *listRoot = deckList->getTree()->getRoot(); for (int i = 0; i < listRoot->size(); i++) { auto *currentZone = static_cast(listRoot->at(i)); diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h index c03497bd5..7d8265d7a 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -202,7 +202,7 @@ public: * affects its hash. * * Slots: - * - rebuildTree(): rebuilds the model structure from the underlying DeckLoader. + * - rebuildTree(): rebuilds the model structure from the underlying node tree. */ class DeckListModel : public QAbstractItemModel { @@ -210,7 +210,7 @@ class DeckListModel : public QAbstractItemModel public slots: /** - * @brief Rebuilds the model tree from the underlying DeckLoader. + * @brief Rebuilds the model tree from the underlying node tree. * * This updates all indices and ensures the model reflects the current * state of the deck.