mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-03-21 17:55:21 -05:00
[DeckList] Refactor: Create class to RAII underlying tree (#6412)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, 11) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, 13) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, skip, 12) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, 43) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, skip, 42) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Servatrice_Debian, DEB, yes, skip, 11) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, 24.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, skip, 22.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (yes, Arch, skip) (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (Windows10-installer, true, Visual Studio 17 2022, x64, 1, Windows, -Win10, win64_msvc2019_64, qtimageformats qtmultimedia qt… (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (Windows7-installer, true, Visual Studio 17 2022, x64, 1, Windows, -Win7, win64_msvc2019_64, 5.15.*, windows-2022, 7, Release) (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (false, Ninja, macOS, clang_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, macos-15, Apple, 15, Debug, 1, 16.4) (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macOS13_Intel-package, false, Ninja, 1, macOS, 13, -macOS13_Intel, clang_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*… (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macOS14-package, false, Ninja, 1, macOS, -macOS14, clang_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, macos-14, Appl… (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macOS15-package, false, Ninja, 1, macOS, -macOS15, clang_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, macos-15, Appl… (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, 11) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, 13) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, skip, 12) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, 43) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, skip, 42) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Servatrice_Debian, DEB, yes, skip, 11) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, 24.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, skip, 22.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (yes, Arch, skip) (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (Windows10-installer, true, Visual Studio 17 2022, x64, 1, Windows, -Win10, win64_msvc2019_64, qtimageformats qtmultimedia qt… (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (Windows7-installer, true, Visual Studio 17 2022, x64, 1, Windows, -Win7, win64_msvc2019_64, 5.15.*, windows-2022, 7, Release) (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (false, Ninja, macOS, clang_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, macos-15, Apple, 15, Debug, 1, 16.4) (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macOS13_Intel-package, false, Ninja, 1, macOS, 13, -macOS13_Intel, clang_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*… (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macOS14-package, false, Ninja, 1, macOS, -macOS14, clang_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, macos-14, Appl… (push) Blocked by required conditions
Build Desktop / ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macOS15-package, false, Ninja, 1, macOS, -macOS15, clang_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, macos-15, Appl… (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [DeckList] Create class to RAII underlying tree * Update usages * fixes after rebase * update docs
This commit is contained in:
parent
589e9a15a6
commit
b29909bdbe
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<QString, SideboardPlan *> &sideboardPlans)
|
||||
: metadata(metadata), sideboardPlans(sideboardPlans), tree(tree)
|
||||
{
|
||||
}
|
||||
|
||||
DeckList::~DeckList()
|
||||
{
|
||||
delete root;
|
||||
|
||||
QMapIterator<QString, SideboardPlan *> 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<QString, SideboardPlan *> 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<InnerDecklistNode *>(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<CardRef> DeckList::getCardRefList() const
|
||||
{
|
||||
auto nodes = getCardNodes();
|
||||
auto nodes = tree.getCardNodes();
|
||||
|
||||
QList<CardRef> result;
|
||||
std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result),
|
||||
|
|
@ -547,42 +529,19 @@ QList<CardRef> DeckList::getCardRefList() const
|
|||
return result;
|
||||
}
|
||||
|
||||
QList<const DecklistCardNode *> DeckList::getCardNodes(const QStringList &restrictToZones) const
|
||||
QList<const DecklistCardNode *> DeckList::getCardNodes(const QSet<QString> &restrictToZones) const
|
||||
{
|
||||
QList<const DecklistCardNode *> result;
|
||||
|
||||
auto zoneNodes = getZoneNodes();
|
||||
for (auto *zoneNode : zoneNodes) {
|
||||
if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) {
|
||||
continue;
|
||||
}
|
||||
for (auto *cardNode : *zoneNode) {
|
||||
auto *cardCardNode = dynamic_cast<DecklistCardNode *>(cardNode);
|
||||
if (cardCardNode != nullptr) {
|
||||
result.append(cardCardNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return tree.getCardNodes(restrictToZones);
|
||||
}
|
||||
|
||||
QList<const InnerDecklistNode *> DeckList::getZoneNodes() const
|
||||
{
|
||||
QList<const InnerDecklistNode *> zones;
|
||||
for (auto *node : *root) {
|
||||
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(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<InnerDecklistNode *>(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<InnerDecklistNode *>(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<const DecklistCardNode *> &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<void(InnerDecklistNode *, DecklistCardNode *)> &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<InnerDecklistNode *>(root->at(i));
|
||||
for (int j = 0; j < node->size(); j++) {
|
||||
DecklistCardNode *card = dynamic_cast<DecklistCardNode *>(node->at(j));
|
||||
func(node, card);
|
||||
}
|
||||
}
|
||||
tree.forEachCard(func);
|
||||
}
|
||||
|
||||
DeckListMemento DeckList::createMemento(const QString &reason) const
|
||||
|
|
|
|||
|
|
@ -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 <QMap>
|
||||
|
|
@ -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<QString, SideboardPlan *> 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<QString, SideboardPlan *> &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<CardRef> getCardRefList() const;
|
||||
QList<const DecklistCardNode *> getCardNodes(const QStringList &restrictToZones = QStringList()) const;
|
||||
QList<const DecklistCardNode *> getCardNodes(const QSet<QString> &restrictToZones = {}) const;
|
||||
QList<const InnerDecklistNode *> 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
#include "deck_list_node_tree.h"
|
||||
|
||||
#include "tree/deck_list_card_node.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QSet>
|
||||
|
||||
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<const DecklistCardNode *> DecklistNodeTree::getCardNodes(const QSet<QString> &restrictToZones) const
|
||||
{
|
||||
QList<const DecklistCardNode *> result;
|
||||
|
||||
for (auto *zoneNode : getZoneNodes()) {
|
||||
if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) {
|
||||
continue;
|
||||
}
|
||||
for (auto *cardNode : *zoneNode) {
|
||||
auto *cardCardNode = dynamic_cast<DecklistCardNode *>(cardNode);
|
||||
if (cardCardNode) {
|
||||
result.append(cardCardNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<const InnerDecklistNode *> DecklistNodeTree::getZoneNodes() const
|
||||
{
|
||||
QList<const InnerDecklistNode *> zones;
|
||||
for (auto *node : *root) {
|
||||
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(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<const DecklistCardNode *> &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<InnerDecklistNode *>(rootNode->at(i));
|
||||
if (inner) {
|
||||
if (deleteNode(node, inner)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DecklistNodeTree::forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &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<InnerDecklistNode *>(root->at(i));
|
||||
for (int j = 0; j < node->size(); j++) {
|
||||
DecklistCardNode *card = dynamic_cast<DecklistCardNode *>(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<InnerDecklistNode *>(root->at(i));
|
||||
if (node->getName() == zoneName) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return new InnerDecklistNode(zoneName, root);
|
||||
}
|
||||
|
|
@ -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 <QSet>
|
||||
|
||||
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<const DecklistCardNode *> getCardNodes(const QSet<QString> &restrictToZones = {}) const;
|
||||
|
||||
QList<const InnerDecklistNode *> 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<void(InnerDecklistNode *, DecklistCardNode *)> &func) const;
|
||||
|
||||
private:
|
||||
// Helpers for traversing the tree
|
||||
InnerDecklistNode *getZoneObjFromName(const QString &zoneName) const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECKLIST_NODE_TREE_H
|
||||
|
|
@ -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<InnerDecklistNode *>(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<DecklistModelCardNode *>(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<InnerDecklistNode *>(listRoot->at(i));
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user