InfiniTime icon indicating copy to clipboard operation
InfiniTime copied to clipboard

Add dice rolling app

Open stoehraj opened this issue 3 years ago • 39 comments

Preview Video

https://user-images.githubusercontent.com/31697004/128671489-f3a153a2-a103-4d30-8fe4-19bd4dd53d4a.mp4

Description

This PR adds a new dice rolling app. I play D&D and thought it would be fun to be able to roll dice on my PineTime, so here we are. I thought others might enjoy it too.

This is what the basic UI looks like:

dice_ui

There are buttons to select the number and size of dice. The number of dice can be between 1 and 99, and the supported dice sizes are d2, d3, d4, d6, d8, d12, d20, and d100.

When the desired number and size of dice have been configured, the "Roll" button will, well, roll the dice. Rolls can also be made by slowly rolling the wrist, as shown below:

https://user-images.githubusercontent.com/31697004/128671511-c4d74205-6235-47f4-ac0b-bc59e43df45a.mp4

If the roll is made using the motion control, the watch will vibrate, giving feedback that the roll occurred.

After rolling, the first line of the output box at the top of the screens shows the total result, and, if multiple dice are rolled, the second line shows each individual die roll.

stoehraj avatar Aug 09 '21 07:08 stoehraj

I might overlook something - but if you use rand() and do not set seed by srand(), do you get the same pseudorandom numbers again after InfiniTime reboots? I cannot see srand anywhere in the InfiniTime code, so either it should be set system-wide after boot based on NRF52832 RNG peripheral, or in the app where seed could be for example uptime in milliseconds. Otherwise, nice app. Thanks for adding this feature!

hubmartin avatar Aug 09 '21 11:08 hubmartin

I might overlook something - but if you use rand() and do not set seed by srand(), do you get the same pseudorandom numbers again after InfiniTime reboots? I cannot see srand anywhere in the InfiniTime code, so either it should be set system-wide after boot based on NRF52832 RNG peripheral, or in the app where seed could be for example uptime in milliseconds. Otherwise, nice app. Thanks for adding this feature!

I saw that another app was using rand() (Twos) without anything additional, so I figured everything was all set up for it. I just tested and do indeed get the same output if I do the same roll after a reboot. I'll take care of that.

stoehraj avatar Aug 09 '21 13:08 stoehraj

@hubmartin I could use some help with the srand call for seeding the random number generator. I tried using srand(time(nullptr)); in my object's constructor and also tried doing srand(std::chrono::system_clock::now().time_since_epoch().count()); and neither seemed to have an impact -- that's to say, I could still reproduce getting the same roll each time after a reboot. Would it be better to get the system time using the DateTimeController functions? Also would it be better to have the seed set system-wide as you mentioned? What would that look like?

stoehraj avatar Aug 12 '21 04:08 stoehraj

My guess is that constructors are run immediatelly after start and they are called cycle-perfect every time, so you seed the random, but it is same every time. You have to call srand somewhere where the called function is run based on user's input. It might be Dice::createScreen() which if i'm not mistaken is called when you run application

Then I would suggest to seed rather some milliseconds counter/timer. But your srand(std::chrono::system_clock::now().time_since_epoch().count()); might work, or I see also dateTimeController.Uptime().count() value. Its seconds counter so it might be ok, but I would suggest to find millisecond counter somewhere. Not sure whether it has some function or it needs to be added, because it seems like lot of them returns just a seconds.

hubmartin avatar Aug 12 '21 18:08 hubmartin

This is a cool idea

virtualbeehive avatar Aug 13 '21 17:08 virtualbeehive

With @hubmartin suggestion of using the screen constructor using the current time should be much more effective, dateTimeController.CurrentDateTime().count() should work well as long as the clock is set.

geekbozu avatar Aug 18 '21 01:08 geekbozu

@hubmartin @geekbozu I did a bit more looking at this -- I ended up using srand(dateTimeController.CurrentDateTime().time_since_epoch().count()); in the constructor, and that seemed to do the trick. No repeat rolls after rebooting and trying the same roll (10 six-sided dice) three times.

I believe the constructor is actually called every time the app is opened, rather than once every boot, though with this implementation (using current time rather than uptime) it shouldn't really matter either way.

stoehraj avatar Aug 18 '21 03:08 stoehraj

I gave this a try the other day and it worked really well I will try the new changes and see if it works out well. Never can go wrong with more features.

Is there any plans to actually make apps configurable on compile time so we can remove stuff we don't want when we finally get to the point of running out of room. Not necessarily something you need to figure out just more of a curiosity.

geekbozu avatar Aug 21 '21 19:08 geekbozu

For the seed part for the random number generation, in an embedded software project I participate with we used ADC values on unused pins or very fluctuant voltage in order to have something different each time. I don't know if we have an ADC on the nrf but we can try to use something proportional to the battery voltage for example before filtering if possible.

toitoinou avatar Aug 30 '21 20:08 toitoinou

@toitoinou NRF52 has special and certified RNG peripheral exactly for this reason. I did some quick tests and had something wrong, the numbers did not come. If anyone could look into that it would be great to have system-wide random seed right after boot for the whole system.

hubmartin avatar Aug 30 '21 21:08 hubmartin

Using the RNG is pretty simple on the NRF52 https://devzone.nordicsemi.com/f/nordic-q-a/19429/random-number-without-softdevice. I suggest seeding using srand on boot-up.

tmilburn avatar Sep 01 '21 15:09 tmilburn

If we are going to use the hardware RNG maybe consider making an "RNG" driver so it can maintain portability between different platforms with more "ease".....aka just have it default to std::rand() when not on an NRF platform

