[Game] Refactor subtype tally code to be more generic (#7021)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run

* [Game] Refactor subtype tally code to be more generic

* remove default case

* consolidate empty check

* fix include guard

* backwards compatibility with qt5
This commit is contained in:
RickyRister 2026-07-05 19:11:08 -07:00 committed by GitHub
parent 430fc117b4
commit c5b37cdffe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 138 additions and 84 deletions

View file

@ -83,7 +83,6 @@ set(cockatrice_SOURCES
src/game/game_state.cpp src/game/game_state.cpp
src/game_graphics/game_view.cpp src/game_graphics/game_view.cpp
src/game_graphics/hand_counter.cpp src/game_graphics/hand_counter.cpp
src/game/selection_subtype_tally.cpp
src/game_graphics/log/message_log_widget.cpp src/game_graphics/log/message_log_widget.cpp
src/game/phase.cpp src/game/phase.cpp
src/game_graphics/phases_toolbar.cpp src/game_graphics/phases_toolbar.cpp
@ -99,6 +98,8 @@ set(cockatrice_SOURCES
src/game_graphics/player/menu/say_menu.cpp src/game_graphics/player/menu/say_menu.cpp
src/game_graphics/player/menu/sideboard_menu.cpp src/game_graphics/player/menu/sideboard_menu.cpp
src/game_graphics/player/menu/utility_menu.cpp src/game_graphics/player/menu/utility_menu.cpp
src/game_graphics/tally/subtype_tally.cpp
src/game_graphics/tally/tally.cpp
src/game/player/player_actions.cpp src/game/player/player_actions.cpp
src/game_graphics/player/player_area.cpp src/game_graphics/player/player_area.cpp
src/game_graphics/player/player_dialogs.cpp src/game_graphics/player/player_dialogs.cpp

View file

@ -1,36 +0,0 @@
#ifndef SELECTION_SUBTYPE_TALLY_H
#define SELECTION_SUBTYPE_TALLY_H
#include <QList>
#include <QString>
class CardItem;
/** @brief A single subtype (e.g., "Goblin", "Warrior") with its occurrence count. */
struct SubtypeEntry
{
QString name; ///< The subtype name
int count; ///< Number of selected cards with this subtype
bool operator==(const SubtypeEntry &other) const
{
return name == other.name && count == other.count;
}
};
/**
* @brief Extracts and tallies subtypes from selected cards.
*/
namespace SelectionSubtypeTally
{
/**
* @brief Parses card type lines and counts each subtype occurrence.
*
* Skips face-down cards and cards without type info.
* @param cards The list of selected card items to analyze.
* @return Entries sorted by count ascending, then alphabetically.
*/
QList<SubtypeEntry> countSubtypes(const QList<CardItem *> &cards);
} // namespace SelectionSubtypeTally
#endif

View file

@ -1,7 +1,6 @@
#include "game_view.h" #include "game_view.h"
#include "../client/settings/cache_settings.h" #include "../client/settings/cache_settings.h"
#include "../game/selection_subtype_tally.h"
#include "game_scene.h" #include "game_scene.h"
#include <QAction> #include <QAction>
@ -79,12 +78,12 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
totalCountLabel->setStyleSheet(totalCountLabelStyle); totalCountLabel->setStyleSheet(totalCountLabelStyle);
totalCountLabel->hide(); totalCountLabel->hide();
subtypeTallyContainer = new QWidget(this); tallyContainer = new QWidget(this);
subtypeTallyContainer->setStyleSheet(subtypeTallyLabelStyle); tallyContainer->setStyleSheet(subtypeTallyLabelStyle);
subtypeTallyLayout = new QGridLayout(subtypeTallyContainer); tallyLayout = new QGridLayout(tallyContainer);
subtypeTallyLayout->setContentsMargins(2, 2, 2, 2); tallyLayout->setContentsMargins(2, 2, 2, 2);
subtypeTallyLayout->setSpacing(2); tallyLayout->setSpacing(2);
subtypeTallyContainer->hide(); tallyContainer->hide();
} }
void GameView::resizeEvent(QResizeEvent *event) void GameView::resizeEvent(QResizeEvent *event)
@ -177,14 +176,14 @@ void GameView::refreshShortcuts()
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
} }
void GameView::clearSubtypeLabels() void GameView::clearTallyLabels()
{ {
QtUtils::clearLayoutRec(subtypeTallyLayout); QtUtils::clearLayoutRec(tallyLayout);
} }
QSize GameView::rebuildSubtypeLabels(const QList<SubtypeEntry> &entries) QSize GameView::rebuildTallyLabels(const QList<TallyRow> &entries)
{ {
clearSubtypeLabels(); clearTallyLabels();
const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;"); const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;");
const QString countStyle = const QString countStyle =
@ -195,16 +194,16 @@ QSize GameView::rebuildSubtypeLabels(const QList<SubtypeEntry> &entries)
int maxCountWidth = 0; int maxCountWidth = 0;
int row = 0; int row = 0;
for (const SubtypeEntry &entry : entries) { for (const TallyRow &entry : entries) {
auto *nameLabel = new QLabel(entry.name, subtypeTallyContainer); auto *nameLabel = new QLabel(entry.name, tallyContainer);
nameLabel->setStyleSheet(nameStyle); nameLabel->setStyleSheet(nameStyle);
nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
subtypeTallyLayout->addWidget(nameLabel, row, 0); tallyLayout->addWidget(nameLabel, row, 0);
auto *countLabel = new QLabel(QString::number(entry.count), subtypeTallyContainer); auto *countLabel = new QLabel(entry.value, tallyContainer);
countLabel->setStyleSheet(countStyle); countLabel->setStyleSheet(countStyle);
countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
subtypeTallyLayout->addWidget(countLabel, row, 1); tallyLayout->addWidget(countLabel, row, 1);
QSize nameSize = nameLabel->sizeHint(); QSize nameSize = nameLabel->sizeHint();
QSize countSize = countLabel->sizeHint(); QSize countSize = countLabel->sizeHint();
@ -215,9 +214,9 @@ QSize GameView::rebuildSubtypeLabels(const QList<SubtypeEntry> &entries)
++row; ++row;
} }
int spacing = subtypeTallyLayout->spacing(); int spacing = tallyLayout->spacing();
int margins = subtypeTallyLayout->contentsMargins().left() + subtypeTallyLayout->contentsMargins().right(); int margins = tallyLayout->contentsMargins().left() + tallyLayout->contentsMargins().right();
int verticalMargins = subtypeTallyLayout->contentsMargins().top() + subtypeTallyLayout->contentsMargins().bottom(); int verticalMargins = tallyLayout->contentsMargins().top() + tallyLayout->contentsMargins().bottom();
int width = maxNameWidth + spacing + maxCountWidth + margins; int width = maxNameWidth + spacing + maxCountWidth + margins;
int height = totalHeight + (row - 1) * spacing + verticalMargins; int height = totalHeight + (row - 1) * spacing + verticalMargins;
@ -247,29 +246,26 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize)
totalCountLabel->show(); totalCountLabel->show();
} }
if (!SettingsCache::instance().getShowSubtypeSelectionTally() || count <= 1) { TallyType tallyType =
subtypeTallyContainer->hide(); SettingsCache::instance().getShowSubtypeSelectionTally() ? TallyType::Subtypes : TallyType::None;
cachedSubtypeEntries.clear();
return;
}
GameScene *gameScene = static_cast<GameScene *>(scene()); GameScene *gameScene = static_cast<GameScene *>(scene());
QList<SubtypeEntry> entries = SelectionSubtypeTally::countSubtypes(gameScene->selectedCards()); QList<TallyRow> entries = Tally::compute(gameScene->selectedCards(), tallyType);
if (entries.isEmpty()) { if (entries.isEmpty() || count <= 1) {
subtypeTallyContainer->hide(); tallyContainer->hide();
cachedSubtypeEntries.clear(); cachedTallyRows.clear();
return; return;
} }
// Only rebuild labels if entries changed // Only rebuild labels if entries changed
QSize containerSize; QSize containerSize;
if (entries != cachedSubtypeEntries) { if (entries != cachedTallyRows) {
cachedSubtypeEntries = entries; cachedTallyRows = entries;
containerSize = rebuildSubtypeLabels(entries); containerSize = rebuildTallyLabels(entries);
subtypeTallyContainer->resize(containerSize); tallyContainer->resize(containerSize);
} else { } else {
containerSize = subtypeTallyContainer->size(); containerSize = tallyContainer->size();
} }
int x = availableWidth - containerSize.width() - kMarginInPixels; int x = availableWidth - containerSize.width() - kMarginInPixels;
@ -283,8 +279,8 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize)
y = qMax(kMarginInPixels, y); y = qMax(kMarginInPixels, y);
subtypeTallyContainer->move(x, y); tallyContainer->move(x, y);
subtypeTallyContainer->show(); tallyContainer->show();
} }
/** /**

View file

@ -7,7 +7,7 @@
#ifndef GAMEVIEW_H #ifndef GAMEVIEW_H
#define GAMEVIEW_H #define GAMEVIEW_H
#include "../game/selection_subtype_tally.h" #include "tally/tally.h"
#include <QGraphicsView> #include <QGraphicsView>
@ -24,13 +24,13 @@ private:
QRubberBand *rubberBand; QRubberBand *rubberBand;
QLabel *dragCountLabel; QLabel *dragCountLabel;
QLabel *totalCountLabel; QLabel *totalCountLabel;
QWidget *subtypeTallyContainer; QWidget *tallyContainer;
QGridLayout *subtypeTallyLayout; QGridLayout *tallyLayout;
QPointF selectionOrigin; QPointF selectionOrigin;
QList<SubtypeEntry> cachedSubtypeEntries; ///< Cached entries to avoid redundant rebuilds QList<TallyRow> cachedTallyRows; ///< Cached entries to avoid redundant rebuilds
QSize rebuildSubtypeLabels(const QList<SubtypeEntry> &entries); QSize rebuildTallyLabels(const QList<TallyRow> &entries);
void clearSubtypeLabels(); void clearTallyLabels();
protected: protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;

View file

@ -1,6 +1,6 @@
#include "selection_subtype_tally.h" #include "subtype_tally.h"
#include "../game_graphics/board/card_item.h" #include "../board/card_item.h"
#include <QMap> #include <QMap>
#include <algorithm> #include <algorithm>
@ -19,12 +19,19 @@ QStringList extractSubtypesFromFace(const QString &faceType)
return {}; return {};
} }
/** @brief A single subtype (e.g., "Goblin", "Warrior") with its occurrence count. */
struct SubtypeEntry
{
QString name;
int count;
};
} // anonymous namespace } // anonymous namespace
namespace SelectionSubtypeTally namespace SubtypeTally
{ {
QList<SubtypeEntry> countSubtypes(const QList<CardItem *> &cards) QList<TallyRow> countSubtypes(const QList<CardItem *> &cards)
{ {
QMap<QString, int> subtypeCounts; QMap<QString, int> subtypeCounts;
@ -58,7 +65,13 @@ QList<SubtypeEntry> countSubtypes(const QList<CardItem *> &cards)
return a.name < b.name; return a.name < b.name;
}); });
return entries; // convert entries into TallyRows
QList<TallyRow> rows;
rows.reserve(entries.size()); // for backwards compatibility with Qt5
std::transform(entries.begin(), entries.end(), std::back_inserter(rows),
[](const SubtypeEntry &e) { return TallyRow{e.name, QString::number(e.count)}; });
return rows;
} }
} // namespace SelectionSubtypeTally } // namespace SubtypeTally

