From 854208ea0a6294365d952715a688101dc781eb71 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 16 Apr 2025 16:23:13 +0200 Subject: [PATCH] Implement deck analytics widgets. (#5837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lukas BrĂ¼bach --- cockatrice/CMakeLists.txt | 7 +- .../tab_deck_editor_visual.cpp | 3 + .../tab_deck_editor_visual_tab_widget.cpp | 7 + .../tab_deck_editor_visual_tab_widget.h | 4 + .../deck_analytics/deck_analytics_widget.cpp | 25 +++ .../deck_analytics/deck_analytics_widget.h | 34 ++++ .../deck_analytics/mana_base_widget.cpp | 135 ++++++++++++++++ .../widgets/deck_analytics/mana_base_widget.h | 37 +++++ .../deck_analytics/mana_curve_widget.cpp | 99 ++++++++++++ .../deck_analytics/mana_curve_widget.h | 33 ++++ .../deck_analytics/mana_devotion_widget.cpp | 146 ++++++++++++++++++ .../deck_analytics/mana_devotion_widget.h | 36 +++++ .../ui/widgets/general/display/bar_widget.cpp | 56 +++++++ .../ui/widgets/general/display/bar_widget.h | 27 ++++ .../visual_deck_editor_sample_hand_widget.cpp | 105 +++++++++++++ .../visual_deck_editor_sample_hand_widget.h | 29 ++++ 16 files changed, 782 insertions(+), 1 deletion(-) create mode 100644 cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h create mode 100644 cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.h create mode 100644 cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.h create mode 100644 cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.h create mode 100644 cockatrice/src/client/ui/widgets/general/display/bar_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/general/display/bar_widget.h create mode 100644 cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 1c967d0e5..3a1cb2924 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -74,13 +74,17 @@ set(cockatrice_SOURCES src/client/ui/widgets/cards/card_size_widget.cpp src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp + src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp + src/client/ui/widgets/deck_analytics/mana_base_widget.cpp + src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp + src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp src/client/ui/widgets/general/display/banner_widget.cpp - src/client/ui/widgets/general/display/banner_widget.cpp + src/client/ui/widgets/general/display/bar_widget.cpp src/client/ui/widgets/general/display/dynamic_font_size_label.cpp src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp src/client/ui/widgets/general/display/labeled_input.cpp @@ -108,6 +112,7 @@ set(cockatrice_SOURCES src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.cpp src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp + src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index fad34ba3a..224c4db15 100644 --- a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -8,6 +8,7 @@ #include "../../../settings/cache_settings.h" #include "../../ui/pixel_map_generator.h" #include "../../ui/widgets/cards/card_info_frame_widget.h" +#include "../../ui/widgets/deck_analytics/deck_analytics_widget.h" #include "../../ui/widgets/visual_deck_editor/visual_deck_editor_widget.h" #include "../tab_deck_editor.h" #include "../tab_supervisor.h" @@ -81,6 +82,8 @@ void TabDeckEditorVisual::onDeckChanged() { AbstractTabDeckEditor::onDeckChanged(); tabContainer->visualDeckView->decklistDataChanged(QModelIndex(), QModelIndex()); + tabContainer->deckAnalytics->refreshDisplays(deckDockWidget->deckModel); + tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); } void TabDeckEditorVisual::createMenus() diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp index 3c0452008..a34d7ad94 100644 --- a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp @@ -35,8 +35,15 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent, connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay, this, &TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay); + deckAnalytics = new DeckAnalyticsWidget(this, deckModel); + deckAnalytics->setObjectName("deckAnalytics"); + + sampleHandWidget = new VisualDeckEditorSampleHandWidget(this, deckModel); + this->addNewTab(visualDeckView, tr("Visual Deck View")); this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display")); + this->addNewTab(deckAnalytics, tr("Deck Analytics")); + this->addNewTab(sampleHandWidget, tr("Sample Hand")); } void TabDeckEditorVisualTabWidget::onCardChanged(CardInfoPtr activeCard) diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h index bc76d55be..c2454538a 100644 --- a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h @@ -1,8 +1,10 @@ #ifndef TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H #define TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H +#include "../../ui/widgets/deck_analytics/deck_analytics_widget.h" #include "../../ui/widgets/printing_selector/printing_selector.h" #include "../../ui/widgets/visual_database_display/visual_database_display_widget.h" +#include "../../ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h" #include "../../ui/widgets/visual_deck_editor/visual_deck_editor_widget.h" #include "../abstract_tab_deck_editor.h" @@ -29,8 +31,10 @@ public: int getTabCount() const; VisualDeckEditorWidget *visualDeckView; + DeckAnalyticsWidget *deckAnalytics; VisualDatabaseDisplayWidget *visualDatabaseDisplay; PrintingSelector *printingSelector; + VisualDeckEditorSampleHandWidget *sampleHandWidget; public slots: void onCardChanged(CardInfoPtr activeCard); diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp new file mode 100644 index 000000000..02a4bfb5a --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp @@ -0,0 +1,25 @@ +#include "deck_analytics_widget.h" + +DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + mainLayout = new QVBoxLayout(); + setLayout(mainLayout); + + manaCurveWidget = new ManaCurveWidget(this, deckListModel); + mainLayout->addWidget(manaCurveWidget); + + manaDevotionWidget = new ManaDevotionWidget(this, deckListModel); + mainLayout->addWidget(manaDevotionWidget); + + manaBaseWidget = new ManaBaseWidget(this, deckListModel); + mainLayout->addWidget(manaBaseWidget); +} + +void DeckAnalyticsWidget::refreshDisplays(DeckListModel *_deckModel) +{ + deckListModel = _deckModel; + manaCurveWidget->setDeckModel(_deckModel); + manaDevotionWidget->setDeckModel(_deckModel); + manaBaseWidget->setDeckModel(_deckModel); +} diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h new file mode 100644 index 000000000..6e7a273cf --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h @@ -0,0 +1,34 @@ +#ifndef DECK_ANALYTICS_WIDGET_H +#define DECK_ANALYTICS_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../../../ui/widgets/general/layout_containers/flow_widget.h" +#include "mana_base_widget.h" +#include "mana_curve_widget.h" +#include "mana_devotion_widget.h" + +#include +#include +#include +#include + +class DeckAnalyticsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel); + void setDeckList(const DeckList &_deckListModel); + std::map analyzeManaCurve(); + void refreshDisplays(DeckListModel *_deckListModel); + +private: + DeckListModel *deckListModel; + QVBoxLayout *mainLayout; + + ManaCurveWidget *manaCurveWidget; + ManaDevotionWidget *manaDevotionWidget; + ManaBaseWidget *manaBaseWidget; +}; + +#endif // DECK_ANALYTICS_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.cpp new file mode 100644 index 000000000..895e2d64d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.cpp @@ -0,0 +1,135 @@ +#include "mana_base_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../general/display/banner_widget.h" +#include "../general/display/bar_widget.h" + +#include +#include +#include + +ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + bannerWidget = new BannerWidget(this, tr("Mana Base"), Qt::Vertical, 100); + bannerWidget->setMaximumHeight(100); + layout->addWidget(bannerWidget); + + barContainer = new QWidget(this); + barLayout = new QHBoxLayout(barContainer); + layout->addWidget(barContainer); + + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase); + + retranslateUi(); +} + +void ManaBaseWidget::retranslateUi() +{ + bannerWidget->setText(tr("Mana Base")); +} + +void ManaBaseWidget::setDeckModel(DeckListModel *deckModel) +{ + deckListModel = deckModel; + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase); + analyzeManaBase(); +} + +void ManaBaseWidget::updateDisplay() +{ + // Clear the layout first + QLayoutItem *item; + while ((item = barLayout->takeAt(0)) != nullptr) { + item->widget()->deleteLater(); + delete item; + } + + int highestEntry = 0; + for (auto entry : manaBaseMap) { + if (entry > highestEntry) { + highestEntry = entry; + } + } + + // Define color mapping for mana types + QHash manaColors; + manaColors.insert("W", QColor(248, 231, 185)); + manaColors.insert("U", QColor(14, 104, 171)); + manaColors.insert("B", QColor(21, 11, 0)); + manaColors.insert("R", QColor(211, 32, 42)); + manaColors.insert("G", QColor(0, 115, 62)); + manaColors.insert("C", QColor(150, 150, 150)); + + for (auto manaColor : manaBaseMap.keys()) { + QColor barColor = manaColors.value(manaColor, Qt::gray); + BarWidget *barWidget = new BarWidget(QString(manaColor), manaBaseMap[manaColor], highestEntry, barColor, this); + barLayout->addWidget(barWidget); + } + + update(); +} + +QHash ManaBaseWidget::analyzeManaBase() +{ + manaBaseMap.clear(); + InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; + + for (int k = 0; k < currentCard->getNumber(); ++k) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + if (info) { + auto devotion = determineManaProduction(info->getText()); + mergeManaCounts(manaBaseMap, devotion); + } + } + } + } + + updateDisplay(); + return manaBaseMap; +} + +QHash ManaBaseWidget::determineManaProduction(const QString &rulesText) +{ + QHash manaCounts = {{"W", 0}, {"U", 0}, {"B", 0}, {"R", 0}, {"G", 0}, {"C", 0}}; + + QString text = rulesText.toLower(); // Normalize case for matching + + // Quick keyword-based checks for any color and colorless mana + if (text.contains("{t}: add one mana of any color") || text.contains("add one mana of any color")) { + for (const auto &color : {QStringLiteral("W"), QStringLiteral("U"), QStringLiteral("B"), QStringLiteral("R"), + QStringLiteral("G")}) { + manaCounts[color]++; + } + } + if (text.contains("{t}: add {c}") || text.contains("add one colorless mana")) { + manaCounts["C"]++; + } + + // Optimized regex for specific mana symbols + static const QRegularExpression specificColorRegex(R"(\{T\}:\s*Add\s*\{([WUBRG])\})"); + QRegularExpressionMatch match = specificColorRegex.match(rulesText); + if (match.hasMatch()) { + manaCounts[match.captured(1)]++; + } + + return manaCounts; +} + +void ManaBaseWidget::mergeManaCounts(QHash &manaCounts1, const QHash &manaCounts2) +{ + for (auto it = manaCounts2.constBegin(); it != manaCounts2.constEnd(); ++it) { + manaCounts1[it.key()] += it.value(); + } +} diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.h new file mode 100644 index 000000000..66442f687 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.h @@ -0,0 +1,37 @@ +#ifndef MANA_BASE_WIDGET_H +#define MANA_BASE_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../general/display/banner_widget.h" + +#include +#include +#include +#include + +class ManaBaseWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ManaBaseWidget(QWidget *parent, DeckListModel *deckListModel); + QHash analyzeManaBase(); + void updateDisplay(); + + QHash determineManaProduction(const QString &manaString); + void mergeManaCounts(QHash &manaCounts1, const QHash &manaCounts2); + +public slots: + void setDeckModel(DeckListModel *deckModel); + void retranslateUi(); + +private: + DeckListModel *deckListModel; + BannerWidget *bannerWidget; + QHash manaBaseMap; + QVBoxLayout *layout; + QWidget *barContainer; + QHBoxLayout *barLayout; +}; + +#endif // MANA_BASE_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp new file mode 100644 index 000000000..acf240e0e --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp @@ -0,0 +1,99 @@ +#include "mana_curve_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../main.h" +#include "../general/display/banner_widget.h" +#include "../general/display/bar_widget.h" + +#include +#include + +ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + bannerWidget = new BannerWidget(this, tr("Mana Curve"), Qt::Vertical, 100); + bannerWidget->setMaximumHeight(100); + layout->addWidget(bannerWidget); + + barContainer = new QWidget(this); + barLayout = new QHBoxLayout(barContainer); + layout->addWidget(barContainer); + + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve); + + retranslateUi(); +} + +void ManaCurveWidget::retranslateUi() +{ + bannerWidget->setText(tr("Mana Curve")); +} + +void ManaCurveWidget::setDeckModel(DeckListModel *deckModel) +{ + deckListModel = deckModel; + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve); + analyzeManaCurve(); +} + +std::unordered_map ManaCurveWidget::analyzeManaCurve() +{ + manaCurveMap.clear(); + InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; + + for (int k = 0; k < currentCard->getNumber(); ++k) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + if (info) { + int cmc = info->getCmc().toInt(); + manaCurveMap[cmc]++; + } + } + } + } + + updateDisplay(); + + return manaCurveMap; +} + +void ManaCurveWidget::updateDisplay() +{ + // Clear the layout first + if (barLayout != nullptr) { + QLayoutItem *item; + while ((item = barLayout->takeAt(0)) != nullptr) { + item->widget()->deleteLater(); + delete item; + } + } + + int highestEntry = 0; + for (const auto &entry : manaCurveMap) { + if (entry.second > highestEntry) { + highestEntry = entry.second; + } + } + + // Convert unordered_map to ordered map to ensure sorting by CMC + std::map sortedManaCurve(manaCurveMap.begin(), manaCurveMap.end()); + + // Add new widgets to the layout in sorted order + for (const auto &entry : sortedManaCurve) { + BarWidget *barWidget = + new BarWidget(QString::number(entry.first), entry.second, highestEntry, QColor(11, 11, 11), this); + barLayout->addWidget(barWidget); + } + + update(); // Update the widget display +} diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.h new file mode 100644 index 000000000..b45e5f862 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.h @@ -0,0 +1,33 @@ +#ifndef MANA_CURVE_WIDGET_H +#define MANA_CURVE_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../general/display/banner_widget.h" + +#include +#include +#include + +class ManaCurveWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ManaCurveWidget(QWidget *parent, DeckListModel *deckListModel); + void updateDisplay(); + +public slots: + void setDeckModel(DeckListModel *deckModel); + std::unordered_map analyzeManaCurve(); + void retranslateUi(); + +private: + DeckListModel *deckListModel; + std::unordered_map manaCurveMap; + QVBoxLayout *layout; + BannerWidget *bannerWidget; + QWidget *barContainer; + QHBoxLayout *barLayout; +}; + +#endif // MANA_CURVE_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp new file mode 100644 index 000000000..50b241a2a --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp @@ -0,0 +1,146 @@ +#include "mana_devotion_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../main.h" +#include "../general/display/banner_widget.h" +#include "../general/display/bar_widget.h" + +#include +#include +#include +#include +#include + +ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + bannerWidget = new BannerWidget(this, tr("Mana Devotion"), Qt::Vertical, 100); + bannerWidget->setMaximumHeight(100); + layout->addWidget(bannerWidget); + + barLayout = new QHBoxLayout(); + layout->addLayout(barLayout); + + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion); + + retranslateUi(); +} + +void ManaDevotionWidget::retranslateUi() +{ + bannerWidget->setText(tr("Mana Devotion")); +} + +void ManaDevotionWidget::setDeckModel(DeckListModel *deckModel) +{ + deckListModel = deckModel; + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion); + analyzeManaDevotion(); +} + +std::unordered_map ManaDevotionWidget::analyzeManaDevotion() +{ + manaDevotionMap.clear(); + InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; + + for (int k = 0; k < currentCard->getNumber(); ++k) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + if (info) { + auto devotion = countManaSymbols(info->getManaCost()); + mergeManaCounts(manaDevotionMap, devotion); + } + } + } + } + + updateDisplay(); + return manaDevotionMap; +} + +void ManaDevotionWidget::updateDisplay() +{ + // Clear the layout first + QLayoutItem *item; + while ((item = barLayout->takeAt(0)) != nullptr) { + item->widget()->deleteLater(); + delete item; + } + + int highestEntry = 0; + for (auto entry : manaDevotionMap) { + if (highestEntry < entry.second) { + highestEntry = entry.second; + } + } + + // Define color mapping for devotion bars + std::unordered_map manaColors = {{'W', QColor(248, 231, 185)}, {'U', QColor(14, 104, 171)}, + {'B', QColor(21, 11, 0)}, {'R', QColor(211, 32, 42)}, + {'G', QColor(0, 115, 62)}, {'C', QColor(150, 150, 150)}}; + + for (auto entry : manaDevotionMap) { + QColor barColor = manaColors.count(entry.first) ? manaColors[entry.first] : Qt::gray; + BarWidget *barWidget = new BarWidget(QString(entry.first), entry.second, highestEntry, barColor, this); + barLayout->addWidget(barWidget); + } + + update(); // Update the widget display +} + +std::unordered_map ManaDevotionWidget::countManaSymbols(const QString &manaString) +{ + std::unordered_map manaCounts = {{'W', 0}, {'U', 0}, {'B', 0}, {'R', 0}, {'G', 0}}; + + int len = manaString.length(); + for (int i = 0; i < len; ++i) { + if (manaString[i] == '{') { + ++i; // Move past '{' + if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + char mana1 = manaString[i].toLatin1(); + ++i; // Move to next character + if (i < len && manaString[i] == '/') { + ++i; // Move past '/' + if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + char mana2 = manaString[i].toLatin1(); + manaCounts[mana1]++; + manaCounts[mana2]++; + } else { + // Handle cases like "{W/}" where second part is invalid + manaCounts[mana1]++; + } + } else { + manaCounts[mana1]++; + } + } + // Ensure we always skip to the closing '}' + while (i < len && manaString[i] != '}') { + ++i; + } + } + // Check if the character is a standalone mana symbol (not inside {}) + else if (manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + manaCounts[manaString[i].toLatin1()]++; + } + } + + return manaCounts; +} + +void ManaDevotionWidget::mergeManaCounts(std::unordered_map &manaCounts1, + const std::unordered_map &manaCounts2) +{ + for (const auto &pair : manaCounts2) { + manaCounts1[pair.first] += pair.second; // Add values for matching keys + } +} diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.h new file mode 100644 index 000000000..551ecd82d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.h @@ -0,0 +1,36 @@ +#ifndef MANA_DEVOTION_WIDGET_H +#define MANA_DEVOTION_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../general/display/banner_widget.h" + +#include +#include +#include +#include + +class ManaDevotionWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ManaDevotionWidget(QWidget *parent, DeckListModel *deckListModel); + void updateDisplay(); + + std::unordered_map countManaSymbols(const QString &manaString); + void mergeManaCounts(std::unordered_map &manaCounts1, const std::unordered_map &manaCounts2); + +public slots: + void setDeckModel(DeckListModel *deckModel); + std::unordered_map analyzeManaDevotion(); + void retranslateUi(); + +private: + DeckListModel *deckListModel; + BannerWidget *bannerWidget; + std::unordered_map manaDevotionMap; + QVBoxLayout *layout; + QHBoxLayout *barLayout; +}; + +#endif // MANA_DEVOTION_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/general/display/bar_widget.cpp b/cockatrice/src/client/ui/widgets/general/display/bar_widget.cpp new file mode 100644 index 000000000..2194b6b03 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/bar_widget.cpp @@ -0,0 +1,56 @@ +#include "bar_widget.h" + +#include +#include + +BarWidget::BarWidget(QString label, int value, int total, QColor barColor, QWidget *parent) + : QWidget(parent), label(std::move(label)), value(value), total(total), barColor(barColor) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +QSize BarWidget::sizeHint() const +{ + QFontMetrics metrics(font()); + int labelHeight = metrics.height(); + int valueHeight = metrics.height(); + + // Calculate the height dynamically based on the total + int barHeight = (total > 0) ? (value * 200 / total) : 20; // Scale height proportionally + int totalHeight = barHeight + labelHeight + valueHeight + 30; // Extra space for text + return QSize(60, totalHeight); // Allow width to expand +} + +void BarWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + int widgetWidth = width(); + int widgetHeight = height(); + + // Calculate bar dimensions + int barWidth = widgetWidth * 0.8; // Use 80% of the available width + int fullBarHeight = widgetHeight - 40; // Leave space for labels + int valueBarHeight = (total > 0) ? (value * fullBarHeight / total) : 0; + + // Draw full bar background (gray) + painter.setBrush(QColor(200, 200, 200)); + painter.drawRect((widgetWidth - barWidth) / 2, 10, barWidth, fullBarHeight); + + // Draw the value-specific bar using the assigned color + painter.setBrush(barColor); + painter.drawRect((widgetWidth - barWidth) / 2, 10 + fullBarHeight - valueBarHeight, barWidth, valueBarHeight); + + // Draw the CMC label + painter.setPen(Qt::white); + QRect textRect(0, widgetHeight - 30, widgetWidth, 20); + painter.drawText(textRect, Qt::AlignCenter, label); + + // Draw the value count + painter.setPen(Qt::black); + QRect valueRect(0, 10, widgetWidth, 20); + painter.drawText(valueRect, Qt::AlignCenter, QString::number(value)); + + (void)event; // Suppress unused parameter warning +} diff --git a/cockatrice/src/client/ui/widgets/general/display/bar_widget.h b/cockatrice/src/client/ui/widgets/general/display/bar_widget.h new file mode 100644 index 000000000..e32d773e8 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/bar_widget.h @@ -0,0 +1,27 @@ +#ifndef BAR_WIDGET_H +#define BAR_WIDGET_H + +#include +#include +#include + +class BarWidget : public QWidget +{ + Q_OBJECT + +public: + explicit BarWidget(QString label, int value, int total, QColor barColor = Qt::blue, QWidget *parent = nullptr); + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QString label; + int value; + int total; + QColor barColor; // Store the bar color +}; + +#endif // BAR_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp new file mode 100644 index 000000000..f6b73b0d5 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -0,0 +1,105 @@ +#include "visual_deck_editor_sample_hand_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../cards/card_info_picture_widget.h" + +#include + +VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + resetButton = new QPushButton(this); + connect(resetButton, SIGNAL(clicked()), this, SLOT(updateDisplay())); + layout->addWidget(resetButton); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + layout->addWidget(flowWidget); + + for (CardInfoPtr card : getRandomCards(7)) { + auto displayWidget = new CardInfoPictureWidget(this); + displayWidget->setCard(card); + flowWidget->addWidget(displayWidget); + } +} + +void VisualDeckEditorSampleHandWidget::retranslateUi() +{ + resetButton->setText(tr("Reset")); +} + +void VisualDeckEditorSampleHandWidget::setDeckModel(DeckListModel *deckModel) +{ + deckListModel = deckModel; + // connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorSampleHandWidget::updateDisplay); + updateDisplay(); +} + +void VisualDeckEditorSampleHandWidget::updateDisplay() +{ + flowWidget->clearLayout(); + for (CardInfoPtr card : getRandomCards(7)) { + auto displayWidget = new CardInfoPictureWidget(this); + displayWidget->setCard(card); + flowWidget->addWidget(displayWidget); + } +} + +QList VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGet) +{ + QList mainDeckCards; + QList randomCards; + if (!deckListModel) + return randomCards; + DeckList *decklist = deckListModel->getDeckList(); + if (!decklist) + return randomCards; + InnerDecklistNode *listRoot = decklist->getRoot(); + if (!listRoot) + return randomCards; + + // Collect all cards in the main deck, allowing duplicates based on their count + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + if (!currentZone) + continue; + if (currentZone->getName() != DECK_ZONE_MAIN) + continue; // Only process the main deck + + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; + + for (int k = 0; k < currentCard->getNumber(); ++k) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + currentCard->getName(), currentCard->getCardProviderId()); + if (info) { + mainDeckCards.append(info); + } + } + } + } + + if (mainDeckCards.isEmpty()) + return randomCards; + + // Shuffle the deck + std::random_device rd; + std::mt19937 rng(rd()); + std::shuffle(mainDeckCards.begin(), mainDeckCards.end(), rng); + + // Select amountToGet cards + + for (int i = 0; i < qMin(amountToGet, mainDeckCards.size()); ++i) { + randomCards.append(mainDeckCards.at(i)); + } + + std::sort(randomCards.begin(), randomCards.end(), + [](const CardInfoPtr &a, const CardInfoPtr &b) { return a->getCmc() < b->getCmc(); }); + + return randomCards; +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h new file mode 100644 index 000000000..44dc04cab --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h @@ -0,0 +1,29 @@ +#ifndef VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H +#define VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include + +class VisualDeckEditorSampleHandWidget : public QWidget +{ + Q_OBJECT +public: + VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *deckListModel); + QList getRandomCards(int amountToGet); + +public slots: + void updateDisplay(); + void setDeckModel(DeckListModel *deckModel); + void retranslateUi(); + +private: + DeckListModel *deckListModel; + QVBoxLayout *layout; + QPushButton *resetButton; + FlowWidget *flowWidget; +}; + +#endif // VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H