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>
This commit is contained in:
parent
a481af06cf
commit
a40168a9d7
9 changed files with 268 additions and 1 deletions
|
@ -390,6 +390,7 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/BatteryInfo.cpp
|
||||
displayapp/screens/Steps.cpp
|
||||
displayapp/screens/Timer.cpp
|
||||
displayapp/screens/Dice.cpp
|
||||
displayapp/screens/PassKey.cpp
|
||||
displayapp/screens/Error.cpp
|
||||
displayapp/screens/Alarm.cpp
|
||||
|
@ -609,6 +610,7 @@ set(INCLUDE_FILES
|
|||
displayapp/screens/Metronome.h
|
||||
displayapp/screens/Motion.h
|
||||
displayapp/screens/Timer.h
|
||||
displayapp/screens/Dice.h
|
||||
displayapp/screens/Alarm.h
|
||||
displayapp/Colors.h
|
||||
displayapp/widgets/Counter.h
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "displayapp/screens/FlashLight.h"
|
||||
#include "displayapp/screens/BatteryInfo.h"
|
||||
#include "displayapp/screens/Steps.h"
|
||||
#include "displayapp/screens/Dice.h"
|
||||
#include "displayapp/screens/PassKey.h"
|
||||
#include "displayapp/screens/Error.h"
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "Controllers.h"
|
||||
|
||||
#include "displayapp/screens/Alarm.h"
|
||||
#include "displayapp/screens/Dice.h"
|
||||
#include "displayapp/screens/Timer.h"
|
||||
#include "displayapp/screens/Twos.h"
|
||||
#include "displayapp/screens/Tile.h"
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace Pinetime {
|
|||
Metronome,
|
||||
Motion,
|
||||
Steps,
|
||||
Dice,
|
||||
PassKey,
|
||||
QuickSettings,
|
||||
Settings,
|
||||
|
|
|
@ -10,6 +10,7 @@ else ()
|
|||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paint")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paddle")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Twos")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation")
|
||||
#set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather")
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
},
|
||||
{
|
||||
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
||||
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf743"
|
||||
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf522, 0xf743"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
|
|
199
src/displayapp/screens/Dice.cpp
Normal file
199
src/displayapp/screens/Dice.cpp
Normal file
|
@ -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]);
|
||||
}
|
61
src/displayapp/screens/Dice.h
Normal file
61
src/displayapp/screens/Dice.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/widgets/Counter.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
#include <array>
|
||||
#include <random>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class Dice : public Screen {
|
||||
public:
|
||||
Dice(Controllers::MotionController& motionController,
|
||||
Controllers::MotorController& motorController,
|
||||
Controllers::Settings& settingsController);
|
||||
~Dice() override;
|
||||
void Roll();
|
||||
void Refresh() override;
|
||||
|
||||
private:
|
||||
lv_obj_t* btnRoll;
|
||||
lv_obj_t* btnRollLabel;
|
||||
lv_obj_t* resultTotalLabel;
|
||||
lv_obj_t* resultIndividualLabel;
|
||||
lv_task_t* refreshTask;
|
||||
bool enableShakeForDice = false;
|
||||
|
||||
std::mt19937 gen;
|
||||
|
||||
std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
|
||||
uint8_t currentColorIndex;
|
||||
void NextColor();
|
||||
|
||||
Widgets::Counter nCounter = Widgets::Counter(1, 9, jetbrains_mono_42);
|
||||
Widgets::Counter dCounter = Widgets::Counter(2, 99, jetbrains_mono_42);
|
||||
|
||||
bool openingRoll = true;
|
||||
uint8_t currentRollHysteresis = 0;
|
||||
static constexpr uint8_t rollHysteresis = 10;
|
||||
|
||||
Controllers::MotorController& motorController;
|
||||
Controllers::MotionController& motionController;
|
||||
Controllers::Settings& settingsController;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Dice> {
|
||||
static constexpr Apps app = Apps::Dice;
|
||||
static constexpr const char* icon = Screens::Symbols::dice;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ namespace Pinetime {
|
|||
static constexpr const char* hourGlass = "\xEF\x89\x92";
|
||||
static constexpr const char* lapsFlag = "\xEF\x80\xA4";
|
||||
static constexpr const char* drum = "\xEF\x95\xA9";
|
||||
static constexpr const char* dice = "\xEF\x94\xA2";
|
||||
static constexpr const char* eye = "\xEF\x81\xAE";
|
||||
static constexpr const char* home = "\xEF\x80\x95";
|
||||
static constexpr const char* sleep = "\xEE\xBD\x84";
|
||||
|
|
Loading…
Reference in a new issue