View file

@ -0,0 +1,26 @@
#ifndef COCKATRICE_SUBTYPE_TALLY_H
#define COCKATRICE_SUBTYPE_TALLY_H
#include "tally.h"
#include <QList>
#include <QString>
class CardItem;
/**
* @brief Extracts and tallies subtypes from selected cards.
*/
namespace SubtypeTally
{
/**
* @brief Parses card type lines and counts each subtype occurrence.
*
* Skips face-down cards and cards without type info.
* @param cards The list of selected card items to analyze.
* @return Entries sorted by count ascending, then alphabetically.
*/
QList<TallyRow> countSubtypes(const QList<CardItem *> &cards);
} // namespace SubtypeTally
#endif

View file

@ -0,0 +1,14 @@
#include "tally.h"
#include "subtype_tally.h"
QList<TallyRow> Tally::compute(const QList<CardItem *> &cards, const TallyType type)
{
switch (type) {
case TallyType::None:
return {};
case TallyType::Subtypes:
return SubtypeTally::countSubtypes(cards);
}
return {};
}

View file

@ -0,0 +1,40 @@
#ifndef COCKATRICE_TALLY_H
#define COCKATRICE_TALLY_H
#include <QString>
class CardItem;
/** @brief A single row of the tally output. */
struct TallyRow
{
QString name; ///< The row name (displayed on the left)
QString value; ///< Value for the row (displayed on the right)
bool operator==(const TallyRow &) const = default;
};
/**
* The tally type
*/
enum class TallyType
{
None,
Subtypes,
};
namespace Tally
{
/**
* @brief Analyzes the selected cards according to the tally type and builds the resulting tally rows.
* This forwards the cards to the code for that tally type.
*
* @param cards The list of selected card items to analyze.
* @param type The type of tally to do
* @return Rows sorted in top-to-bottom display order
*/
QList<TallyRow> compute(const QList<CardItem *> &cards, TallyType type);
} // namespace Tally
#endif // COCKATRICE_TALLY_H