geekbozu avatar Sep 01 '21 19:09 geekbozu

@geekbozu we don't need to use RNG for rand. Single RNG number on boot and applying it to the random seed srand should be enough. Then srand gives us number immediatelly. RNG is "slow" if you need many numbers in a row. Maybe could be an issue, maybe not.

hubmartin avatar Sep 02 '21 07:09 hubmartin

This is such a cool idea. Bump, to see this finished and included in the next build.

jp-bennett avatar Sep 24 '21 01:09 jp-bennett

@geekbozu we don't need to use RNG for rand. Single RNG number on boot and applying it to the random seed srand should be enough. Then srand gives us number immediatelly. RNG is "slow" if you need many numbers in a row. Maybe could be an issue, maybe not.

Yeah this makes a ton of sense. Even more so for applications where randomness is only partially critical.

geekbozu avatar Sep 24 '21 13:09 geekbozu

This is such a cool idea. Bump, to see this finished and included in the next build.

Hey -- thanks for the support. I'd love to finish this up and get it included in the next build.

Unfortunately life has gotten in the way a bit -- I hopefully should have some time in the next couple weeks to come back to it.

stoehraj avatar Sep 25 '21 18:09 stoehraj

A small recommendation.

When the icon is pressed change the icon to the number that has been rolled , for other things long pressing it can open the full app.

ObiKeahloa avatar Sep 26 '21 04:09 ObiKeahloa

I just pushed new changes that make the dice app compatible with 1.6.0. I likely still have to revisit the tidy warnings, and also the wrist-motion rolling seems to not work on the new version. I'll try to look into these items over the next few days.

The app itself runs fine otherwise.

stoehraj avatar Sep 30 '21 07:09 stoehraj

There are actually two shake detection algorithms in the works. #691 #660. Seems like the roll detection is quite sensitive, so would a shake work better here too?

Riksu9000 avatar Sep 30 '21 10:09 Riksu9000

There are actually two shake detection algorithms in the works. #691 #660. Seems like the roll detection is quite sensitive, so would a shake work better here too?

Oh, that's awesome -- that would be perfect for this application. I'll keep my eyes on that.

stoehraj avatar Oct 01 '21 01:10 stoehraj

@Riksu9000 -- I believe I have all format/tidy issues worked out. At least I didn't see anymore on my end, though I'm not entirely sure I was using clang-tidy correctly. In the meantime, I might just leave this PR open to see if progress gets made on the shake algorithm in the near future. Or do you think it would be better to just strip out my attempt at the motion controls, get the PR pulled in without them, and add motion controls back in later once #691 is pulled in?

Either way, I'd like to squash my commits once the code is finalized and before completing the PR just to keep the git history a bit more clean.

stoehraj avatar Oct 01 '21 07:10 stoehraj

The reason that the roll gesture isn't working is because Refresh() now requires an LVGL task.

This will call the Refresh() function every 20 ticks through the callback function, which is defined in Screen.cpp

taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);

But maybe it's best to remove the gesture and add it separately, as this app works fine without it.

One more thing I noticed while testing is that maybe on pressing the roll button the watch should vibrate or give some other feedback, so we can tell that the tap was actually registered if it ends up rolling the same number.

Riksu9000 avatar Oct 01 '21 11:10 Riksu9000

I would recommend removing the roll and adding it back later. There should be some changes coming down the pipeline on how the BMA interface is defined...one less thing to update with that PR would be nice ;)

geekbozu avatar Oct 01 '21 13:10 geekbozu

@stoehraj Hi, was working on other PR and discovered that Nimble is already using RNG peripheral and it has nice driver and wrapper for rand(). All I had to do was:

#include <controller/ble_ll.h> and then call uint32_t r = ble_ll_rand(); to get the value.

It seems like Nimble has some buffer of pre-generated values so it seems like you can call ble_ll_rand instead of rand directly without any waiting for RNG to generate numbers. This also explains why I was not able to use RNG, since Nimble reconfigured registers for its own use.

hubmartin avatar Oct 03 '21 13:10 hubmartin

@stoehraj Hi. Could you update this PR, so we can merge it hopefully?

Riksu9000 avatar Jan 08 '22 11:01 Riksu9000

I think that in the case when n1, d2 the indicator also should write (heads) or (tails) for those who wishes to flip a coin, see #919

medeyko avatar Jan 08 '22 21:01 medeyko

@Riksu9000

update this PR, so we can merge it hopefully

As far as I see, @stoehraj doesn't respond. What should be updated in this PR? Maybe it could be done w/o @stoehraj?

medeyko avatar Feb 06 '22 22:02 medeyko

@stoehraj Hi. Could you update this PR, so we can merge it hopefully?

@Riksu9000 Hey -- apologies for the extremely late response and lack of activity. I see you've changed some stuff over the past few days. I just flashed the latest stuff onto my unit to play around with a bit.

I do want to try out what @hubmartin suggested with using ble_ll_rand rather than just rand for getting the random values. I'll report back.

Also, if nothing else, I want to squash all of my prior commits to clean up the git history a bit before this gets merged in.

stoehraj avatar Feb 10 '22 07:02 stoehraj

@hubmartin -- the ble_ll_rand() call seems to work really nicely. Just pushed up changes to move to that over the normal rand().

stoehraj avatar Feb 10 '22 07:02 stoehraj

@Riksu9000 -- do you care if I squash your commits in with mine? Or would you prefer to keep yours separate?

stoehraj avatar Feb 10 '22 07:02 stoehraj