mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
* Refactor CardDatabase *db global variable to singleton CardDatabaseManager. This commit refactors the global variable CardDatabase *db into a singleton encapsulated by the DatabaseManager class, accessible via DatabaseManager::getInstance(). This change centralizes access to the database instance, improving code modularity and encapsulation, resolving dependencies on main.h for code that requires access to the database instance. - Added DatabaseManager class with getInstance() method returning a pointer to the singleton CardDatabase. - Removed global db variable and updated references across the codebase. - Thread-safe static initialization for the singleton. Impact: This refactor should have no functional impact on the application, as it maintains the same interface for accessing the CardDatabase instance. However, the codebase now benefits from improved encapsulation, lifetime management, and thread-safety. * Refactor CardDatabase *db global variable to singleton CardDatabaseManager. This commit refactors the global variable CardDatabase *db into a singleton encapsulated by the DatabaseManager class, accessible via DatabaseManager::getInstance(). This change centralizes access to the database instance, improving code modularity and encapsulation, resolving dependencies on main.h for code that requires access to the database instance. - Added DatabaseManager class with getInstance() method returning a pointer to the singleton CardDatabase. - Removed global db variable and updated references across the codebase. - Thread-safe static initialization for the singleton. Impact: This refactor should have no functional impact on the application, as it maintains the same interface for accessing the CardDatabase instance. However, the codebase now benefits from improved encapsulation, lifetime management, and thread-safety. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
303 lines
9.2 KiB
C++
303 lines
9.2 KiB
C++
#include "deck_loader.h"
|
|
|
|
#include "../game/cards/card_database.h"
|
|
#include "../game/cards/card_database_manager.h"
|
|
#include "../main.h"
|
|
#include "decklist.h"
|
|
|
|
#include <QDebug>
|
|
#include <QFile>
|
|
#include <QRegularExpression>
|
|
#include <QStringList>
|
|
|
|
const QStringList DeckLoader::fileNameFilters = QStringList()
|
|
<< QObject::tr("Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck)")
|
|
<< QObject::tr("All files (*.*)");
|
|
|
|
DeckLoader::DeckLoader() : DeckList(), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
|
|
{
|
|
}
|
|
|
|
DeckLoader::DeckLoader(const QString &nativeString)
|
|
: DeckList(nativeString), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
|
|
{
|
|
}
|
|
|
|
DeckLoader::DeckLoader(const DeckList &other)
|
|
: DeckList(other), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
|
|
{
|
|
}
|
|
|
|
DeckLoader::DeckLoader(const DeckLoader &other)
|
|
: DeckList(other), lastFileName(other.lastFileName), lastFileFormat(other.lastFileFormat),
|
|
lastRemoteDeckId(other.lastRemoteDeckId)
|
|
{
|
|
}
|
|
|
|
bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt)
|
|
{
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
switch (fmt) {
|
|
case PlainTextFormat:
|
|
result = loadFromFile_Plain(&file);
|
|
break;
|
|
case CockatriceFormat: {
|
|
result = loadFromFile_Native(&file);
|
|
qDebug() << "Loaded from" << fileName << "-" << result;
|
|
if (!result) {
|
|
qDebug() << "Retying as plain format";
|
|
file.seek(0);
|
|
result = loadFromFile_Plain(&file);
|
|
fmt = PlainTextFormat;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (result) {
|
|
lastFileName = fileName;
|
|
lastFileFormat = fmt;
|
|
|
|
emit deckLoaded();
|
|
}
|
|
|
|
qDebug() << "Deck was loaded -" << result;
|
|
return result;
|
|
}
|
|
|
|
bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
|
|
{
|
|
bool result = loadFromString_Native(nativeString);
|
|
if (result) {
|
|
lastFileName = QString();
|
|
lastFileFormat = CockatriceFormat;
|
|
lastRemoteDeckId = remoteDeckId;
|
|
|
|
emit deckLoaded();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
|
|
{
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
switch (fmt) {
|
|
case PlainTextFormat:
|
|
result = saveToFile_Plain(&file);
|
|
break;
|
|
case CockatriceFormat:
|
|
result = saveToFile_Native(&file);
|
|
break;
|
|
}
|
|
|
|
if (result) {
|
|
lastFileName = fileName;
|
|
lastFileFormat = fmt;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// This struct is here to support the forEachCard function call, defined in decklist. It
|
|
// requires a function to be called for each card, and passes an inner node and a card for
|
|
// each card in the decklist.
|
|
struct FormatDeckListForExport
|
|
{
|
|
// Create refrences for the strings that will be passed in.
|
|
QString &mainBoardCards;
|
|
QString &sideBoardCards;
|
|
// create main operator for struct, allowing the foreachcard to work.
|
|
FormatDeckListForExport(QString &_mainBoardCards, QString &_sideBoardCards)
|
|
: mainBoardCards(_mainBoardCards), sideBoardCards(_sideBoardCards){};
|
|
|
|
void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const
|
|
{
|
|
// Get the card name
|
|
CardInfoPtr dbCard = CardDatabaseManager::getInstance()->getCard(card->getName());
|
|
if (!dbCard || dbCard->getIsToken()) {
|
|
// If it's a token, we don't care about the card.
|
|
return;
|
|
}
|
|
|
|
// Check if it's a sideboard card.
|
|
if (node->getName() == DECK_ZONE_SIDE) {
|
|
// Get the number of cards and add the card name
|
|
sideBoardCards += QString::number(card->getNumber());
|
|
// Add a space between card num and name
|
|
sideBoardCards += "%20";
|
|
// Add card name
|
|
sideBoardCards += card->getName();
|
|
// Add a return at the end of the card
|
|
sideBoardCards += "%0A";
|
|
} else // If it's a mainboard card, do the same thing, but for the mainboard card string
|
|
{
|
|
mainBoardCards += QString::number(card->getNumber());
|
|
mainBoardCards += "%20";
|
|
mainBoardCards += card->getName();
|
|
mainBoardCards += "%0A";
|
|
}
|
|
}
|
|
};
|
|
|
|
// Export deck to decklist function, called to format the deck in a way to be sent to a server
|
|
QString DeckLoader::exportDeckToDecklist()
|
|
{
|
|
// Add the base url
|
|
QString deckString = "https://www.decklist.org/?";
|
|
// Create two strings to pass to function
|
|
QString mainBoardCards, sideBoardCards;
|
|
// Set up the struct to call.
|
|
FormatDeckListForExport formatDeckListForExport(mainBoardCards, sideBoardCards);
|
|
// call our struct function for each card in the deck
|
|
forEachCard(formatDeckListForExport);
|
|
// Remove the extra return at the end of the last cards
|
|
mainBoardCards.chop(3);
|
|
sideBoardCards.chop(3);
|
|
// if after we've called it for each card, and the strings are empty, we know that
|
|
// there were no non-token cards in the deck, so show an error message.
|
|
if ((QString::compare(mainBoardCards, "", Qt::CaseInsensitive) == 0) &&
|
|
(QString::compare(sideBoardCards, "", Qt::CaseInsensitive) == 0)) {
|
|
return "";
|
|
}
|
|
// return a string with the url for decklist export
|
|
deckString += "deckmain=" + mainBoardCards + "&deckside=" + sideBoardCards;
|
|
return deckString;
|
|
}
|
|
|
|
DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName)
|
|
{
|
|
if (fileName.endsWith(".cod", Qt::CaseInsensitive)) {
|
|
return CockatriceFormat;
|
|
}
|
|
return PlainTextFormat;
|
|
}
|
|
|
|
bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments)
|
|
{
|
|
if (addComments) {
|
|
saveToStream_DeckHeader(out);
|
|
}
|
|
|
|
// loop zones
|
|
for (int i = 0; i < getRoot()->size(); i++) {
|
|
const auto *zoneNode = dynamic_cast<InnerDecklistNode *>(getRoot()->at(i));
|
|
|
|
saveToStream_DeckZone(out, zoneNode, addComments);
|
|
|
|
// end of zone
|
|
out << "\n";
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DeckLoader::saveToStream_DeckHeader(QTextStream &out)
|
|
{
|
|
if (!getName().isEmpty()) {
|
|
out << "// " << getName() << "\n\n";
|
|
}
|
|
|
|
if (!getComments().isEmpty()) {
|
|
QStringList commentRows = getComments().split(QRegularExpression("\n|\r\n|\r"));
|
|
foreach (QString row, commentRows) {
|
|
out << "// " << row << "\n";
|
|
}
|
|
out << "\n";
|
|
}
|
|
}
|
|
|
|
void DeckLoader::saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode, bool addComments)
|
|
{
|
|
// group cards by card type and count the subtotals
|
|
QMultiMap<QString, DecklistCardNode *> cardsByType;
|
|
QMap<QString, int> cardTotalByType;
|
|
int cardTotal = 0;
|
|
|
|
for (int j = 0; j < zoneNode->size(); j++) {
|
|
auto *card = dynamic_cast<DecklistCardNode *>(zoneNode->at(j));
|
|
|
|
CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(card->getName());
|
|
QString cardType = info ? info->getMainCardType() : "unknown";
|
|
|
|
cardsByType.insert(cardType, card);
|
|
|
|
if (cardTotalByType.contains(cardType)) {
|
|
cardTotalByType[cardType] += card->getNumber();
|
|
} else {
|
|
cardTotalByType[cardType] = card->getNumber();
|
|
}
|
|
|
|
cardTotal += card->getNumber();
|
|
}
|
|
|
|
if (addComments) {
|
|
out << "// " << cardTotal << " " << zoneNode->getVisibleName() << "\n";
|
|
}
|
|
|
|
// print cards to stream
|
|
foreach (QString cardType, cardsByType.uniqueKeys()) {
|
|
if (addComments) {
|
|
out << "// " << cardTotalByType[cardType] << " " << cardType << "\n";
|
|
}
|
|
|
|
QList<DecklistCardNode *> cards = cardsByType.values(cardType);
|
|
|
|
saveToStream_DeckZoneCards(out, zoneNode, cards, addComments);
|
|
|
|
if (addComments) {
|
|
out << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
|
|
const InnerDecklistNode *zoneNode,
|
|
QList<DecklistCardNode *> cards,
|
|
bool addComments)
|
|
{
|
|
// QMultiMap sorts values in reverse order
|
|
for (int i = cards.size() - 1; i >= 0; --i) {
|
|
DecklistCardNode *card = cards[i];
|
|
|
|
if (zoneNode->getName() == DECK_ZONE_SIDE && addComments) {
|
|
out << "SB: ";
|
|
}
|
|
|
|
out << card->getNumber() << " " << card->getName() << "\n";
|
|
}
|
|
}
|
|
|
|
QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName)
|
|
{
|
|
CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(cardName);
|
|
|
|
if (card && card->getIsToken()) {
|
|
return DECK_ZONE_TOKENS;
|
|
}
|
|
|
|
return currentZoneName;
|
|
}
|
|
|
|
QString DeckLoader::getCompleteCardName(const QString &cardName) const
|
|
{
|
|
if (CardDatabaseManager::getInstance()) {
|
|
CardInfoPtr temp = CardDatabaseManager::getInstance()->guessCard(cardName);
|
|
if (temp) {
|
|
return temp->getName();
|
|
}
|
|
}
|
|
|
|
return cardName;
|
|
}
|