Skip to content

Commit a40168a

Browse files
yusufmteNeroBurnerRiksu9000PoohlFintasticMan
authored
New dice-rolling app: InfiniDice! (InfiniTimeOrg#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 (InfiniTimeOrg#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>
1 parent a481af0 commit a40168a

File tree

9 files changed

+268
-1
lines changed

9 files changed

+268
-1
lines changed

src/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ list(APPEND SOURCE_FILES
390390
displayapp/screens/BatteryInfo.cpp
391391
displayapp/screens/Steps.cpp
392392
displayapp/screens/Timer.cpp
393+
displayapp/screens/Dice.cpp
393394
displayapp/screens/PassKey.cpp
394395
displayapp/screens/Error.cpp
395396
displayapp/screens/Alarm.cpp
@@ -609,6 +610,7 @@ set(INCLUDE_FILES
609610
displayapp/screens/Metronome.h
610611
displayapp/screens/Motion.h
611612
displayapp/screens/Timer.h
613+
displayapp/screens/Dice.h
612614
displayapp/screens/Alarm.h
613615
displayapp/Colors.h
614616
displayapp/widgets/Counter.h

src/displayapp/DisplayApp.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "displayapp/screens/FlashLight.h"
2727
#include "displayapp/screens/BatteryInfo.h"
2828
#include "displayapp/screens/Steps.h"
29+
#include "displayapp/screens/Dice.h"
2930
#include "displayapp/screens/PassKey.h"
3031
#include "displayapp/screens/Error.h"
3132

src/displayapp/UserApps.h

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "Controllers.h"
44

55
#include "displayapp/screens/Alarm.h"
6+
#include "displayapp/screens/Dice.h"
67
#include "displayapp/screens/Timer.h"
78
#include "displayapp/screens/Twos.h"
89
#include "displayapp/screens/Tile.h"

src/displayapp/apps/Apps.h.in

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ namespace Pinetime {
2727
Metronome,
2828
Motion,
2929
Steps,
30+
Dice,
3031
PassKey,
3132
QuickSettings,
3233
Settings,

src/displayapp/apps/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ else ()
1010
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paint")
1111
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paddle")
1212
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Twos")
13+
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice")
1314
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome")
1415
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation")
1516
#set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather")

src/displayapp/fonts/fonts.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
{
99
"file": "FontAwesome5-Solid+Brands+Regular.woff",
10-
"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"
10+
"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"
1111
}
1212
],
1313
"bpp": 1,

src/displayapp/screens/Dice.cpp

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#include "displayapp/screens/Dice.h"
2+
#include "displayapp/screens/Screen.h"
3+
#include "displayapp/screens/Symbols.h"
4+
#include "components/settings/Settings.h"
5+
#include "components/motor/MotorController.h"
6+
#include "components/motion/MotionController.h"
7+
8+
using namespace Pinetime::Applications::Screens;
9+
10+
namespace {
11+
lv_obj_t* MakeLabel(lv_font_t* font,
12+
lv_color_t color,
13+
lv_label_long_mode_t longMode,
14+
uint8_t width,
15+
lv_label_align_t labelAlignment,
16+
const char* text,
17+
lv_obj_t* reference,
18+
lv_align_t alignment,
19+
int8_t x,
20+
int8_t y) {
21+
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
22+
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
23+
lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
24+
lv_label_set_long_mode(label, longMode);
25+
if (width != 0) {
26+
lv_obj_set_width(label, width);
27+
}
28+
lv_label_set_align(label, labelAlignment);
29+
lv_label_set_text(label, text);
30+
lv_obj_align(label, reference, alignment, x, y);
31+
return label;
32+
}
33+
34+
void btnRollEventHandler(lv_obj_t* obj, lv_event_t event) {
35+
auto* screen = static_cast<Dice*>(obj->user_data);
36+
if (event == LV_EVENT_CLICKED) {
37+
screen->Roll();
38+
}
39+
}
40+
}
41+
42+
Dice::Dice(Controllers::MotionController& motionController,
43+
Controllers::MotorController& motorController,
44+
Controllers::Settings& settingsController)
45+
: motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
46+
std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()),
47+
static_cast<uint32_t>(motionController.X()),
48+
static_cast<uint32_t>(motionController.Y()),
49+
static_cast<uint32_t>(motionController.Z())};
50+
gen.seed(sseq);
51+
52+
lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
53+
LV_COLOR_WHITE,
54+
LV_LABEL_LONG_EXPAND,
55+
0,
56+
LV_LABEL_ALIGN_CENTER,
57+
"count",
58+
lv_scr_act(),
59+
LV_ALIGN_IN_TOP_LEFT,
60+
0,
61+
0);
62+
63+
lv_obj_t* dCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
64+
LV_COLOR_WHITE,
65+
LV_LABEL_LONG_EXPAND,
66+
0,
67+
LV_LABEL_ALIGN_CENTER,
68+
"sides",
69+
nCounterLabel,
70+
LV_ALIGN_OUT_RIGHT_MID,
71+
20,
72+
0);
73+
74+
nCounter.Create();
75+
lv_obj_align(nCounter.GetObject(), nCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
76+
nCounter.SetValue(1);
77+
78+
dCounter.Create();
79+
lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
80+
dCounter.SetValue(6);
81+
82+
std::uniform_int_distribution<> distrib(0, resultColors.size() - 1);
83+
currentColorIndex = distrib(gen);
84+
85+
resultTotalLabel = MakeLabel(&jetbrains_mono_42,
86+
resultColors[currentColorIndex],
87+
LV_LABEL_LONG_BREAK,
88+
120,
89+
LV_LABEL_ALIGN_CENTER,
90+
"",
91+
lv_scr_act(),
92+
LV_ALIGN_IN_TOP_RIGHT,
93+
11,
94+
38);
95+
resultIndividualLabel = MakeLabel(&jetbrains_mono_bold_20,
96+
resultColors[currentColorIndex],
97+
LV_LABEL_LONG_BREAK,
98+
90,
99+
LV_LABEL_ALIGN_CENTER,
100+
"",
101+
resultTotalLabel,
102+
LV_ALIGN_OUT_BOTTOM_MID,
103+
0,
104+
10);
105+
106+
Roll();
107+
openingRoll = false;
108+
109+
btnRoll = lv_btn_create(lv_scr_act(), nullptr);
110+
btnRoll->user_data = this;
111+
lv_obj_set_event_cb(btnRoll, btnRollEventHandler);
112+
lv_obj_set_size(btnRoll, 240, 50);
113+
lv_obj_align(btnRoll, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
114+
115+
btnRollLabel = MakeLabel(&jetbrains_mono_bold_20,
116+
LV_COLOR_WHITE,
117+
LV_LABEL_LONG_EXPAND,
118+
0,
119+
LV_LABEL_ALIGN_CENTER,
120+
Symbols::dice,
121+
btnRoll,
122+
LV_ALIGN_CENTER,
123+
0,
124+
0);
125+
126+
// Spagetti code in motion controller: it only updates the shake speed when shake to wake is on...
127+
enableShakeForDice = !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake);
128+
if (enableShakeForDice) {
129+
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, true);
130+
}
131+
refreshTask = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
132+
}
133+
134+
Dice::~Dice() {
135+
// reset the shake to wake mode.
136+
if (enableShakeForDice) {
137+
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, false);
138+
enableShakeForDice = false;
139+
}
140+
lv_task_del(refreshTask);
141+
lv_obj_clean(lv_scr_act());
142+
}
143+
144+
void Dice::Refresh() {
145+
// we only reset the hysteresis when at rest
146+
if (motionController.CurrentShakeSpeed() >= settingsController.GetShakeThreshold()) {
147+
if (currentRollHysteresis <= 0) {
148+
// this timestamp is used for the screen timeout
149+
lv_disp_get_next(NULL)->last_activity_time = lv_tick_get();
150+
151+
Roll();
152+
}
153+
} else if (currentRollHysteresis > 0)
154+
--currentRollHysteresis;
155+
}
156+
157+
void Dice::Roll() {
158+
uint8_t resultIndividual;
159+
uint16_t resultTotal = 0;
160+
std::uniform_int_distribution<> distrib(1, dCounter.GetValue());
161+
162+
lv_label_set_text(resultIndividualLabel, "");
163+
164+
if (nCounter.GetValue() == 1) {
165+
resultTotal = distrib(gen);
166+
if (dCounter.GetValue() == 2) {
167+
switch (resultTotal) {
168+
case 1:
169+
lv_label_set_text(resultIndividualLabel, "HEADS");
170+
break;
171+
case 2:
172+
lv_label_set_text(resultIndividualLabel, "TAILS");
173+
break;
174+
}
175+
}
176+
} else {
177+
for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
178+
resultIndividual = distrib(gen);
179+
resultTotal += resultIndividual;
180+
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
181+
if (i < (nCounter.GetValue() - 1)) {
182+
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, "+");
183+
}
184+
}
185+
}
186+
187+
lv_label_set_text_fmt(resultTotalLabel, "%d", resultTotal);
188+
if (openingRoll == false) {
189+
motorController.RunForDuration(30);
190+
NextColor();
191+
currentRollHysteresis = rollHysteresis;
192+
}
193+
}
194+
195+
void Dice::NextColor() {
196+
currentColorIndex = (currentColorIndex + 1) % resultColors.size();
197+
lv_obj_set_style_local_text_color(resultTotalLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
198+
lv_obj_set_style_local_text_color(resultIndividualLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
199+
}

src/displayapp/screens/Dice.h

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
3+
#include "displayapp/apps/Apps.h"
4+
#include "displayapp/screens/Screen.h"
5+
#include "displayapp/widgets/Counter.h"
6+
#include "displayapp/Controllers.h"
7+
#include "Symbols.h"
8+
9+
#include <array>
10+
#include <random>
11+
12+
namespace Pinetime {
13+
namespace Applications {
14+
namespace Screens {
15+
class Dice : public Screen {
16+
public:
17+
Dice(Controllers::MotionController& motionController,
18+
Controllers::MotorController& motorController,
19+
Controllers::Settings& settingsController);
20+
~Dice() override;
21+
void Roll();
22+
void Refresh() override;
23+
24+
private:
25+
lv_obj_t* btnRoll;
26+
lv_obj_t* btnRollLabel;
27+
lv_obj_t* resultTotalLabel;
28+
lv_obj_t* resultIndividualLabel;
29+
lv_task_t* refreshTask;
30+
bool enableShakeForDice = false;
31+
32+
std::mt19937 gen;
33+
34+
std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
35+
uint8_t currentColorIndex;
36+
void NextColor();
37+
38+
Widgets::Counter nCounter = Widgets::Counter(1, 9, jetbrains_mono_42);
39+
Widgets::Counter dCounter = Widgets::Counter(2, 99, jetbrains_mono_42);
40+
41+
bool openingRoll = true;
42+
uint8_t currentRollHysteresis = 0;
43+
static constexpr uint8_t rollHysteresis = 10;
44+
45+
Controllers::MotorController& motorController;
46+
Controllers::MotionController& motionController;
47+
Controllers::Settings& settingsController;
48+
};
49+
}
50+
51+
template <>
52+
struct AppTraits<Apps::Dice> {
53+
static constexpr Apps app = Apps::Dice;
54+
static constexpr const char* icon = Screens::Symbols::dice;
55+
56+
static Screens::Screen* Create(AppControllers& controllers) {
57+
return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
58+
};
59+
};
60+
}
61+
}

src/displayapp/screens/Symbols.h

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ namespace Pinetime {
3434
static constexpr const char* hourGlass = "\xEF\x89\x92";
3535
static constexpr const char* lapsFlag = "\xEF\x80\xA4";
3636
static constexpr const char* drum = "\xEF\x95\xA9";
37+
static constexpr const char* dice = "\xEF\x94\xA2";
3738
static constexpr const char* eye = "\xEF\x81\xAE";
3839
static constexpr const char* home = "\xEF\x80\x95";
3940
static constexpr const char* sleep = "\xEE\xBD\x84";

0 commit comments

Comments
 (0)