mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-27 00:14:40 -07:00
[DeckList] Add custom deck zones to the deck tree
Introduce user-definable zones nested under a board zone (main, side or maybeboard) so players can organize cards inside a board without changing board semantics. - addCustomZone, renameCustomZone, moveCustomZone and removeCustomZone manage zones. Names are unique across the whole deck and the standard zone names (main/side/maybeboard/tokens) stay reserved. - Board zones are created lazily on first use. - getZoneObjFromName resolves custom names to their nested node so addCard and XML loading route cards into them. Unknown names keep creating legacy top-level zones. - deleteNode keeps empty custom zones alive and only prunes empty board zones. - New deck_list_zones test suite locks hash parity with flat decks, sideboard size accounting, maybeboard exclusion from plain export and native-format round-trips. Took 17 minutes Took 11 minutes
This commit is contained in:
parent
dba7cc73a4
commit
09260ee7d9
6 changed files with 551 additions and 2 deletions
|
|
@ -145,7 +145,8 @@ bool DecklistNodeTree::deleteNode(AbstractDecklistNode *node, InnerDecklistNode
|
|||
if (index != -1) {
|
||||
delete rootNode->takeAt(index);
|
||||
|
||||
if (rootNode->empty()) {
|
||||
// Empty custom zones are kept while empty board zones get pruned.
|
||||
if (rootNode->empty() && rootNode->getParent() == root) {
|
||||
deleteNode(rootNode, rootNode->getParent());
|
||||
}
|
||||
|
||||
|
|
@ -188,15 +189,151 @@ void DecklistNodeTree::forEachCard(const std::function<void(InnerDecklistNode *,
|
|||
|
||||
/**
|
||||
* Gets the InnerDecklistNode that is the root node for the given zone, creating a new node if it doesn't exist.
|
||||
*
|
||||
* Top-level zones take precedence, then deck-unique custom zones nested under boards
|
||||
* are resolved. Unknown names create a new top-level zone (legacy behavior).
|
||||
*/
|
||||
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) {
|
||||
if (node && node->getName() == zoneName) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto *customZone = findCustomZoneByName(zoneName)) {
|
||||
return customZone;
|
||||
}
|
||||
|
||||
return new InnerDecklistNode(zoneName, root);
|
||||
}
|
||||
|
||||
InnerDecklistNode *DecklistNodeTree::addCustomZone(const QString &boardZoneName, const QString &zoneName)
|
||||
{
|
||||
if (hasZoneName(zoneName)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *boardZone = dynamic_cast<InnerDecklistNode *>(root->findChild(boardZoneName));
|
||||
if (!boardZone &&
|
||||
(boardZoneName == DECK_ZONE_MAYBEBOARD || boardZoneName == DECK_ZONE_MAIN || boardZoneName == DECK_ZONE_SIDE)) {
|
||||
// The boards are lazy zones: they only exist once cards or custom zones need them.
|
||||
boardZone = new InnerDecklistNode(boardZoneName, root);
|
||||
}
|
||||
|
||||
if (!boardZone) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new InnerDecklistNode(zoneName, boardZone);
|
||||
}
|
||||
|
||||
bool DecklistNodeTree::renameCustomZone(const QString &oldZoneName, const QString &newZoneName)
|
||||
{
|
||||
if (hasZoneName(newZoneName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *zone = findCustomZoneByName(oldZoneName);
|
||||
if (!zone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
zone->setName(newZoneName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecklistNodeTree::moveCustomZone(const QString &zoneName, const QString &newBoardZoneName)
|
||||
{
|
||||
auto *zone = findCustomZoneByName(zoneName);
|
||||
if (!zone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *currentBoardZone = zone->getParent();
|
||||
if (currentBoardZone && currentBoardZone->getName() == newBoardZoneName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto *newBoardZone = dynamic_cast<InnerDecklistNode *>(root->findChild(newBoardZoneName));
|
||||
if (!newBoardZone && (newBoardZoneName == DECK_ZONE_MAYBEBOARD || newBoardZoneName == DECK_ZONE_MAIN ||
|
||||
newBoardZoneName == DECK_ZONE_SIDE)) {
|
||||
// The boards are lazy zones: they only exist once cards or custom zones need them.
|
||||
newBoardZone = new InnerDecklistNode(newBoardZoneName, root);
|
||||
}
|
||||
if (!newBoardZone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
currentBoardZone->removeOne(zone);
|
||||
newBoardZone->append(zone);
|
||||
zone->setParent(newBoardZone);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecklistNodeTree::removeCustomZone(const QString &zoneName)
|
||||
{
|
||||
auto *zone = findCustomZoneByName(zoneName);
|
||||
if (!zone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detach and delete without pruning the board zone.
|
||||
auto *boardZone = zone->getParent();
|
||||
boardZone->removeOne(zone);
|
||||
delete zone;
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<const InnerDecklistNode *> DecklistNodeTree::getCustomZones(const QString &boardZoneName) const
|
||||
{
|
||||
QList<const InnerDecklistNode *> result;
|
||||
|
||||
auto *boardZone = dynamic_cast<InnerDecklistNode *>(root->findChild(boardZoneName));
|
||||
if (!boardZone) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (int i = 0; i < boardZone->size(); i++) {
|
||||
if (auto *customZone = dynamic_cast<InnerDecklistNode *>(boardZone->at(i))) {
|
||||
result.append(customZone);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
InnerDecklistNode *DecklistNodeTree::findCustomZoneByName(const QString &zoneName) const
|
||||
{
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
auto *boardZone = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
if (!boardZone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < boardZone->size(); j++) {
|
||||
auto *customZone = dynamic_cast<InnerDecklistNode *>(boardZone->at(j));
|
||||
if (customZone && customZone->getName() == zoneName) {
|
||||
return customZone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DecklistNodeTree::hasZoneName(const QString &zoneName) const
|
||||
{
|
||||
// The standard zones are reserved names even before they are created lazily.
|
||||
if (zoneName == DECK_ZONE_MAIN || zoneName == DECK_ZONE_SIDE || zoneName == DECK_ZONE_MAYBEBOARD ||
|
||||
zoneName == DECK_ZONE_TOKENS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (root->findChild(zoneName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return findCustomZoneByName(zoneName) != nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,43 @@ public:
|
|||
const bool formatLegal = true);
|
||||
bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Creates a new custom zone nested under a board zone.
|
||||
*
|
||||
* Custom zone names must be unique across the whole deck so that cards can be
|
||||
* added to a custom zone without specifying its board zone.
|
||||
*
|
||||
* @param boardZoneName Name of the board zone (e.g. DECK_ZONE_MAIN).
|
||||
* @param zoneName Name of the custom zone.
|
||||
* @return The created zone node, or nullptr if the name is already in use.
|
||||
*/
|
||||
InnerDecklistNode *addCustomZone(const QString &boardZoneName, const QString &zoneName);
|
||||
|
||||
/**
|
||||
* @brief Renames a custom zone.
|
||||
* @return true on success, false if the zone was not found or the new name is taken.
|
||||
*/
|
||||
bool renameCustomZone(const QString &oldZoneName, const QString &newZoneName);
|
||||
|
||||
/**
|
||||
* @brief Moves a custom zone (and all its cards) to another board zone.
|
||||
* @return true on success, false if the zone or the new board zone was not found.
|
||||
*/
|
||||
bool moveCustomZone(const QString &zoneName, const QString &newBoardZoneName);
|
||||
|
||||
/**
|
||||
* @brief Removes a custom zone and all its cards.
|
||||
* @return true if the zone was found and removed.
|
||||
*/
|
||||
bool removeCustomZone(const QString &zoneName);
|
||||
|
||||
/**
|
||||
* @brief Gets all custom zones nested under a board zone.
|
||||
* @param boardZoneName Name of the board zone.
|
||||
* @return The custom zones, in insertion order.
|
||||
*/
|
||||
QList<const InnerDecklistNode *> getCustomZones(const QString &boardZoneName) const;
|
||||
|
||||
/**
|
||||
* @brief Applies a function to every card in the deck tree. This can modify the cards.
|
||||
*
|
||||
|
|
@ -88,6 +125,8 @@ public:
|
|||
private:
|
||||
// Helpers for traversing the tree
|
||||
InnerDecklistNode *getZoneObjFromName(const QString &zoneName) const;
|
||||
InnerDecklistNode *findCustomZoneByName(const QString &zoneName) const;
|
||||
bool hasZoneName(const QString &zoneName) const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECKLIST_NODE_TREE_H
|
||||
|
|
|
|||
|
|
@ -142,6 +142,12 @@ public:
|
|||
return parent;
|
||||
}
|
||||
|
||||
/** @param newParent Reparent this node. The new parent takes ownership. */
|
||||
void setParent(InnerDecklistNode *newParent)
|
||||
{
|
||||
parent = newParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute the depth of this node in the tree.
|
||||
* @return Distance from the root (root = 0, children = 1, etc.).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue