mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
Merge e540126056 into df74917cb0
This commit is contained in:
commit
a37a494ce7
12 changed files with 464 additions and 131 deletions
|
|
@ -15,9 +15,58 @@
|
|||
#include <QtGlobal>
|
||||
#include <libcockatrice/filters/filter_tree.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* This widget provides a graphical control element for the CardFilter::Attr::AttrColor filters applied to the filter
|
||||
* model.
|
||||
* @brief Information about the behaviour of a filter mode.
|
||||
*/
|
||||
struct FilterModeInfo
|
||||
{
|
||||
// When a color symbol is enabled, add filters of this type. Skipped if equal to CardFilter::Type::TypeEnd.
|
||||
CardFilter::Type includeType;
|
||||
|
||||
// When a color symbol is disabled, add filters of this type. Skipped if equal to CardFilter::Type::TypeEnd.
|
||||
CardFilter::Type excludeType;
|
||||
|
||||
// The attribute to filter for when adding the filters.
|
||||
CardFilter::Attr attr;
|
||||
};
|
||||
|
||||
/** @brief Return information about the given filter mode. */
|
||||
static FilterModeInfo getFilterModeInfo(FilterMode filterMode)
|
||||
{
|
||||
switch (filterMode) {
|
||||
case FilterMode::Includes:
|
||||
return {CardFilter::Type::TypeOr, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrColor};
|
||||
case FilterMode::Excludes:
|
||||
return {CardFilter::Type::TypeAndNot, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrColor};
|
||||
case FilterMode::IncludeExclude:
|
||||
return {CardFilter::Type::TypeOr, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor};
|
||||
case FilterMode::ExactMatch:
|
||||
return {CardFilter::Type::TypeAnd, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor};
|
||||
case FilterMode::ColorIdentity:
|
||||
return {CardFilter::Type::TypeEnd, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColorIdentity};
|
||||
case FilterMode::Custom:
|
||||
case FilterMode::End:
|
||||
break;
|
||||
}
|
||||
return {CardFilter::Type::TypeEnd, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrEnd};
|
||||
}
|
||||
|
||||
/** @brief Remove the filters that apply to the given color and attribute from the given model. */
|
||||
static void removeFilters(FilterTreeModel *filterModel, QChar color, CardFilter::Attr attr)
|
||||
{
|
||||
for (const CardFilter *filter : filterModel->getFiltersOfType(attr)) {
|
||||
if (filter->term() == color) {
|
||||
filterModel->removeFilter(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* This widget provides a graphical control element for the CardFilter::Attr::AttrColor and
|
||||
* CardFilter::Attr::AttrColorIdentity filters applied to the filter model.
|
||||
* @param parent The Qt Widget that this widget will parent to
|
||||
* @param _filterModel The filter model that this widget will manipulate
|
||||
*/
|
||||
|
|
@ -59,11 +108,25 @@ void VisualDatabaseDisplayColorFilterWidget::retranslateUi()
|
|||
modeComboBox->blockSignals(true);
|
||||
modeComboBox->clear();
|
||||
|
||||
modeComboBox->addItem(tr("Exact match"), QVariant::fromValue(FilterMode::ExactMatch));
|
||||
modeComboBox->addItem(tr("Includes"), QVariant::fromValue(FilterMode::Includes));
|
||||
modeComboBox->addItem(tr("Excludes"), QVariant::fromValue(FilterMode::Excludes));
|
||||
modeComboBox->addItem(tr("Include / Exclude"), QVariant::fromValue(FilterMode::IncludeExclude));
|
||||
modeComboBox->addItem(tr("Exact match"), QVariant::fromValue(FilterMode::ExactMatch));
|
||||
modeComboBox->addItem(tr("Color identity"), QVariant::fromValue(FilterMode::ColorIdentity));
|
||||
|
||||
modeComboBox->setItemData(0, tr("Cards containing any of the selected colors"), Qt::ToolTipRole);
|
||||
modeComboBox->setItemData(1, tr("Cards containing none of the selected colors"), Qt::ToolTipRole);
|
||||
modeComboBox->setItemData(2, tr("Cards containing any of the selected colors and none of the unselected colors"),
|
||||
Qt::ToolTipRole);
|
||||
modeComboBox->setItemData(3, tr("Cards being exactly of the selected colors"), Qt::ToolTipRole);
|
||||
modeComboBox->setItemData(4, tr("Cards whose color identity fits within the selected colors"), Qt::ToolTipRole);
|
||||
|
||||
if (currentMode == FilterMode::Custom) {
|
||||
addCustomFilterModeItem();
|
||||
}
|
||||
|
||||
modeComboBox->setToolTip(tr("How selected and unselected colors are combined in the filter"));
|
||||
modeComboBox->setAccessibleName(tr("Color filter mode"));
|
||||
|
||||
// Restore current mode
|
||||
const int index = modeComboBox->findData(QVariant::fromValue(currentMode));
|
||||
|
|
@ -74,6 +137,28 @@ void VisualDatabaseDisplayColorFilterWidget::retranslateUi()
|
|||
modeComboBox->blockSignals(false);
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayColorFilterWidget::setFilterMode(FilterMode filterMode)
|
||||
{
|
||||
int index = modeComboBox->findData(QVariant::fromValue(filterMode));
|
||||
|
||||
if (index < 0 && filterMode == FilterMode::Custom) {
|
||||
index = modeComboBox->count();
|
||||
addCustomFilterModeItem();
|
||||
}
|
||||
|
||||
if (index >= 0) {
|
||||
modeComboBox->setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayColorFilterWidget::addCustomFilterModeItem()
|
||||
{
|
||||
int customIndex = modeComboBox->count();
|
||||
modeComboBox->addItem(tr("Custom"), QVariant::fromValue(FilterMode::Custom));
|
||||
modeComboBox->setItemData(customIndex, tr("Cards matching an unknown combination of color filters"),
|
||||
Qt::ToolTipRole);
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, bool active)
|
||||
{
|
||||
if (active) {
|
||||
|
|
@ -85,74 +170,24 @@ void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, boo
|
|||
|
||||
void VisualDatabaseDisplayColorFilterWidget::addFilter(QChar color)
|
||||
{
|
||||
QString colorString = color;
|
||||
QString typeStr;
|
||||
FilterModeInfo info = getFilterModeInfo(currentMode);
|
||||
|
||||
// Remove previous filters
|
||||
removeFilters(filterModel, color, info.attr);
|
||||
|
||||
QList<const CardFilter *> allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor);
|
||||
QList<const CardFilter *> matchingFilters;
|
||||
|
||||
for (const CardFilter *filter : allColorFilters) {
|
||||
if (filter->term() == color) {
|
||||
matchingFilters.append(filter);
|
||||
}
|
||||
}
|
||||
|
||||
for (const CardFilter *filter : matchingFilters) {
|
||||
filterModel->removeFilter(filter);
|
||||
}
|
||||
|
||||
// Add actual filter
|
||||
|
||||
switch (currentMode) {
|
||||
case FilterMode::ExactMatch:
|
||||
filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrColor));
|
||||
break;
|
||||
|
||||
case FilterMode::Includes:
|
||||
filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrColor));
|
||||
break;
|
||||
|
||||
case FilterMode::IncludeExclude:
|
||||
filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrColor));
|
||||
break;
|
||||
if (info.includeType != CardFilter::Type::TypeEnd) {
|
||||
filterModel->addFilter(new CardFilter(color, info.includeType, info.attr));
|
||||
}
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayColorFilterWidget::removeFilter(QChar color)
|
||||
{
|
||||
QString colorString = color;
|
||||
FilterModeInfo info = getFilterModeInfo(currentMode);
|
||||
|
||||
// Remove inclusion filters
|
||||
QList<const CardFilter *> allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor);
|
||||
QList<const CardFilter *> matchingFilters;
|
||||
removeFilters(filterModel, color, info.attr);
|
||||
|
||||
for (const CardFilter *filter : allColorFilters) {
|
||||
if (filter->term() == color) {
|
||||
matchingFilters.append(filter);
|
||||
}
|
||||
}
|
||||
|
||||
for (const CardFilter *filter : matchingFilters) {
|
||||
filterModel->removeFilter(filter);
|
||||
}
|
||||
|
||||
// Add exclusion filters if the mode demands it
|
||||
switch (currentMode) {
|
||||
case FilterMode::ExactMatch:
|
||||
filterModel->addFilter(
|
||||
new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor));
|
||||
break;
|
||||
|
||||
case FilterMode::IncludeExclude:
|
||||
filterModel->addFilter(
|
||||
new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor));
|
||||
break;
|
||||
|
||||
case FilterMode::Includes:
|
||||
// No exclusion in Includes mode
|
||||
break;
|
||||
// Add exclusion filters if the mode demands it.
|
||||
if (info.excludeType != CardFilter::Type::TypeEnd) {
|
||||
filterModel->addFilter(new CardFilter(color, info.excludeType, info.attr));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +204,7 @@ void VisualDatabaseDisplayColorFilterWidget::updateFilterMode()
|
|||
filterModel->filterTree()->blockSignals(true);
|
||||
|
||||
filterModel->clearFiltersOfType(CardFilter::Attr::AttrColor);
|
||||
filterModel->clearFiltersOfType(CardFilter::Attr::AttrColorIdentity);
|
||||
|
||||
const QList<ManaSymbolWidget *> manaSymbolWidgets = findChildren<ManaSymbolWidget *>();
|
||||
|
||||
|
|
@ -211,52 +247,94 @@ QList<QChar> VisualDatabaseDisplayColorFilterWidget::getActiveColors()
|
|||
|
||||
void VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel()
|
||||
{
|
||||
QList<const CardFilter *> allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor);
|
||||
// Determine the correct currentMode from the filter model by finding which modes
|
||||
// are not possible due to the model containing (a combination of) filters that
|
||||
// that mode cannot add.
|
||||
constexpr int modeCount = static_cast<int>(FilterMode::End);
|
||||
bool modeImpossible[modeCount] = {};
|
||||
|
||||
QList<ManaSymbolWidget *> manaSymbolWidgets = findChildren<ManaSymbolWidget *>();
|
||||
QList<const CardFilter *> filters;
|
||||
filters.append(filterModel->getFiltersOfType(CardFilter::Attr::AttrColor));
|
||||
filters.append(filterModel->getFiltersOfType(CardFilter::Attr::AttrColorIdentity));
|
||||
|
||||
for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) {
|
||||
for (int i = 0; i < modeCount; ++i) {
|
||||
FilterModeInfo modeInfo = getFilterModeInfo(static_cast<FilterMode>(i));
|
||||
|
||||
// Remove the modes that cannot create one of the filters.
|
||||
for (const CardFilter *filter : filters) {
|
||||
if (filter->attr() != modeInfo.attr ||
|
||||
(filter->type() != modeInfo.includeType && filter->type() != modeInfo.excludeType)) {
|
||||
modeImpossible[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Modes that have both an include and exclude filter should always have a filter for each color.
|
||||
if (modeInfo.includeType != CardFilter::Type::TypeEnd && modeInfo.excludeType != CardFilter::Type::TypeEnd &&
|
||||
!modeImpossible[i]) {
|
||||
for (const QChar &color : QString("WUBRG")) {
|
||||
int count = 0;
|
||||
for (const CardFilter *filter : filters) {
|
||||
count += filter->term() == color;
|
||||
}
|
||||
if (count != 1) {
|
||||
modeImpossible[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the current mode is not possible with the current filters, set it to the first
|
||||
// one that is. Switching to and from the custom mode is always possible.
|
||||
if (modeImpossible[static_cast<int>(currentMode)] || currentMode == FilterMode::Custom) {
|
||||
for (int i = 0; i < modeCount; ++i) {
|
||||
if (!modeImpossible[i] || i == static_cast<int>(FilterMode::Custom)) {
|
||||
currentMode = static_cast<FilterMode>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the combobox. Add a Custom item only if we have a custom filter mode.
|
||||
int customIndex = modeComboBox->findData(QVariant::fromValue(FilterMode::Custom));
|
||||
modeComboBox->blockSignals(true);
|
||||
if (currentMode == FilterMode::Custom) {
|
||||
if (customIndex < 0) {
|
||||
customIndex = modeComboBox->count();
|
||||
addCustomFilterModeItem();
|
||||
}
|
||||
modeComboBox->setCurrentIndex(customIndex);
|
||||
} else {
|
||||
if (customIndex >= 0) {
|
||||
modeComboBox->removeItem(customIndex);
|
||||
}
|
||||
int index = modeComboBox->findData(QVariant::fromValue(currentMode));
|
||||
if (index >= 0) {
|
||||
modeComboBox->setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
modeComboBox->blockSignals(false);
|
||||
|
||||
// Update the mana symbol widgets by looking for the current mode's include or exclude filters.
|
||||
const FilterModeInfo modeInfo = getFilterModeInfo(currentMode);
|
||||
|
||||
const bool useExclude =
|
||||
modeInfo.includeType == CardFilter::Type::TypeEnd && modeInfo.excludeType != CardFilter::Type::TypeEnd;
|
||||
const CardFilter::Type filterType = useExclude ? modeInfo.excludeType : modeInfo.includeType;
|
||||
|
||||
filters = filterModel->getFiltersOfType(modeInfo.attr);
|
||||
|
||||
for (ManaSymbolWidget *manaSymbolWidget : findChildren<ManaSymbolWidget *>()) {
|
||||
bool found = false;
|
||||
for (const CardFilter *filter : allColorFilters) {
|
||||
if (manaSymbolWidget->getSymbolChar() == filter->term()) {
|
||||
switch (currentMode) {
|
||||
case FilterMode::ExactMatch:
|
||||
switch (filter->type()) {
|
||||
case CardFilter::Type::TypeAnd:
|
||||
setManaSymbolActive(filter->term().at(0), true);
|
||||
break;
|
||||
default:
|
||||
setManaSymbolActive(filter->term().at(0), false);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FilterMode::Includes:
|
||||
switch (filter->type()) {
|
||||
case CardFilter::Type::TypeOr:
|
||||
setManaSymbolActive(filter->term().at(0), true);
|
||||
break;
|
||||
default:
|
||||
setManaSymbolActive(filter->term().at(0), false);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FilterMode::IncludeExclude:
|
||||
switch (filter->type()) {
|
||||
case CardFilter::Type::TypeOr:
|
||||
setManaSymbolActive(filter->term().at(0), true);
|
||||
break;
|
||||
default:
|
||||
setManaSymbolActive(filter->term().at(0), false);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (const CardFilter *filter : filters) {
|
||||
if (filter->type() == filterType && manaSymbolWidget->getSymbolChar() == filter->term()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
setManaSymbolActive(manaSymbolWidget->getSymbolChar(), false);
|
||||
}
|
||||
manaSymbolWidget->setColorActive(useExclude ? !found : found);
|
||||
manaSymbolWidget->setDisabled(currentMode == FilterMode::Custom);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,9 +40,14 @@ private:
|
|||
|
||||
enum class FilterMode
|
||||
{
|
||||
Includes = 0, // Include selected colors (OR condition).
|
||||
Excludes, // Exclude selected colors (AND NOT).
|
||||
IncludeExclude, // Include selected colors (OR) and exclude unselected colors (AND NOT).
|
||||
ExactMatch, // Only selected colors are included, all others are excluded.
|
||||
Includes, // Include selected colors (OR condition).
|
||||
IncludeExclude // Include selected colors (OR) and exclude unselected colors (AND NOT).
|
||||
ColorIdentity, // Keep cards whose color identity is a subset of the selected colors (stored as AND NOT for each
|
||||
// unselected color).
|
||||
Custom, // The color filters do not match any of the above modes. Not included in the combo box.
|
||||
End
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(FilterMode)
|
||||
|
|
@ -54,11 +59,13 @@ class VisualDatabaseDisplayColorFilterWidget : public QWidget
|
|||
public:
|
||||
explicit VisualDatabaseDisplayColorFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
|
||||
void retranslateUi();
|
||||
void setFilterMode(FilterMode filterMode);
|
||||
|
||||
signals:
|
||||
void filterModeChanged(FilterMode filterMode);
|
||||
|
||||
private slots:
|
||||
void addCustomFilterModeItem();
|
||||
void handleColorToggled(QChar color, bool active);
|
||||
void addFilter(QChar color);
|
||||
void removeFilter(QChar color);
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
|||
connect(clearFilterWidget, &QToolButton::clicked, this, [this] {
|
||||
filterModel->blockSignals(true);
|
||||
filterModel->filterTree()->blockSignals(true);
|
||||
colorFilterWidget->setFilterMode(FilterMode::Includes);
|
||||
filterModel->clear();
|
||||
filterModel->blockSignals(false);
|
||||
filterModel->filterTree()->blockSignals(false);
|
||||
|
|
|
|||
|
|
@ -316,6 +316,10 @@ void CardInfo::setColors(const QString &value)
|
|||
{
|
||||
setProperty(Mtg::Colors, value);
|
||||
}
|
||||
QString CardInfo::getColorIdentity() const
|
||||
{
|
||||
return getProperty(Mtg::ColorIdentity);
|
||||
}
|
||||
QString CardInfo::getLoyalty() const
|
||||
{
|
||||
return getProperty(Mtg::Loyalty);
|
||||
|
|
|
|||
|
|
@ -469,6 +469,7 @@ public:
|
|||
[[nodiscard]] QString getCmc() const;
|
||||
[[nodiscard]] QString getColors() const;
|
||||
void setColors(const QString &value);
|
||||
[[nodiscard]] QString getColorIdentity() const;
|
||||
[[nodiscard]] QString getLoyalty() const;
|
||||
[[nodiscard]] QString getMainCardType() const;
|
||||
[[nodiscard]] QString getManaCost() const;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ const QString CardFilter::attrName(Attr a)
|
|||
return tr("Type");
|
||||
case AttrColor:
|
||||
return tr("Color");
|
||||
case AttrColorIdentity:
|
||||
return tr("Color Identity");
|
||||
case AttrText:
|
||||
return tr("Text");
|
||||
case AttrSet:
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public:
|
|||
{
|
||||
AttrCmc = 0,
|
||||
AttrColor,
|
||||
AttrColorIdentity,
|
||||
AttrLoyalty,
|
||||
AttrManaCost,
|
||||
AttrName,
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ static void setupParserRules()
|
|||
const bool identity = sv.tokens[0].empty() || sv.tokens[0][0] != 'i';
|
||||
if (sv.tokens[1][0] == ':') {
|
||||
return [=](const CardData &x) {
|
||||
const QString match = identity ? x->getColors() : x->getProperty("coloridentity");
|
||||
const QString match = identity ? x->getColors() : x->getColorIdentity();
|
||||
if (parts.contains("m") && match.length() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -427,7 +427,7 @@ static void setupParserRules()
|
|||
}
|
||||
|
||||
return [=](const CardData &x) {
|
||||
const QString match = identity ? x->getColors() : x->getProperty("colorIdentity");
|
||||
const QString match = identity ? x->getColors() : x->getColorIdentity();
|
||||
if (parts.contains("m") && match.length() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,23 +176,24 @@ FilterItem::FilterItem(QString trm, FilterItemList *parent) : p(parent), term(st
|
|||
lowercaseTerm = term.toLower();
|
||||
|
||||
switch (attr()) {
|
||||
case CardFilter::AttrColor: {
|
||||
QString converted = term.trimmed();
|
||||
case CardFilter::AttrColor:
|
||||
case CardFilter::AttrColorIdentity: {
|
||||
QString converted = term.trimmed().toLower();
|
||||
|
||||
converted.replace("green", "g", Qt::CaseInsensitive);
|
||||
converted.replace("grn", "g", Qt::CaseInsensitive);
|
||||
converted.replace("blue", "u", Qt::CaseInsensitive);
|
||||
converted.replace("blu", "u", Qt::CaseInsensitive);
|
||||
converted.replace("black", "b", Qt::CaseInsensitive);
|
||||
converted.replace("blk", "b", Qt::CaseInsensitive);
|
||||
converted.replace("red", "r", Qt::CaseInsensitive);
|
||||
converted.replace("white", "w", Qt::CaseInsensitive);
|
||||
converted.replace("wht", "w", Qt::CaseInsensitive);
|
||||
converted.replace("colorless", "c", Qt::CaseInsensitive);
|
||||
converted.replace("colourless", "c", Qt::CaseInsensitive);
|
||||
converted.replace("none", "c", Qt::CaseInsensitive);
|
||||
converted.replace("green", "g");
|
||||
converted.replace("grn", "g");
|
||||
converted.replace("blue", "u");
|
||||
converted.replace("blu", "u");
|
||||
converted.replace("black", "b");
|
||||
converted.replace("blk", "b");
|
||||
converted.replace("red", "r");
|
||||
converted.replace("white", "w");
|
||||
converted.replace("wht", "w");
|
||||
converted.replace("colorless", "c");
|
||||
converted.replace("colourless", "c");
|
||||
converted.replace("none", "c");
|
||||
|
||||
converted.replace(QString(" "), QString(""), Qt::CaseInsensitive);
|
||||
converted.replace(QString(" "), QString(""));
|
||||
|
||||
convertedColorTerm = converted;
|
||||
break;
|
||||
|
|
@ -358,12 +359,17 @@ bool FilterItem::acceptSubType(const CardInfoPtr &info) const
|
|||
return subType.contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptColor(const CardInfoPtr &info) const
|
||||
/**
|
||||
* @brief Return true when every letter of the given term appears in the given colors string.
|
||||
* If the term is equal to "c", then only return true if the color string is empty.
|
||||
*
|
||||
* @param term The letters which should be included, or "c"
|
||||
* @param colors The string to check for letters in
|
||||
*/
|
||||
static bool matchesColor(const QString &term, const QString &colors)
|
||||
{
|
||||
const QString colors = info->getColors();
|
||||
|
||||
// Colorless card filter
|
||||
if (convertedColorTerm.toLower() == "c" && colors.length() < 1) {
|
||||
if (term == "c" && colors.length() < 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -372,13 +378,23 @@ bool FilterItem::acceptColor(const CardInfoPtr &info) const
|
|||
* then we should match all of them to the card's colors
|
||||
*/
|
||||
int match_count = 0;
|
||||
for (const QChar &color : convertedColorTerm) {
|
||||
for (const QChar &color : term) {
|
||||
if (colors.contains(color, Qt::CaseInsensitive)) {
|
||||
match_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return match_count == convertedColorTerm.length();
|
||||
return match_count == term.length();
|
||||
}
|
||||
|
||||
bool FilterItem::acceptColor(const CardInfoPtr &info) const
|
||||
{
|
||||
return matchesColor(convertedColorTerm, info->getColors());
|
||||
}
|
||||
|
||||
bool FilterItem::acceptColorIdentity(const CardInfoPtr &info) const
|
||||
{
|
||||
return matchesColor(convertedColorTerm, info->getColorIdentity());
|
||||
}
|
||||
|
||||
bool FilterItem::acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const
|
||||
|
|
@ -540,6 +556,8 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr &info,
|
|||
return acceptType(info);
|
||||
case CardFilter::AttrColor:
|
||||
return acceptColor(info);
|
||||
case CardFilter::AttrColorIdentity:
|
||||
return acceptColorIdentity(info);
|
||||
case CardFilter::AttrText:
|
||||
return acceptText(info, searchLanguage);
|
||||
case CardFilter::AttrSet:
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ public:
|
|||
[[nodiscard]] bool acceptMainType(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptSubType(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptColor(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptColorIdentity(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptSet(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptManaCost(const CardInfoPtr &info) const;
|
||||
|
|
|
|||
|
|
@ -21,10 +21,12 @@ cockatrice_add_test(
|
|||
)
|
||||
|
||||
# ------------------------
|
||||
# Filter String Test
|
||||
# (guard must match the condition for libcockatrice_filters in the root CMakeLists.txt)
|
||||
# Guard to match condition for libcockatrice_filters in the root CMakeLists.txt
|
||||
# ------------------------
|
||||
if(WITH_ORACLE OR WITH_CLIENT)
|
||||
# ------------------------
|
||||
# Filter String Test
|
||||
# ------------------------
|
||||
cockatrice_add_test(
|
||||
NAME
|
||||
filter_string_test
|
||||
|
|
@ -36,6 +38,20 @@ if(WITH_ORACLE OR WITH_CLIENT)
|
|||
libcockatrice_filters
|
||||
)
|
||||
|
||||
# ------------------------
|
||||
# Filter Tree Test
|
||||
# ------------------------
|
||||
cockatrice_add_test(
|
||||
NAME
|
||||
filter_tree_test
|
||||
SOURCES
|
||||
${VERSION_STRING_CPP}
|
||||
filter_tree_test.cpp
|
||||
mocks.cpp
|
||||
LIBS
|
||||
libcockatrice_filters
|
||||
)
|
||||
|
||||
# ------------------------
|
||||
# Visual Display Model Test
|
||||
# ------------------------
|
||||
|
|
|
|||
204
tests/carddatabase/filter_tree_test.cpp
Normal file
204
tests/carddatabase/filter_tree_test.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
|
||||
#include "libcockatrice/filters/filter_tree.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/filters/filter_string.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
class FilterTreeTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
language = CardSearchLanguage{};
|
||||
|
||||
island = CardInfo::newInstance("Island", "", false, {{"colors", ""}, {"coloridentity", "U"}}, {}, {}, {}, {});
|
||||
artifact =
|
||||
CardInfo::newInstance("Artifact", "", false, {{"colors", ""}, {"coloridentity", ""}}, {}, {}, {}, {});
|
||||
truth = CardInfo::newInstance("Truth", "", false, {{"colors", "U"}, {"coloridentity", "U"}}, {}, {}, {}, {});
|
||||
notDead =
|
||||
CardInfo::newInstance("Not Dead", "", false, {{"colors", "B"}, {"coloridentity", "B"}}, {}, {}, {}, {});
|
||||
wallOfDenial = CardInfo::newInstance("Wall of Denial", "", false, {{"colors", "WU"}, {"coloridentity", "WU"}},
|
||||
{}, {}, {}, {});
|
||||
maelstromAngel = CardInfo::newInstance("Maelstrom Archangel", "", false,
|
||||
{{"colors", "WUBRG"}, {"coloridentity", "WUBRG"}}, {}, {}, {}, {});
|
||||
}
|
||||
|
||||
CardSearchLanguage language;
|
||||
|
||||
CardData island; // c/u
|
||||
CardData artifact; // c
|
||||
CardData truth; // u
|
||||
CardData notDead; // b
|
||||
CardData wallOfDenial; // wu
|
||||
CardData maelstromAngel; // wubrg
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// =====================================================================================================================
|
||||
// acceptColor
|
||||
// =====================================================================================================================
|
||||
|
||||
TEST_F(FilterTreeTest, Color)
|
||||
{
|
||||
FilterTree filterTree;
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "wu");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(island, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(artifact, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
}
|
||||
|
||||
TEST_F(FilterTreeTest, ColorColorless)
|
||||
{
|
||||
FilterTree filterTree;
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "c");
|
||||
ASSERT_TRUE(filterTree.acceptsCard(island, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
}
|
||||
|
||||
TEST_F(FilterTreeTest, ColorNot)
|
||||
{
|
||||
FilterTree filterTree;
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAndNot, "b");
|
||||
ASSERT_TRUE(filterTree.acceptsCard(island, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
}
|
||||
|
||||
TEST_F(FilterTreeTest, ColorAliases)
|
||||
{
|
||||
FilterTree filterTree;
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "green");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "grn");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "blue");
|
||||
ASSERT_TRUE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "blu");
|
||||
ASSERT_TRUE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "black");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(notDead, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "blk");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(notDead, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "red");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "white");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "wht");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "colorless");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "colourless");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
|
||||
filterTree.clear();
|
||||
|
||||
filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "none");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
|
||||
filterTree.clear();
|
||||
}
|
||||
|
||||
// =====================================================================================================================
|
||||
// acceptColorIdentity
|
||||
// =====================================================================================================================
|
||||
|
||||
TEST_F(FilterTreeTest, ColorIdentity)
|
||||
{
|
||||
FilterTree filterTree;
|
||||
filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAnd, "wu");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(island, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(artifact, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
}
|
||||
|
||||
TEST_F(FilterTreeTest, ColorIdentityColorless)
|
||||
{
|
||||
FilterTree filterTree;
|
||||
filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAnd, "c");
|
||||
ASSERT_FALSE(filterTree.acceptsCard(island, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
}
|
||||
|
||||
TEST_F(FilterTreeTest, ColorIdentityNot)
|
||||
{
|
||||
FilterTree filterTree;
|
||||
filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAndNot, "b");
|
||||
ASSERT_TRUE(filterTree.acceptsCard(island, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
}
|
||||
|
||||
TEST_F(FilterTreeTest, ColorIdentityMultiple)
|
||||
{
|
||||
// Simulate a WU commander.
|
||||
FilterTree filterTree;
|
||||
filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAndNot, "b");
|
||||
filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAndNot, "r");
|
||||
filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAndNot, "g");
|
||||
ASSERT_TRUE(filterTree.acceptsCard(island, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(truth, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
|
||||
ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
|
||||
ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue