diff --git a/clangify.sh b/clangify.sh
index 9113952e..671c69b9 100755
--- a/clangify.sh
+++ b/clangify.sh
@@ -14,6 +14,7 @@ include=("common" \
"servatrice/src")
exclude=("servatrice/src/smtp" \
"common/sfmt" \
+"common/lib" \
"oracle/src/zip" \
"oracle/src/lzma" \
"oracle/src/qt-json")
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index 3619cc0d..aff2f002 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -351,6 +351,7 @@
resources/tips/images/cockatrice_register.png
resources/tips/images/cockatrice_wiki.png
resources/tips/images/coin_flip.png
+ resources/tips/images/counter_expression.png
resources/tips/images/face_down.png
resources/tips/images/filter_games.png
resources/tips/images/github_logo.png
diff --git a/cockatrice/resources/tips/images/counter_expression.png b/cockatrice/resources/tips/images/counter_expression.png
new file mode 100644
index 00000000..0e6bbcef
Binary files /dev/null and b/cockatrice/resources/tips/images/counter_expression.png differ
diff --git a/cockatrice/resources/tips/tips_of_the_day.xml b/cockatrice/resources/tips/tips_of_the_day.xml
index 5625fbe0..aa2cb463 100644
--- a/cockatrice/resources/tips/tips_of_the_day.xml
+++ b/cockatrice/resources/tips/tips_of_the_day.xml
@@ -83,4 +83,10 @@
face_down.png
2018-03-01
+
+ Counter expressions
+ When setting a counter value, you can type a math expression in the box and the counter will be set to the result.<br>The "x" variable contains the current counter value.
+ counter_expression.png
+ 2019-02-02
+
\ No newline at end of file
diff --git a/cockatrice/src/abstractcounter.cpp b/cockatrice/src/abstractcounter.cpp
index 42cf6ad8..d9095138 100644
--- a/cockatrice/src/abstractcounter.cpp
+++ b/cockatrice/src/abstractcounter.cpp
@@ -1,9 +1,11 @@
#include "abstractcounter.h"
+#include "expression.h"
#include "pb/command_inc_counter.pb.h"
#include "pb/command_set_counter.pb.h"
#include "player.h"
#include "settingscache.h"
#include
+#include
#include
#include
#include
@@ -17,7 +19,7 @@ AbstractCounter::AbstractCounter(Player *_player,
bool _useNameForShortcut,
QGraphicsItem *parent)
: QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value),
- useNameForShortcut(_useNameForShortcut), hovered(false), aDec(0), aInc(0), dialogSemaphore(false),
+ useNameForShortcut(_useNameForShortcut), hovered(false), aDec(nullptr), aInc(nullptr), dialogSemaphore(false),
deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea)
{
setAcceptHoverEvents(true);
@@ -114,7 +116,11 @@ void AbstractCounter::setValue(int _value)
void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (isUnderMouse() && player->getLocal()) {
- if (event->button() == Qt::LeftButton) {
+ if (event->button() == Qt::MidButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
+ if (menu)
+ menu->exec(event->screenPos());
+ event->accept();
+ } else if (event->button() == Qt::LeftButton) {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(1);
@@ -126,10 +132,6 @@ void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
cmd.set_delta(-1);
player->sendGameCommand(cmd);
event->accept();
- } else if (event->button() == Qt::MidButton) {
- if (menu)
- menu->exec(event->screenPos());
- event->accept();
}
} else
event->ignore();
@@ -160,8 +162,12 @@ void AbstractCounter::setCounter()
{
bool ok;
dialogSemaphore = true;
- int newValue = QInputDialog::getInt(0, tr("Set counter"), tr("New value for counter '%1':").arg(name), value,
- -2000000000, 2000000000, 1, &ok);
+ QString expression = QInputDialog::getText(nullptr, tr("Set counter"), tr("New value for counter '%1':").arg(name),
+ QLineEdit::Normal, QString::number(value), &ok);
+
+ Expression exp(value);
+ int newValue = static_cast(exp.parse(expression));
+
if (deleteAfterDialog) {
deleteLater();
return;
diff --git a/cockatrice/src/abstractcounter.h b/cockatrice/src/abstractcounter.h
index 99bcd7ca..2e592348 100644
--- a/cockatrice/src/abstractcounter.h
+++ b/cockatrice/src/abstractcounter.h
@@ -11,6 +11,7 @@ class AbstractCounter : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
+
protected:
Player *player;
int id;
@@ -18,15 +19,17 @@ protected:
int value;
bool useNameForShortcut, hovered;
- void mousePressEvent(QGraphicsSceneMouseEvent *event);
- void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
- void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private:
QAction *aSet, *aDec, *aInc;
QMenu *menu;
bool dialogSemaphore, deleteAfterDialog;
bool shownInCounterArea;
+ bool shortcutActive;
+
private slots:
void refreshShortcuts();
void incrementCounter();
@@ -39,14 +42,19 @@ public:
bool _shownInCounterArea,
int _value,
bool _useNameForShortcut = false,
- QGraphicsItem *parent = 0);
- ~AbstractCounter();
+ QGraphicsItem *parent = nullptr);
+ ~AbstractCounter() override;
+
+ void retranslateUi();
+ void setValue(int _value);
+ void setShortcutsActive();
+ void setShortcutsInactive();
+ void delCounter();
QMenu *getMenu() const
{
return menu;
}
- void retranslateUi();
int getId() const
{
@@ -64,12 +72,6 @@ public:
{
return value;
}
- void setValue(int _value);
- void delCounter();
-
- void setShortcutsActive();
- void setShortcutsInactive();
- bool shortcutActive;
};
#endif
diff --git a/cockatrice/src/dlg_tip_of_the_day.cpp b/cockatrice/src/dlg_tip_of_the_day.cpp
index 67ecc400..37d6465f 100644
--- a/cockatrice/src/dlg_tip_of_the_day.cpp
+++ b/cockatrice/src/dlg_tip_of_the_day.cpp
@@ -13,7 +13,7 @@
#define MIN_TIP_IMAGE_HEIGHT 200
#define MIN_TIP_IMAGE_WIDTH 200
#define MAX_TIP_IMAGE_HEIGHT 300
-#define MAX_TIP_IMAGE_WIDTH 300
+#define MAX_TIP_IMAGE_WIDTH 500
DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent)
{
@@ -149,9 +149,9 @@ void DlgTipOfTheDay::updateTip(int tipId)
qDebug() << "Image failed to load from" << imagePath;
imageLabel->clear();
} else {
- int h = std::min(std::max(image->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT);
+ int h = std::min(std::max(imageLabel->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT);
int w = std::min(std::max(imageLabel->width(), MIN_TIP_IMAGE_WIDTH), MAX_TIP_IMAGE_WIDTH);
- imageLabel->setPixmap(image->scaled(h, w, Qt::KeepAspectRatio, Qt::SmoothTransformation));
+ imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
date->setText("Tip added on: " + tip.getDate().toString("yyyy.MM.dd") + "");
@@ -163,9 +163,7 @@ void DlgTipOfTheDay::updateTip(int tipId)
void DlgTipOfTheDay::resizeEvent(QResizeEvent *event)
{
- int h = imageLabel->height();
- int w = imageLabel->width();
- imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
+ imageLabel->setPixmap(image->scaled(imageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
QWidget::resizeEvent(event);
}
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index f32d59a2..cf04f195 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -26,6 +26,7 @@ SET(common_SOURCES
server_room.cpp
serverinfo_user_container.cpp
sfmt/SFMT.c
+ expression.cpp
)
set(ORACLE_LIBS)
diff --git a/common/expression.cpp b/common/expression.cpp
new file mode 100644
index 00000000..1671d04b
--- /dev/null
+++ b/common/expression.cpp
@@ -0,0 +1,108 @@
+#include "expression.h"
+#include "./lib/peglib.h"
+
+#include
+#include
+#include
+#include
+
+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 <- < [x] >
+ FUNCTION <- NAME '(' EXPRESSION ( [,\n] EXPRESSION )* ')'
+
+ %whitespace <- [ \t\r]*
+ )");
+
+QMap> *default_functions = nullptr;
+
+Expression::Expression(double initial) : value(initial)
+{
+ if (default_functions == nullptr) {
+ default_functions = new QMap>();
+ default_functions->insert("sin", [](double a) { return sin(a); });
+ default_functions->insert("cos", [](double a) { return cos(a); });
+ default_functions->insert("tan", [](double a) { return tan(a); });
+ default_functions->insert("sqrt", [](double a) { return sqrt(a); });
+ default_functions->insert("log", [](double a) { return log(a); });
+ default_functions->insert("log10", [](double a) { return log(a); });
+ default_functions->insert("trunc", [](double a) { return trunc(a); });
+ default_functions->insert("abs", [](double a) { return abs(a); });
+
+ default_functions->insert("floor", [](double a) { return floor(a); });
+ default_functions->insert("ceil", [](double a) { return ceil(a); });
+ default_functions->insert("round", [](double a) { return round(a); });
+ default_functions->insert("trunc", [](double a) { return trunc(a); });
+ }
+ fns = QMap>(*default_functions);
+}
+
+double Expression::eval(const peg::Ast &ast)
+{
+ const auto &nodes = ast.nodes;
+ if (ast.name == "NUMBER") {
+ return stod(ast.token);
+ } else if (ast.name == "FUNCTION") {
+ QString name = QString::fromStdString(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 (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 = pow(result, arg);
+ break;
+ default:
+ result = 0;
+ break;
+ }
+ }
+ return result;
+ } else {
+ return -1;
+ }
+}
+
+double Expression::parse(const QString &expr)
+{
+ QByteArray ba = expr.toLocal8Bit();
+
+ math.enable_ast();
+
+ std::shared_ptr ast;
+ if (math.parse(ba.data(), ast)) {
+ ast = peg::AstOptimizer(true).optimize(ast);
+ return eval(*ast);
+ }
+
+ return 0;
+}
diff --git a/common/expression.h b/common/expression.h
new file mode 100644
index 00000000..8c6a9493
--- /dev/null
+++ b/common/expression.h
@@ -0,0 +1,28 @@
+#ifndef EXPRESSION_H
+#define EXPRESSION_H
+
+#include
+#include
+#include
+
+namespace peg
+{
+template struct AstBase;
+struct EmptyType;
+typedef AstBase 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> fns;
+};
+
+#endif
diff --git a/common/lib/peglib.h b/common/lib/peglib.h
new file mode 100644
index 00000000..5c5e4c01
--- /dev/null
+++ b/common/lib/peglib.h
@@ -0,0 +1,3293 @@
+//
+// peglib.h
+//
+// Copyright (c) 2015-18 Yuji Hirose. All rights reserved.
+// MIT License
+//
+
+#ifndef CPPPEGLIB_PEGLIB_H
+#define CPPPEGLIB_PEGLIB_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include