aboutsummaryrefslogtreecommitdiffstats
path: root/src/displayapp/screens/Dice.cpp
diff options
context:
space:
mode:
authorYusuf Ebrahim <45940010+yusufmte@users.noreply.github.com>2024-01-23 03:45:52 -0500
committerGitHub <noreply@github.com>2024-01-23 09:45:52 +0100
commita40168a9d7f13891c541055b167a315eb752cf1d (patch)
tree2bddfaa9cd7cad1b2cef89f22a2a1228d94acd67 /src/displayapp/screens/Dice.cpp
parenta481af06cff915ca37deb5b967433480612186aa (diff)
New dice-rolling app: InfiniDice! (#1326)
Add new App `Dice.h` to randomly roll the dice(s). The number of dice can range from 1-9 (default 1), and the sides can range from d2-d99 (default d2). To have a haptic feedback we make Dice vibrate on roll. Regarding the use of C++ `<random>` library: There are known problems with `rand()` and `srand()` (see https://en.cppreference.com/w/cpp/numeric/random/rand) and the `<random>` library is preferred for this reason. The function used from `<random>` also avoids a very rare bias that would occur using `rand()` and modulo, when `RAND_MAX` is not a multiple of `d` and the initially generated number falls in the last "short" segment. This commit also updates the seed to derive entropy (via `seed_seq`) from a mix of the system tick count and the x,y,z components of the PineTime motion controller -- taking inspiration from and with credit to @w4tsn (https://github.com/InfiniTimeOrg/InfiniTime/pull/1199) Thanks for suggestions: * in Dice, when rolling 1d2, also show "HEADS" or "TAILS" -- suggestion by @medeyko * ui adjustments and result realignment -- suggestion by @Boteium --------- Co-authored-by: NeroBurner <pyro4hell@gmail.com> Co-authored-by: Riku Isokoski <riksu9000@gmail.com> Co-authored-by: Paul Weiß <45500341+Poohl@users.noreply.github.com> Co-authored-by: FintasticMan <finlay.neon.kid@gmail.com>
Diffstat (limited to 'src/displayapp/screens/Dice.cpp')
-rw-r--r--src/displayapp/screens/Dice.cpp199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/displayapp/screens/Dice.cpp b/src/displayapp/screens/Dice.cpp
new file mode 100644
index 00000000..302c5f3f
--- /dev/null
+++ b/src/displayapp/screens/Dice.cpp
@@ -0,0 +1,199 @@
+#include "displayapp/screens/Dice.h"
+#include "displayapp/screens/Screen.h"
+#include "displayapp/screens/Symbols.h"
+#include "components/settings/Settings.h"
+#include "components/motor/MotorController.h"
+#include "components/motion/MotionController.h"
+
+using namespace Pinetime::Applications::Screens;
+
+namespace {
+ lv_obj_t* MakeLabel(lv_font_t* font,
+ lv_color_t color,
+ lv_label_long_mode_t longMode,
+ uint8_t width,
+ lv_label_align_t labelAlignment,
+ const char* text,
+ lv_obj_t* reference,
+ lv_align_t alignment,
+ int8_t x,
+ int8_t y) {
+ lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
+ lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
+ lv_label_set_long_mode(label, longMode);
+ if (width != 0) {
+ lv_obj_set_width(label, width);
+ }
+ lv_label_set_align(label, labelAlignment);
+ lv_label_set_text(label, text);
+ lv_obj_align(label, reference, alignment, x, y);
+ return label;
+ }
+
+ void btnRollEventHandler(lv_obj_t* obj, lv_event_t event) {
+ auto* screen = static_cast<Dice*>(obj->user_data);
+ if (event == LV_EVENT_CLICKED) {
+ screen->Roll();
+ }
+ }
+}
+
+Dice::Dice(Controllers::MotionController& motionController,
+ Controllers::MotorController& motorController,
+ Controllers::Settings& settingsController)
+ : motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
+ std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()),
+ static_cast<uint32_t>(motionController.X()),
+ static_cast<uint32_t>(motionController.Y()),
+ static_cast<uint32_t>(motionController.Z())};
+ gen.seed(sseq);
+
+ lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
+ LV_COLOR_WHITE,
+ LV_LABEL_LONG_EXPAND,
+ 0,
+ LV_LABEL_ALIGN_CENTER,
+ "count",
+ lv_scr_act(),
+ LV_ALIGN_IN_TOP_LEFT,
+ 0,
+ 0);
+
+ lv_obj_t* dCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
+ LV_COLOR_WHITE,
+ LV_LABEL_LONG_EXPAND,
+ 0,
+ LV_LABEL_ALIGN_CENTER,
+ "sides",
+ nCounterLabel,
+ LV_ALIGN_OUT_RIGHT_MID,
+ 20,
+ 0);
+
+ nCounter.Create();
+ lv_obj_align(nCounter.GetObject(), nCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
+ nCounter.SetValue(1);
+
+ dCounter.Create();
+ lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
+ dCounter.SetValue(6);
+
+ std::uniform_int_distribution<> distrib(0, resultColors.size() - 1);
+ currentColorIndex = distrib(gen);
+
+ resultTotalLabel = MakeLabel(&jetbrains_mono_42,
+ resultColors[currentColorIndex],
+ LV_LABEL_LONG_BREAK,
+ 120,
+ LV_LABEL_ALIGN_CENTER,
+ "",
+ lv_scr_act(),
+ LV_ALIGN_IN_TOP_RIGHT,
+ 11,
+ 38);
+ resultIndividualLabel = MakeLabel(&jetbrains_mono_bold_20,
+ resultColors[currentColorIndex],
+ LV_LABEL_LONG_BREAK,
+ 90,
+ LV_LABEL_ALIGN_CENTER,
+ "",
+ resultTotalLabel,
+ LV_ALIGN_OUT_BOTTOM_MID,
+ 0,
+ 10);
+
+ Roll();
+ openingRoll = false;
+
+ btnRoll = lv_btn_create(lv_scr_act(), nullptr);
+ btnRoll->user_data = this;
+ lv_obj_set_event_cb(btnRoll, btnRollEventHandler);
+ lv_obj_set_size(btnRoll, 240, 50);
+ lv_obj_align(btnRoll, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
+
+ btnRollLabel = MakeLabel(&jetbrains_mono_bold_20,
+ LV_COLOR_WHITE,
+ LV_LABEL_LONG_EXPAND,
+ 0,
+ LV_LABEL_ALIGN_CENTER,
+ Symbols::dice,
+ btnRoll,
+ LV_ALIGN_CENTER,
+ 0,
+ 0);
+
+ // Spagetti code in motion controller: it only updates the shake speed when shake to wake is on...
+ enableShakeForDice = !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake);
+ if (enableShakeForDice) {
+ settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, true);
+ }
+ refreshTask = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
+}
+
+Dice::~Dice() {
+ // reset the shake to wake mode.
+ if (enableShakeForDice) {
+ settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, false);
+ enableShakeForDice = false;
+ }
+ lv_task_del(refreshTask);
+ lv_obj_clean(lv_scr_act());
+}
+
+void Dice::Refresh() {
+ // we only reset the hysteresis when at rest
+ if (motionController.CurrentShakeSpeed() >= settingsController.GetShakeThreshold()) {
+ if (currentRollHysteresis <= 0) {
+ // this timestamp is used for the screen timeout
+ lv_disp_get_next(NULL)->last_activity_time = lv_tick_get();
+
+ Roll();
+ }
+ } else if (currentRollHysteresis > 0)
+ --currentRollHysteresis;
+}
+
+void Dice::Roll() {
+ uint8_t resultIndividual;
+ uint16_t resultTotal = 0;
+ std::uniform_int_distribution<> distrib(1, dCounter.GetValue());
+
+ lv_label_set_text(resultIndividualLabel, "");
+
+ if (nCounter.GetValue() == 1) {
+ resultTotal = distrib(gen);
+ if (dCounter.GetValue() == 2) {
+ switch (resultTotal) {
+ case 1:
+ lv_label_set_text(resultIndividualLabel, "HEADS");
+ break;
+ case 2:
+ lv_label_set_text(resultIndividualLabel, "TAILS");
+ break;
+ }
+ }
+ } else {
+ for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
+ resultIndividual = distrib(gen);
+ resultTotal += resultIndividual;
+ lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
+ if (i < (nCounter.GetValue() - 1)) {
+ lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, "+");
+ }
+ }
+ }
+
+ lv_label_set_text_fmt(resultTotalLabel, "%d", resultTotal);
+ if (openingRoll == false) {
+ motorController.RunForDuration(30);
+ NextColor();
+ currentRollHysteresis = rollHysteresis;
+ }
+}
+
+void Dice::NextColor() {
+ currentColorIndex = (currentColorIndex + 1) % resultColors.size();
+ lv_obj_set_style_local_text_color(resultTotalLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
+ lv_obj_set_style_local_text_color(resultIndividualLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
+}