diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index bd99d08bf..bc2833b58 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -82,6 +82,7 @@ set(cockatrice_SOURCES src/game_graphics/game_scene.cpp src/game/game_state.cpp src/game_graphics/game_view.cpp + src/game/selection_subtype_counter.cpp src/game_graphics/hand_counter.cpp src/game_graphics/log/message_log_widget.cpp src/game/phase.cpp diff --git a/cockatrice/src/game/selection_subtype_counter.cpp b/cockatrice/src/game/selection_subtype_counter.cpp new file mode 100644 index 000000000..b30f9438b --- /dev/null +++ b/cockatrice/src/game/selection_subtype_counter.cpp @@ -0,0 +1,129 @@ +#include "selection_subtype_counter.h" + +#include "board/card_item.h" + +#include +#include + +namespace SelectionSubtypeCounter +{ + +QStringList extractSubtypesFromFace(const QString &faceType) +{ + QStringList parts = faceType.split(QStringLiteral(" — ")); + if (parts.size() > 1) { + return parts[1].split(QStringLiteral(" "), Qt::SkipEmptyParts); + } + return {}; +} + +QList countSubtypes(const QList &cards) +{ + QMap> subtypesByMainType; + QMap cardCountPerMainType; + + for (CardItem *card : cards) { + if (card->getFaceDown() || card->getCard().isEmpty()) { + continue; + } + + QString mainType = card->getCardInfo().getMainCardType(); + if (mainType.isEmpty()) { + mainType = QStringLiteral("Other"); + } + + QString cardType = card->getCardInfo().getCardType(); + QStringList cardFaces = cardType.split(QStringLiteral(" // ")); + + bool contributedSubtypes = false; + for (const QString &face : cardFaces) { + QStringList subtypes = extractSubtypesFromFace(face); + for (const QString &subtype : subtypes) { + subtypesByMainType[mainType][subtype]++; + contributedSubtypes = true; + } + } + + if (contributedSubtypes) { + cardCountPerMainType[mainType]++; + } + } + + QList groups; + for (auto it = subtypesByMainType.constBegin(); it != subtypesByMainType.constEnd(); ++it) { + MainTypeGroup group; + group.mainType = it.key(); + group.cardCount = cardCountPerMainType.value(it.key(), 0); + + for (auto subIt = it.value().constBegin(); subIt != it.value().constEnd(); ++subIt) { + group.subtypes.append({subIt.key(), subIt.value()}); + } + + // Sort subtypes: by count ascending, then alphabetically + std::sort(group.subtypes.begin(), group.subtypes.end(), [](const SubtypeEntry &a, const SubtypeEntry &b) { + if (a.count != b.count) { + return a.count < b.count; + } + return a.name < b.name; + }); + + groups.append(group); + } + + // Sort groups: by card count ascending, then alphabetically by main type + std::sort(groups.begin(), groups.end(), [](const MainTypeGroup &a, const MainTypeGroup &b) { + if (a.cardCount != b.cardCount) { + return a.cardCount < b.cardCount; + } + return a.mainType < b.mainType; + }); + + return groups; +} + +QString formatAsHtml(const QList &groups) +{ + // Flatten to final ordered list + QList sortedEntries; + for (const MainTypeGroup &group : groups) { + for (const auto &entry : group.subtypes) { + sortedEntries.append(entry); + } + } + + // Calculate padding widths + int maxNameLen = 0; + int maxCountLen = 0; + for (const auto &entry : sortedEntries) { + maxNameLen = qMax(maxNameLen, entry.name.length()); + maxCountLen = qMax(maxCountLen, QString::number(entry.count).length()); + } + + // Format output + QStringList lines; + for (const auto &entry : sortedEntries) { + QString name = entry.name.toHtmlEscaped(); + QString count = QString::number(entry.count); + + QString namePadding = QString(QStringLiteral(" ")).repeated(maxNameLen - entry.name.length()); + QString countPadding = QString(QStringLiteral(" ")).repeated(maxCountLen - count.length()); + + lines << QStringLiteral( + "%1%2 %3%4") + .arg(namePadding, name, countPadding, count); + } + + return QStringLiteral("") + lines.join(QStringLiteral("
")) + + QStringLiteral("
"); +} + +QString buildSubtypeCountText(const QList &cards) +{ + QList groups = countSubtypes(cards); + if (groups.isEmpty()) { + return QString(); + } + return formatAsHtml(groups); +} + +} // namespace SelectionSubtypeCounter diff --git a/cockatrice/src/game/selection_subtype_counter.h b/cockatrice/src/game/selection_subtype_counter.h new file mode 100644 index 000000000..08dc389a7 --- /dev/null +++ b/cockatrice/src/game/selection_subtype_counter.h @@ -0,0 +1,31 @@ +#ifndef SELECTION_SUBTYPE_COUNTER_H +#define SELECTION_SUBTYPE_COUNTER_H + +#include +#include +#include + +class CardItem; + +struct SubtypeEntry +{ + QString name; + int count; +}; + +struct MainTypeGroup +{ + QString mainType; + int cardCount; + QList subtypes; +}; + +namespace SelectionSubtypeCounter +{ +QStringList extractSubtypesFromFace(const QString &faceType); +QList countSubtypes(const QList &cards); +QString formatAsHtml(const QList &groups); +QString buildSubtypeCountText(const QList &cards); +} // namespace SelectionSubtypeCounter + +#endif diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index e546e6863..83ee92099 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -1,15 +1,13 @@ #include "game_view.h" #include "../client/settings/cache_settings.h" -#include "board/card_item.h" #include "game_scene.h" +#include "selection_subtype_counter.h" #include #include -#include #include #include -#include // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // This subclass disables that behavior so dragCountLabel can appear above it. @@ -174,133 +172,13 @@ void GameView::refreshShortcuts() SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); } -/** @brief Extracts subtypes from a card face type string (e.g., "Creature — Human Wizard" -> ["Human", "Wizard"]) */ -static QStringList extractSubtypesFromFace(const QString &faceType) -{ - QStringList parts = faceType.split(QStringLiteral(" — ")); - if (parts.size() > 1) { - return parts[1].split(QStringLiteral(" "), Qt::SkipEmptyParts); - } - return {}; -} - QString GameView::buildSubtypeCountText() const { GameScene *gameScene = dynamic_cast(scene()); if (!gameScene) { return QString(); } - - // Map: main card type -> (subtype -> count) - QMap> subtypesByMainType; - // Track cards contributing subtypes per main type (for group ordering) - QMap cardCountPerMainType; - - for (CardItem *card : gameScene->selectedCards()) { - if (card->getFaceDown() || card->getCard().isEmpty()) { - continue; - } - - QString mainType = card->getCardInfo().getMainCardType(); - if (mainType.isEmpty()) { - mainType = QStringLiteral("Other"); - } - - QString cardType = card->getCardInfo().getCardType(); - QStringList cardFaces = cardType.split(QStringLiteral(" // ")); - - bool contributedSubtypes = false; - for (const QString &face : cardFaces) { - QStringList subtypes = extractSubtypesFromFace(face); - for (const QString &subtype : subtypes) { - subtypesByMainType[mainType][subtype]++; - contributedSubtypes = true; - } - } - - if (contributedSubtypes) { - cardCountPerMainType[mainType]++; - } - } - - if (subtypesByMainType.isEmpty()) { - return QString(); - } - - // Build groups with sorted subtypes - struct MainTypeGroup - { - QString mainType; - int cardCount; - QList> subtypes; - }; - - QList groups; - for (auto it = subtypesByMainType.constBegin(); it != subtypesByMainType.constEnd(); ++it) { - MainTypeGroup group; - group.mainType = it.key(); - group.cardCount = cardCountPerMainType.value(it.key(), 0); - - for (auto subIt = it.value().constBegin(); subIt != it.value().constEnd(); ++subIt) { - group.subtypes.append({subIt.key(), subIt.value()}); - } - - /** - * Sort subtypes: by count ascending (lower counts at top of the list), then alphabetically. - * Since the subtype list displays above the total count label (bottom-right corner), - * ascending order places the most common subtypes visually adjacent to the total. - */ - std::sort(group.subtypes.begin(), group.subtypes.end(), - [](const QPair &a, const QPair &b) { - if (a.second != b.second) { - return a.second < b.second; - } - return a.first < b.first; - }); - - groups.append(group); - } - - // Sort groups: by card count ascending, then alphabetically by main type - std::sort(groups.begin(), groups.end(), [](const MainTypeGroup &a, const MainTypeGroup &b) { - if (a.cardCount != b.cardCount) { - return a.cardCount < b.cardCount; - } - return a.mainType < b.mainType; - }); - - // Flatten to final ordered list - QList> sortedEntries; - for (const MainTypeGroup &group : groups) { - for (const auto &entry : group.subtypes) { - sortedEntries.append(entry); - } - } - - // Calculate padding widths - int maxNameLen = 0; - int maxCountLen = 0; - for (const auto &entry : sortedEntries) { - maxNameLen = qMax(maxNameLen, entry.first.length()); - maxCountLen = qMax(maxCountLen, QString::number(entry.second).length()); - } - - // Format output - QStringList lines; - for (const auto &entry : sortedEntries) { - QString name = entry.first.toHtmlEscaped(); - QString count = QString::number(entry.second); - - QString namePadding = QString(QStringLiteral(" ")).repeated(maxNameLen - entry.first.length()); - QString countPadding = QString(QStringLiteral(" ")).repeated(maxCountLen - count.length()); - - lines << QStringLiteral( - "%1%2 %3%4") - .arg(namePadding, name, countPadding, count); - } - - return QStringLiteral("") + lines.join(QStringLiteral("
")) + - QStringLiteral("
"); + return SelectionSubtypeCounter::buildSubtypeCountText(gameScene->selectedCards()); } void GameView::updateSelectionCount(const QSize &viewSize)