Initial commit of mtgonline project
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
#ifndef CARD_REF_H
|
||||
#define CARD_REF_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* The information passed over the server that is required to identify the exact card to display.
|
||||
*
|
||||
* @param name The name of the card. Should not be empty, unless to indicate the lack of a card.
|
||||
* @param providerId Determines which printing of the card to use. Can be empty, in which case Cockatrice should default
|
||||
* to using the preferred set.
|
||||
*/
|
||||
struct CardRef
|
||||
{
|
||||
QString name;
|
||||
QString providerId = QString();
|
||||
|
||||
bool operator==(const CardRef &other) const
|
||||
{
|
||||
return name == other.name && providerId == other.providerId;
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return name.isEmpty() && providerId.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CARD_REF_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CLAMPED_ARITHMETIC_H
|
||||
#define CLAMPED_ARITHMETIC_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Overflow-safe clamped addition: returns value + delta bounded to [minValue, maxValue].
|
||||
*
|
||||
* Uses a 64-bit intermediate so the addition cannot overflow int. Shared by the bounded
|
||||
* counter arithmetic in both the client and the server.
|
||||
*
|
||||
* @note Requires minValue <= maxValue. Bounds come from trusted compile-time call sites;
|
||||
* qBound() asserts this internally in debug builds.
|
||||
*/
|
||||
inline int addClamped(int value, int delta, int minValue, int maxValue)
|
||||
{
|
||||
const auto result = static_cast<int64_t>(value) + static_cast<int64_t>(delta);
|
||||
return static_cast<int>(qBound(static_cast<int64_t>(minValue), result, static_cast<int64_t>(maxValue)));
|
||||
}
|
||||
|
||||
#endif // CLAMPED_ARITHMETIC_H
|
||||
@@ -0,0 +1,103 @@
|
||||
#ifndef COLOR_H
|
||||
#define COLOR_H
|
||||
|
||||
#ifdef QT_GUI_LIB
|
||||
#include <QColor>
|
||||
#endif
|
||||
|
||||
#include <libcockatrice/protocol/pb/color.pb.h>
|
||||
|
||||
#ifdef QT_GUI_LIB
|
||||
inline QColor convertColorToQColor(const color &c)
|
||||
{
|
||||
return QColor(c.r(), c.g(), c.b());
|
||||
}
|
||||
|
||||
inline color convertQColorToColor(const QColor &c)
|
||||
{
|
||||
color result;
|
||||
result.set_r(c.red());
|
||||
result.set_g(c.green());
|
||||
result.set_b(c.blue());
|
||||
return result;
|
||||
}
|
||||
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
|
||||
namespace GameSpecificColors
|
||||
{
|
||||
namespace MTG
|
||||
{
|
||||
inline QColor colorHelper(const QString &name)
|
||||
{
|
||||
static const QMap<QString, QColor> colorMap = {
|
||||
{"W", QColor(245, 245, 220)},
|
||||
{"U", QColor(80, 140, 255)},
|
||||
{"B", QColor(60, 60, 60)},
|
||||
{"R", QColor(220, 60, 50)},
|
||||
{"G", QColor(70, 160, 70)},
|
||||
{"Creature", QColor(70, 130, 180)},
|
||||
{"Instant", QColor(138, 43, 226)},
|
||||
{"Sorcery", QColor(199, 21, 133)},
|
||||
{"Enchantment", QColor(218, 165, 32)},
|
||||
{"Artifact", QColor(169, 169, 169)},
|
||||
{"Planeswalker", QColor(210, 105, 30)},
|
||||
{"Land", QColor(110, 80, 50)},
|
||||
};
|
||||
|
||||
if (colorMap.contains(name)) {
|
||||
return colorMap[name];
|
||||
}
|
||||
|
||||
if (name.length() == 1 && colorMap.contains(name.toUpper())) {
|
||||
return colorMap[name.toUpper()];
|
||||
}
|
||||
|
||||
uint h = qHash(name);
|
||||
int r = 100 + (h % 120);
|
||||
int g = 100 + ((h >> 8) % 120);
|
||||
int b = 100 + ((h >> 16) % 120);
|
||||
|
||||
return QColor(r, g, b);
|
||||
}
|
||||
|
||||
inline QList<QPair<QString, int>> sortManaMapWUBRGCFirst(const QMap<QString, int> &input)
|
||||
{
|
||||
static const QStringList priorityOrder = {"W", "U", "B", "R", "G", "C"};
|
||||
|
||||
QList<QPair<QString, int>> result;
|
||||
QSet<QString> consumed;
|
||||
|
||||
// 1. Add priority colors in fixed order
|
||||
for (const QString &key : priorityOrder) {
|
||||
auto it = input.find(key);
|
||||
if (it != input.end()) {
|
||||
result.append({it.key(), it.value()});
|
||||
consumed.insert(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Add remaining keys (QMap iteration is already sorted)
|
||||
for (auto it = input.begin(); it != input.end(); ++it) {
|
||||
if (!consumed.contains(it.key())) {
|
||||
result.append({it.key(), it.value()});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace MTG
|
||||
} // namespace GameSpecificColors
|
||||
#endif
|
||||
|
||||
inline color makeColor(int r, int g, int b)
|
||||
{
|
||||
color result;
|
||||
result.set_r(r);
|
||||
result.set_g(g);
|
||||
result.set_b(b);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef COUNTER_LIMITS_H
|
||||
#define COUNTER_LIMITS_H
|
||||
|
||||
/**
|
||||
* @brief Upper bound for a bounded counter's value: [0, MAX_COUNTER_VALUE].
|
||||
*
|
||||
* Caps an individual counter's VALUE (e.g. a +1/+1 counter at 999), not how many counters
|
||||
* something holds. Applies to counters that are constrained to a non-negative display range,
|
||||
* such as card counters and commander tax. Unbounded counters (e.g. a player's life total)
|
||||
* do not use this limit and may go negative, saturating only at the int range.
|
||||
*
|
||||
* The max of 999 is a display constraint (3-digit rendering) and a reasonable gameplay limit.
|
||||
* The server enforces these bounds; the client may also check them for UX optimization.
|
||||
*/
|
||||
constexpr int MAX_COUNTER_VALUE = 999;
|
||||
|
||||
#endif // COUNTER_LIMITS_H
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef COCKATRICE_DAYS_YEARS_BETWEEN_H
|
||||
#define COCKATRICE_DAYS_YEARS_BETWEEN_H
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
inline static QPair<int, int> getDaysAndYearsBetween(const QDate &then, const QDate &now)
|
||||
{
|
||||
int years = now.addDays(1 - then.dayOfYear()).year() - then.year(); // there is no yearsTo
|
||||
int days = then.addYears(years).daysTo(now);
|
||||
return {days, years};
|
||||
}
|
||||
|
||||
#endif // COCKATRICE_DAYS_YEARS_BETWEEN_H
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef DICE_LIMITS_H
|
||||
#define DICE_LIMITS_H
|
||||
|
||||
#include <QtGlobal> // for uint
|
||||
|
||||
/** @brief Fewest sides a rollable die may have. */
|
||||
constexpr uint MINIMUM_DIE_SIDES = 2;
|
||||
/** @brief Most sides a rollable die may have. */
|
||||
constexpr uint MAXIMUM_DIE_SIDES = 1000000;
|
||||
/** @brief Fewest dice that may be rolled at once. */
|
||||
constexpr uint MINIMUM_DICE_TO_ROLL = 1;
|
||||
/** @brief Most dice that may be rolled at once. */
|
||||
constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
|
||||
|
||||
#endif // DICE_LIMITS_H
|
||||
@@ -0,0 +1,108 @@
|
||||
#include "expression.h"
|
||||
|
||||
#include "peglib.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QtMath>
|
||||
#include <functional>
|
||||
|
||||
peg::parser math(R"(
|
||||
EXPRESSION <- P0
|
||||
P0 <- P1 (P1_OPERATOR P1)*
|
||||
P1 <- P2 (P2_OPERATOR P2)*
|
||||
P2 <- P3 (P3_OPERATOR P3)*
|
||||
P3 <- NUMBER / FUNCTION / VARIABLE / '(' P0 ')'
|
||||
|
||||
P1_OPERATOR <- < [-+] >
|
||||
P2_OPERATOR <- < [/*] >
|
||||
P3_OPERATOR <- < '^' >
|
||||
|
||||
NUMBER <- < '-'? [0-9]+ >
|
||||
NAME <- < [a-z][a-z0-9]* >
|
||||
VARIABLE <- < [xX] >
|
||||
FUNCTION <- NAME '(' EXPRESSION ( [,\n] EXPRESSION )* ')'
|
||||
|
||||
%whitespace <- [ \t\r]*
|
||||
)");
|
||||
|
||||
QMap<QString, std::function<double(double)>> *default_functions = nullptr;
|
||||
|
||||
Expression::Expression(double initial) : value(initial)
|
||||
{
|
||||
if (default_functions == nullptr) {
|
||||
default_functions = new QMap<QString, std::function<double(double)>>();
|
||||
default_functions->insert("abs", [](double a) { return qFabs(a); });
|
||||
default_functions->insert("ceil", [](double a) { return qCeil(a); });
|
||||
default_functions->insert("cos", [](double a) { return qCos(a); });
|
||||
default_functions->insert("floor", [](double a) { return qFloor(a); });
|
||||
default_functions->insert("log", [](double a) { return qLn(a); });
|
||||
default_functions->insert("log10", [](double a) { return qLn(a); });
|
||||
default_functions->insert("round", [](double a) { return qRound(a); });
|
||||
default_functions->insert("sin", [](double a) { return qSin(a); });
|
||||
default_functions->insert("sqrt", [](double a) { return qSqrt(a); });
|
||||
default_functions->insert("tan", [](double a) { return qTan(a); });
|
||||
default_functions->insert("trunc", [](double a) { return std::trunc(a); });
|
||||
}
|
||||
fns = QMap<QString, std::function<double(double)>>(*default_functions);
|
||||
}
|
||||
|
||||
double Expression::eval(const peg::Ast &ast)
|
||||
{
|
||||
const auto &nodes = ast.nodes;
|
||||
if (ast.name == "NUMBER") {
|
||||
return stod(std::string(ast.token));
|
||||
} else if (ast.name == "FUNCTION") {
|
||||
QString name = QString::fromStdString(std::string(nodes[0]->token));
|
||||
if (!fns.contains(name)) {
|
||||
return 0;
|
||||
}
|
||||
return fns[name](eval(*nodes[1]));
|
||||
} else if (ast.name == "VARIABLE") {
|
||||
return value;
|
||||
} else if (ast.name[0] == 'P') {
|
||||
double result = eval(*nodes[0]);
|
||||
for (unsigned int i = 1; i < nodes.size(); i += 2) {
|
||||
double arg = eval(*nodes[i + 1]);
|
||||
char operation = nodes[i]->token[0];
|
||||
switch (operation) {
|
||||
case '+':
|
||||
result += arg;
|
||||
break;
|
||||
case '-':
|
||||
result -= arg;
|
||||
break;
|
||||
case '*':
|
||||
result *= arg;
|
||||
break;
|
||||
case '/':
|
||||
result /= arg;
|
||||
break;
|
||||
case '^':
|
||||
result = qPow(result, arg);
|
||||
break;
|
||||
default:
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
double Expression::parse(const QString &expr)
|
||||
{
|
||||
QByteArray ba = expr.toUtf8();
|
||||
|
||||
math.enable_ast();
|
||||
|
||||
std::shared_ptr<peg::Ast> ast;
|
||||
if (math.parse(ba.data(), ast)) {
|
||||
ast = peg::AstOptimizer(true).optimize(ast);
|
||||
return eval(*ast);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef EXPRESSION_H
|
||||
#define EXPRESSION_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
|
||||
namespace peg
|
||||
{
|
||||
template <typename Annotation> struct AstBase;
|
||||
struct EmptyType;
|
||||
typedef AstBase<EmptyType> Ast;
|
||||
} // namespace peg
|
||||
|
||||
class Expression
|
||||
{
|
||||
public:
|
||||
double value;
|
||||
|
||||
explicit Expression(double initial = 0);
|
||||
double parse(const QString &expr);
|
||||
|
||||
private:
|
||||
double eval(const peg::Ast &ast);
|
||||
QMap<QString, std::function<double(double)>> fns;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "levenshtein.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
int levenshteinDistance(const QString &s1, const QString &s2)
|
||||
{
|
||||
int len1 = s1.size();
|
||||
int len2 = s2.size();
|
||||
std::vector<std::vector<int>> dp(len1 + 1, std::vector<int>(len2 + 1));
|
||||
|
||||
for (int i = 0; i <= len1; i++) {
|
||||
dp[i][0] = i;
|
||||
}
|
||||
for (int j = 0; j <= len2; j++) {
|
||||
dp[0][j] = j;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= len1; i++) {
|
||||
for (int j = 1; j <= len2; j++) {
|
||||
int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
|
||||
dp[i][j] = std::min({dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost});
|
||||
}
|
||||
}
|
||||
|
||||
return dp[len1][len2];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @file levenshtein.h
|
||||
* @ingroup Core
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef LEVENSHTEIN_H
|
||||
#define LEVENSHTEIN_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
int levenshteinDistance(const QString &s1, const QString &s2);
|
||||
|
||||
#endif // LEVENSHTEIN_H
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef COCKATRICE_MACROS_H
|
||||
#define COCKATRICE_MACROS_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
// Qt6.7 changed how stateChanged functionality
|
||||
// of QCheckBoxes work.
|
||||
// See https://doc.qt.io/qt-6/qcheckbox.html#checkStateChanged
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
#define QT_STATE_CHANGED checkStateChanged
|
||||
#define QT_STATE_CHANGED_T Qt::CheckState
|
||||
#else
|
||||
#define QT_STATE_CHANGED stateChanged
|
||||
#define QT_STATE_CHANGED_T int
|
||||
#endif
|
||||
|
||||
#endif // COCKATRICE_MACROS_H
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "passwordhasher.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <libcockatrice/rng/rng_sfmt.h>
|
||||
|
||||
QString PasswordHasher::computeHash(const QString &password, const QString &salt)
|
||||
{
|
||||
QCryptographicHash::Algorithm algo = QCryptographicHash::Sha512;
|
||||
const int rounds = 1000;
|
||||
|
||||
QByteArray hash = (salt + password).toUtf8();
|
||||
for (int i = 0; i < rounds; ++i) {
|
||||
hash = QCryptographicHash::hash(hash, algo);
|
||||
}
|
||||
QString hashedPass = salt + QString(hash.toBase64());
|
||||
return hashedPass;
|
||||
}
|
||||
|
||||
QString PasswordHasher::generateRandomSalt(const int len)
|
||||
{
|
||||
static const char alphanum[] = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
QString ret;
|
||||
int size = sizeof(alphanum) - 1;
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
ret.append(alphanum[rng->rand(0, size)]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString PasswordHasher::generateActivationToken()
|
||||
{
|
||||
return QCryptographicHash::hash(generateRandomSalt().toUtf8(), QCryptographicHash::Md5).toBase64().left(16);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef PASSWORDHASHER_H
|
||||
#define PASSWORDHASHER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class PasswordHasher
|
||||
{
|
||||
public:
|
||||
static QString computeHash(const QString &password, const QString &salt);
|
||||
static QString generateRandomSalt(const int len = 16);
|
||||
static QString generateActivationToken();
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
#ifndef COCKATRICE_QT_UTILS_H
|
||||
#define COCKATRICE_QT_UTILS_H
|
||||
#include <QLayout>
|
||||
#include <QObject>
|
||||
|
||||
namespace QtUtils
|
||||
{
|
||||
template <typename T> T *findParentOfType(const QObject *obj)
|
||||
{
|
||||
const QObject *p = obj ? obj->parent() : nullptr;
|
||||
while (p) {
|
||||
if (auto casted = qobject_cast<T *>(const_cast<QObject *>(p))) {
|
||||
return casted;
|
||||
}
|
||||
p = p->parent();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline void clearLayoutRec(QLayout *l)
|
||||
{
|
||||
if (!l) {
|
||||
return;
|
||||
}
|
||||
QLayoutItem *it;
|
||||
while ((it = l->takeAt(0)) != nullptr) {
|
||||
if (QWidget *w = it->widget()) {
|
||||
w->deleteLater();
|
||||
}
|
||||
if (QLayout *sub = it->layout()) {
|
||||
clearLayoutRec(sub);
|
||||
}
|
||||
delete it;
|
||||
}
|
||||
}
|
||||
} // namespace QtUtils
|
||||
|
||||
#endif // COCKATRICE_QT_UTILS_H
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef STRING_LIMITS_H
|
||||
#define STRING_LIMITS_H
|
||||
|
||||
#include <QString>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
/** @brief Max size for short strings, like names and things that are generally a single phrase. */
|
||||
constexpr int MAX_NAME_LENGTH = 0xff;
|
||||
/** @brief Max size for chat messages and text contents. */
|
||||
constexpr int MAX_TEXT_LENGTH = 0xfff;
|
||||
/** @brief Max size for deck files and pictures (about 2 megabytes). */
|
||||
constexpr int MAX_FILE_LENGTH = 0x1fffff;
|
||||
|
||||
/** @brief Returns a QString from a std::string, truncated to at most MAX_NAME_LENGTH bytes. */
|
||||
inline QString nameFromStdString(const std::string &_string)
|
||||
{
|
||||
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_NAME_LENGTH));
|
||||
}
|
||||
/** @brief Returns a QString from a std::string, truncated to at most MAX_TEXT_LENGTH bytes. */
|
||||
inline QString textFromStdString(const std::string &_string)
|
||||
{
|
||||
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_TEXT_LENGTH));
|
||||
}
|
||||
/** @brief Returns a QString from a std::string, truncated to at most MAX_FILE_LENGTH bytes. */
|
||||
inline QString fileFromStdString(const std::string &_string)
|
||||
{
|
||||
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_FILE_LENGTH));
|
||||
}
|
||||
|
||||
#endif // STRING_LIMITS_H
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef ZONE_NAMES_H
|
||||
#define ZONE_NAMES_H
|
||||
|
||||
namespace ZoneNames
|
||||
{
|
||||
// Protocol-level zone identifiers shared between client and server.
|
||||
// These must match exactly across all components.
|
||||
|
||||
constexpr const char *TABLE = "table";
|
||||
constexpr const char *GRAVE = "grave";
|
||||
constexpr const char *EXILE = "rfg"; // "removed from game"
|
||||
constexpr const char *HAND = "hand";
|
||||
constexpr const char *DECK = "deck";
|
||||
constexpr const char *SIDEBOARD = "sb";
|
||||
constexpr const char *STACK = "stack";
|
||||
|
||||
} // namespace ZoneNames
|
||||
|
||||
#endif // ZONE_NAMES_H
|
||||
Reference in New Issue
Block a user