summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrandogoth <kflux@posteo.de>2023-04-16 15:16:10 +0100
committerjoeycastillo <joeycastillo@utexas.edu>2023-04-16 11:00:08 -0400
commit721887de3a94ee8e7c87ed3ed0235bacf20ffc59 (patch)
treec07c09c8477d666be00a616cf91a4ba366b78d1f
parentbfeca930089e61dc0ce5680c6cfb9beaa0064ec4 (diff)
downloadSensor-Watch-721887de3a94ee8e7c87ed3ed0235bacf20ffc59.tar.gz
Sensor-Watch-721887de3a94ee8e7c87ed3ed0235bacf20ffc59.tar.bz2
Sensor-Watch-721887de3a94ee8e7c87ed3ed0235bacf20ffc59.zip
Coin & Dice Toss & Geomantic Divination Watch Faces (#235)
* init * advanced latlon setting * simple functionality done * lat lon high precision fwd bwd * edit toggle * added readme for branch * DD DMS conversion & cleanup * DD to OLC conversion * olc encoding & decoding * OLC implementation * swapped bools for modes, code cleanup * place name editor * updated button logic, fixed display * load and save places in state array * todo list * simplified OLC functions * geohash conversion functions * geohash display & digit functions * todo * finished geohash implementation * code display function, defaults, bugfixes * read/write file/reg logic * long light in DATA to cancel * write to registry * todo * read & write backup register * file read/write * todo * new more concise button logic, optimizations * todo * renamed & cleaned up, fixed button logic * documentation * documentation * LAP mode for all coordinate screens * faster and more precise geohash algorithm * updated description * updated docu * simple place face * bugfixes, updated documentation * init * meh * added public functions for OLC and Geohash * randonauting face * fix * display fix * cleanup * bugfixes * bugfix * added place * fixed TRNG call * fixed declaration conflict * modulo bias filter * simplified things, chance RNG selection * fixed button logic, better menus * cleanup * documentation * docu fixes * init * basic functions * all needed static functions done * progress * coins and dice done * progress * place update * divination faces functionality done * better divine_bit * figure numbers and names * captions optional * coin animation * dice animation & optimizations * animation * changed names, documented * bugfix * cleanup * reset config --------- Co-authored-by: joeycastillo <joeycastillo@utexas.edu>
-rw-r--r--movement/make/Makefile2
-rw-r--r--movement/movement_faces.h2
-rw-r--r--movement/watch_faces/complication/geomancy_face.c356
-rw-r--r--movement/watch_faces/complication/geomancy_face.h99
-rw-r--r--movement/watch_faces/complication/toss_up_face.c790
-rw-r--r--movement/watch_faces/complication/toss_up_face.h112
6 files changed, 1361 insertions, 0 deletions
diff --git a/movement/make/Makefile b/movement/make/Makefile
index 2d6584fd..d0d53980 100644
--- a/movement/make/Makefile
+++ b/movement/make/Makefile
@@ -111,6 +111,8 @@ SRCS += \
../watch_faces/complication/invaders_face.c \
../watch_faces/clock/world_clock2_face.c \
../watch_faces/complication/time_left_face.c \
+ ../watch_faces/complication/toss_up_face.c \
+ ../watch_faces/complication/geomancy_face.c \
# New watch faces go above this line.
# Leave this line at the bottom of the file; it has all the targets for making your project.
diff --git a/movement/movement_faces.h b/movement/movement_faces.h
index 358333ae..a75deaec 100644
--- a/movement/movement_faces.h
+++ b/movement/movement_faces.h
@@ -87,6 +87,8 @@
#include "invaders_face.h"
#include "world_clock2_face.h"
#include "time_left_face.h"
+#include "toss_up_face.h"
+#include "geomancy_face.h"
#include "dual_timer_face.h"
// New includes go above this line.
diff --git a/movement/watch_faces/complication/geomancy_face.c b/movement/watch_faces/complication/geomancy_face.c
new file mode 100644
index 00000000..689d4e18
--- /dev/null
+++ b/movement/watch_faces/complication/geomancy_face.c
@@ -0,0 +1,356 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "toss_up_face.h"
+#include "geomancy_face.h"
+
+// CONSTANTS //////////////////////////////////////////////////////////////////
+
+// The Bagua 八卦 Trigrams encoded as 3bit tribbles, represented as binary integer
+static const uint32_t bagua = 0b00000101001110010111011100000000;
+
+// The King Wen Sequence 文王卦序 of the I Ching 易經 Hexagrams 卦 encoded as an array
+// of decimal integers in the order of two combined Trigram tribbles from 0b000000 to
+// 0b111111
+static const uint8_t wen_order[] = {
+ 1, 22, 7, 19, 15, 34, 44, 11,
+ 14, 51, 38, 52, 61, 55, 30, 32,
+ 6, 3, 28, 58, 39, 63, 46, 5,
+ 45, 17, 47, 56, 31, 49, 27, 43,
+ 23, 26, 2, 41, 50, 20, 16, 24,
+ 35, 21, 62, 36, 54, 29, 48, 12,
+ 18, 40, 59, 60, 53, 37, 57, 9,
+ 10, 25, 4, 8, 33, 13, 42, 0
+};
+
+// The geomantic figures encoded as 4 bit nibbles, represented as hexadecimal integer
+static const uint64_t geomantic = 0x4ABF39D25E76C180;
+
+// Abbreviations of the Names of the Geomantic Figures in the order of the 4 bit nibbles
+// from 0b0000 to 0b1111
+static const char figures[16][2] = {
+ "VI" /* Via */, "Hd" /* Head of the Dragon */, "PA" /* Puella */, "GF" /* Greater Fortune*/,
+ "PR" /* Puer */, "AQ" /* Acquisitio */, "CA" /* Carcer */, "TR" /* Tristitia */,
+ "Td" /* Tail of the Dragon */, "CO" /* Conjunctio */, "AM" /* Amissio */, "AL" /* Albus */,
+ "LF" /* Lesser Fortune */, "RU" /* Rubeus */, "LA" /* Laetitia */, "PO" /* Populus */
+};
+
+// DECLARATIONS ///////////////////////////////////////////////////////////////
+
+static void geomancy_face_display();
+static nibble_t _geomancy_pick_figure();
+static tribble_t _iching_pick_trigram();
+static uint8_t _iching_form_hexagram();
+static void _geomancy_display(nibble_t code);
+static void _display_hexagram(uint8_t hexagram, char* str);
+static void _fix_broken_line(uint8_t hexagram);
+static void _throw_animation(geomancy_state_t *state);
+
+// WATCH FACE FUNCTIONS ///////////////////////////////////////////////////////
+
+void geomancy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
+ (void) settings;
+ if (*context_ptr == NULL) {
+ *context_ptr = malloc(sizeof(geomancy_state_t));
+ memset(*context_ptr, 0, sizeof(geomancy_state_t));
+ }
+}
+
+void geomancy_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ geomancy_state_t *state = (geomancy_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ state->animate = false;
+ state->animation = 0;
+ watch_display_string(" IChing", 0);
+ break;
+ case EVENT_TICK:
+ if ( state->animate ) {
+ state->animation = (state->animation + 1) % 39;
+ geomancy_face_display(state);
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ if ( state->animate ) break;
+ if ( state->mode <= 1 ) state->mode = 2;
+ else if ( state->mode >= 2 ) state->mode = 0;
+ geomancy_face_display(state);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ if ( state->animate ) break;
+ switch ( state->mode ) {
+ case 0:
+ state->mode++;
+ case 1:
+ state->animate = true;
+ state->i_ching_hexagram = _iching_form_hexagram();
+ break;
+ case 2:
+ state->mode++;
+ case 3:
+ state->animate = true;
+ state->geomantic_figure = _geomancy_pick_figure().bits;
+ break;
+ default:
+ break;
+ }
+ geomancy_face_display(state);
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ if ( state->animate ) break;
+ state->caption = !state->caption;
+ watch_display_string(" ", 0);
+ geomancy_face_display(state);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+ return true;
+}
+
+void geomancy_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+// STATIC FUNCTIONS ///////////////////////////////////////////////////////////
+
+/** @brief display handler */
+static void geomancy_face_display(geomancy_state_t *state) {
+ char token[7] = {0};
+ nibble_t figure = *((nibble_t*) &state->geomantic_figure);
+ switch ( state->mode ) {
+ case 0:
+ watch_display_string(" IChing", 0);
+ break;
+ case 1:
+ _throw_animation(state);
+ if ( !state->animate ) {
+ _display_hexagram(state->i_ching_hexagram, token);
+ watch_display_string(token, 4);
+ _fix_broken_line(state->i_ching_hexagram);
+ if (state->caption) {
+ sprintf(token, "%2d", wen_order[state->i_ching_hexagram] + 1);
+ watch_display_string(token, 2);
+ }
+ }
+ break;
+ case 2:
+ watch_display_string(" GeomCy", 0);
+ break;
+ case 3:
+ _throw_animation(state);
+ if ( !state->animate ) {
+ if ( state->caption ) {
+ sprintf(token, "%c%c", figures[state->geomantic_figure][0], figures[state->geomantic_figure][1]);
+ watch_display_string(token, 0);
+ }
+ _geomancy_display(figure);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/** @brief screen clearing animation between castings */
+static void _throw_animation(geomancy_state_t *state) {
+ movement_request_tick_frequency(16);
+ switch ( state->animation ) {
+ case 0:
+ watch_set_pixel(0, 22);
+ break;
+ case 1:
+ watch_set_pixel(2, 22);
+ watch_set_pixel(2, 23);
+ watch_clear_pixel(0, 22);
+ break;
+ case 2:
+ watch_set_pixel(1, 22);
+ watch_set_pixel(0, 23);
+ break;
+ case 3:
+ watch_set_pixel(2, 0);
+ watch_set_pixel(1, 0);
+ watch_set_pixel(2, 21);
+ watch_set_pixel(1, 21);
+ watch_clear_pixel(2, 22);
+ watch_clear_pixel(1, 22);
+ watch_clear_pixel(2, 23);
+ watch_clear_pixel(0, 23);
+ watch_clear_pixel(1, 23);
+ break;
+ case 4:
+ watch_set_pixel(1, 17);
+ watch_set_pixel(0, 20);
+ watch_set_pixel(2, 10);
+ watch_set_pixel(0, 1);
+ break;
+ case 5:
+ watch_clear_pixel(2, 21);
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(1, 0);
+ watch_clear_pixel(1, 20);
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(0, 21);
+ watch_clear_pixel(1, 1);
+ watch_clear_pixel(0, 0);
+ watch_clear_pixel(2, 1);
+ watch_set_pixel(2, 19);
+ watch_set_pixel(0, 19);
+ watch_set_pixel(1, 2);
+ watch_set_pixel(0, 2);
+ break;
+ case 6:
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 20);
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(0, 1);
+ watch_set_pixel(2, 18);
+ watch_set_pixel(0, 18);
+ watch_set_pixel(2, 3);
+ watch_set_pixel(0, 4);
+ break;
+ case 7:
+ watch_clear_pixel(2, 19);
+ watch_clear_pixel(0, 19);
+ watch_clear_pixel(1, 18);
+ watch_clear_pixel(1, 19);
+ watch_clear_pixel(1, 2);
+ watch_clear_pixel(0, 2);
+ watch_clear_pixel(1, 3);
+ watch_clear_pixel(0, 3);
+ watch_clear_pixel(2, 2);
+ watch_set_pixel(1, 4);
+ watch_set_pixel(0, 5);
+ break;
+ case 8:
+ watch_clear_pixel(2, 18);
+ watch_clear_pixel(0, 18);
+ watch_clear_pixel(2, 3);
+ watch_clear_pixel(0, 4);
+ watch_set_pixel(2, 5);
+ watch_set_pixel(1, 6);
+ break;
+ case 9:
+ watch_clear_pixel(1, 4);
+ watch_clear_pixel(0, 5);
+ watch_clear_pixel(1, 5);
+ watch_clear_pixel(2, 4);
+ watch_clear_pixel(0, 6);
+ break;
+ case 10:
+ watch_clear_pixel(2, 5);
+ watch_clear_pixel(1, 6);
+ break;
+ case 11:
+ state->animate = false;
+ state->animation = 0;
+ movement_request_tick_frequency(1);
+ break;
+ }
+}
+
+// I CHING FUNCTIONS //////////////////////////////////////////////////////////
+
+/** @brief form a trigram from three random bit picks
+ */
+static tribble_t _iching_pick_trigram() {
+ uint8_t index = (divine_bit() << 2) | (divine_bit() << 1) | divine_bit();
+ tribble_t trigram = {(bagua >> (3 * index)) & 0b111};
+ return trigram;
+}
+
+/** @brief form a hexagram from two trigrams
+ */
+static uint8_t _iching_form_hexagram() {
+ tribble_t inner = _iching_pick_trigram();
+ tribble_t outer = _iching_pick_trigram();
+ uint8_t hexagram = (inner.bits << 3) | outer.bits;
+ return hexagram;
+}
+
+/** @brief display hexagram
+ * @details | for unbroken lines and Ξ for broken lines, left of display is bottom
+ */
+static void _display_hexagram(uint8_t hexagram, char* str) {
+ str[6] = '\0'; // Null-terminate the string
+ for (uint8_t i = 0; i < 6; i++) {
+ if (hexagram & (1 << (5 - i))) {
+ str[i] = '1';
+ } else {
+ str[i] = '=';
+ }
+ }
+}
+
+/** @brief when Ξ digits show as = then manually add a line on top
+ */
+static void _fix_broken_line(uint8_t hexagram) {
+ for (uint8_t i = 0; i < 6; i++) {
+ if (!(hexagram & (1 << (5 - i)))) {
+ if ( i == 1 ) watch_set_pixel(2, 20);
+ if ( i == 3 ) watch_set_pixel(2, 1);
+ if ( i == 4 ) watch_set_pixel(2, 2);
+ if ( i == 5 ) watch_set_pixel(2, 4);
+ }
+ }
+}
+
+// GEOMANCY FUNCTIONS /////////////////////////////////////////////////////////
+
+/** @brief choose a geomantic figure from four random bits
+ * @details 0 represents · and 1 represents : counting from the bottom
+ */
+static nibble_t _geomancy_pick_figure() {
+ uint8_t index = (divine_bit() << 3) | (divine_bit() << 2) | (divine_bit() << 1) | divine_bit();
+ nibble_t figure = {(geomantic >> (4 * (15 - index))) & 0xF};
+ return figure;
+}
+
+/** @brief display the geomantic figure, left of display is bottom
+ */
+static void _geomancy_display(nibble_t code) {
+ // draw geomantic figures
+ bool row1 = (code.bits >> 3) & 1;
+ bool row2 = (code.bits >> 2) & 1;
+ bool row3 = (code.bits >> 1) & 1;
+ bool row4 = code.bits & 1;
+
+ if ( row1 ) watch_set_pixel(1, 18); else watch_set_pixel(1, 19);
+ if ( row2 ) { watch_set_pixel(2, 20); watch_set_pixel(0, 21);} else watch_set_pixel(1, 20);
+ if ( row3 ) watch_set_pixel(0, 22); else watch_set_pixel(1, 23);
+ if ( row4 ) { watch_set_pixel(2, 1); watch_set_pixel(0, 0);} else watch_set_pixel(1, 1);
+} \ No newline at end of file
diff --git a/movement/watch_faces/complication/geomancy_face.h b/movement/watch_faces/complication/geomancy_face.h
new file mode 100644
index 00000000..4a19ba85
--- /dev/null
+++ b/movement/watch_faces/complication/geomancy_face.h
@@ -0,0 +1,99 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef GEOMANCY_FACE_H_
+#define GEOMANCY_FACE_H_
+
+#include "movement.h"
+
+/*
+ * GEOMANCY WATCH FACE
+ *
+ * A simple and straightforward watch face for the ancient Eastern geomantic divination system
+ * of I Ching and the western system of "Geomancy". It is an optional addition to the Toss Up
+ * Face.
+ *
+ * The LIGHT button toggles between the two systems of geomancy.
+ *
+ * The ALARM button casts an I Ching hexagram or Geomantic figure based on drawing virtual
+ * stalks from the True Random Number Generator in the Sensor Watch.
+ *
+ * The figures are flipped 90 degrees clockwise, so the left side is the bottom and the
+ * right side the top.
+ *
+ * LONG PRESSING ALARM toggles the display of the King Wen sequence index for the cast I Ching
+ * Hexagram (https://en.wikipedia.org/wiki/King_Wen_sequence )or the abbreviated name for the
+ * cast Geomantic Figure:
+ *
+ * GF - Greater Fortune (Fortuna Major)
+ * LF - Lesser Fortune (Fortuna Minor)
+ * PO - Populus
+ * VI - Via
+ * AL - Albus
+ * CO - Conjunctio
+ * PA - Puella
+ * AM - Amissio
+ * PR - Puer
+ * RU - Rubeus
+ * AQ - Acquisitio
+ * LA - Laetitia
+ * TR - Tristitia
+ * CA - Carcer
+ * HD - Head of the Dragon (Caput Draconis)
+ * TD - Tail of the Dragon (Cauda Draconis)
+ *
+ */
+
+typedef struct {
+ uint8_t bits : 4;
+} nibble_t;
+
+typedef struct {
+ uint8_t bits : 3;
+} tribble_t;
+
+typedef struct {
+ uint8_t mode : 3;
+ uint8_t geomantic_figure;
+ uint8_t i_ching_hexagram : 6;
+ bool caption;
+ uint8_t animation;
+ bool animate;
+} geomancy_state_t;
+
+void geomancy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void geomancy_face_activate(movement_settings_t *settings, void *context);
+bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void geomancy_face_resign(movement_settings_t *settings, void *context);
+
+#define geomancy_face ((const watch_face_t){ \
+ geomancy_face_setup, \
+ geomancy_face_activate, \
+ geomancy_face_loop, \
+ geomancy_face_resign, \
+ NULL, \
+})
+
+#endif // GEOMANCY_FACE_H_
+
diff --git a/movement/watch_faces/complication/toss_up_face.c b/movement/watch_faces/complication/toss_up_face.c
new file mode 100644
index 00000000..1923302f
--- /dev/null
+++ b/movement/watch_faces/complication/toss_up_face.c
@@ -0,0 +1,790 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "toss_up_face.h"
+#if __EMSCRIPTEN__
+#include <time.h>
+#else
+#include "saml22j18a.h"
+#endif
+
+static const char heads[] = { '8', 'h', '4', 'E', '(' };
+static const char tails[] = { '0', '+', 'N', '3', ')' };
+static const uint8_t dd[] = {2, 4, 6, 8, 10,12,20,24,30,32,36,48,99};
+
+static void _roll_dice_multiple(char* result, uint8_t* dice, uint8_t num_dice);
+static void _sort_coins(char* token, uint8_t num_bits, uint8_t bits, char* heads, char* tails);
+void _display_coins(char* token, bool* bit_array, uint8_t length, toss_up_state_t *state);
+static void _toss_up_face_display(toss_up_state_t *state);
+static void _dice_animation(toss_up_state_t *state);
+static void _coin_animation(toss_up_state_t *state);
+
+// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
+
+void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
+ (void) settings;
+ if (*context_ptr == NULL) {
+ *context_ptr = malloc(sizeof(toss_up_state_t));
+ memset(*context_ptr, 0, sizeof(toss_up_state_t));
+ toss_up_state_t *state = (toss_up_state_t *)*context_ptr;
+
+ // defaults
+ state->coin_num = 1;
+ state->dice_num = 1;
+ state->dice_sides[0] = 6;
+ state->dice_sides[1] = 6;
+ state->dice_sides[2] = 6;
+ state->coin_style[0] = '8';
+ state->coin_style[1] = '0';
+ }
+}
+
+void toss_up_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ toss_up_state_t *state = (toss_up_state_t *)context;
+ uint8_t i = 0;
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ watch_display_string(" Coins ", 0);
+ break;
+ case EVENT_TICK:
+ if ( state->animate ) {
+ state->animation = (state->animation + 1);
+ _toss_up_face_display(state);
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ if ( state->animate ) break;
+ // change between coins and dice
+ if ( state->mode <= 1 ) state->mode = 2;
+ else if ( state->mode >= 2 ) state->mode = 0;
+ _toss_up_face_display(state);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ // toss
+ if ( state->animate ) break;
+ switch (state->mode) {
+ case 0:
+ state->mode++;
+ case 1:
+ state->animate = true;
+ for (i = 0; i < state->coin_num; i++) {
+ state->coins[i] = divine_bit();
+ }
+ break;
+ case 2:
+ state->mode++;
+ case 3:
+ state->animate = true;
+ for (i = 0; i < state->dice_num; i++) {
+ state->dice[i] = roll_dice(state->dice_sides[i]);
+ }
+ break;
+ default:
+ break;
+ }
+ _toss_up_face_display(state);
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ if ( state->animate ) break;
+ state->animate = false;
+ switch (state->mode) {
+ case 0: // change to default coin style
+ state->coin_style[0] = heads[0];
+ state->coin_style[1] = tails[0];
+ state->coinface = 0;
+ break;
+ case 1: // change the coin style
+ state->coinface = (state->coinface + 1) % 5;
+ state->coin_style[0] = heads[state->coinface];
+ state->coin_style[1] = tails[state->coinface];
+ break;
+ case 2: // change to default dice sides
+ state->dice_sides[0] = 6;
+ state->dice_sides[1] = 6;
+ state->dice_sides[2] = 6;
+ state->dd = 0;
+ break;
+ case 3: // change the sides of the dice
+ state->dd = (state->dd + 1) % 13;
+ state->dice_sides[state->dice_num-1] = dd[state->dd];
+ state->dice[state->dice_num-1] = dd[state->dd];
+ break;
+ default:
+ break;
+ }
+ _toss_up_face_display(state);
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ if ( state->animate ) break;
+ state->animate = false;
+ switch (state->mode) {
+ case 0: // back to one coin
+ state->coin_num = 1;
+ break;
+ case 1: // up to 6 coins total
+ state->coin_num = (state->coin_num % 6) + 1;
+ break;
+ case 2: // back to one dice
+ state->dice_num = 1;
+ break;
+ case 3: // add up to 3 dice total
+ state->dice_num = (state->dice_num % 3) + 1;
+ state->dd = 0;
+ break;
+ default:
+ break;
+ }
+ _toss_up_face_display(state);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void toss_up_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+// STATIC FUNCTIONS ///////////////////////////////////////////////////////////
+
+/** @brief handles the display
+ */
+static void _toss_up_face_display(toss_up_state_t *state) {
+ char buf[11] = {0};
+ char token[7] = {0};
+ switch ( state->mode ) {
+ case 0: // coins title
+ sprintf(buf, " Coins ");
+ break;
+ case 1: // coins divination
+ _coin_animation(state);
+ if ( !state->animate ) {
+ watch_clear_display();
+ _display_coins(token, state->coins, state->coin_num, state);
+ sprintf(buf, " %s", token);
+ }
+ break;
+ case 2: // dice title
+ sprintf(buf, " Dice ");
+ break;
+ case 3: // dice divination
+ _dice_animation(state);
+ if ( !state->animate ) {
+ _roll_dice_multiple(token, state->dice, state->dice_num + 1);
+ sprintf(buf, " %s", token);
+ }
+ break;
+ default:
+ break;
+ }
+ watch_display_string(buf, 0);
+}
+
+/** @brief divination method to derive a bit from 32 TRNG bits
+ */
+uint8_t divine_bit(void) {
+ uint32_t stalks;
+ do { // modulo bias filter
+ stalks = get_true_entropy(); // get 32 TRNG bits as stalks
+ } while (stalks >= INT32_MAX || stalks <= 0);
+
+ uint8_t pile1_xor = 0;
+ uint8_t pile2_xor = 0;
+ // Divide the stalks into two piles, alternating ends
+ for (uint8_t i = 0; i < 16; i++) {
+ uint8_t left_bit = (stalks >> (31 - 2*i)) & 1;
+ uint8_t right_bit = (stalks >> (30 - 2*i)) & 1;
+ if (i % 2 == 0) {
+ pile1_xor ^= left_bit;
+ pile2_xor ^= right_bit;
+ } else {
+ pile1_xor ^= right_bit;
+ pile2_xor ^= left_bit;
+ }
+ }
+ // Take the XOR of the pile results
+ uint8_t result_xor = pile1_xor ^ pile2_xor;
+ // Output 1 if result_xor is 1, 0 otherwise
+ return result_xor;
+}
+
+/** @brief get 32 True Random Number bits
+ */
+uint32_t get_true_entropy(void) {
+ #if __EMSCRIPTEN__
+ return rand() % INT32_MAX;
+ #else
+ hri_mclk_set_APBCMASK_TRNG_bit(MCLK);
+ hri_trng_set_CTRLA_ENABLE_bit(TRNG);
+
+ while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY)); // Wait for TRNG data to be ready
+
+ hri_trng_clear_CTRLA_ENABLE_bit(TRNG);
+ hri_mclk_clear_APBCMASK_TRNG_bit(MCLK);
+ return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it
+ #endif
+}
+
+// COIN FUNCTIONS /////////////////////////////////////////////////////////////
+
+/** @brief sort tossed coins into a pile of heads and a pile of tails
+ */
+static void _sort_coins(char* token, uint8_t num_bits, uint8_t bits, char* heads, char* tails) {
+ uint8_t num_ones = 0;
+ for (uint8_t i = 0; i < num_bits; i++) {
+ if ((bits >> i) & 1) {
+ *token++ = *heads;
+ num_ones++;
+ }
+ }
+ if ( num_bits < 6 ) {
+ for (uint8_t i = 0; i < (6 - num_bits); i++) {
+ *token++ = ' ';
+ }
+ }
+ for (uint8_t i = 0; i < (num_bits - num_ones); i++) {
+
+ *token++ = *tails;
+ }
+}
+
+/** @brief convert bool array of coinflips to integer for sorting
+ */
+void _display_coins(char* token, bool* bit_array, uint8_t length, toss_up_state_t *state) {
+ uint8_t bits = 0;
+ for (uint8_t i = 0; i < length; i++) {
+ if (bit_array[i]) {
+ bits |= (1 << (length - 1 - i));
+ }
+ }
+ _sort_coins(token, length, bits, &state->coin_style[0], &state->coin_style[1]);
+}
+
+/** @brief coin animation
+ */
+static void _coin_animation(toss_up_state_t *state) {
+ bool heads = false;
+ bool tails = false;
+ for (uint8_t i = 0; i < state->coin_num; i++) {
+ if (state->coins[i] == true) {
+ heads++;
+ } else {
+ tails++;
+ }
+ }
+ movement_request_tick_frequency(32);
+ switch ( state->animation ) {
+ case 0:
+ watch_display_string(" ", 4);
+ if ( heads ) {
+ watch_set_pixel(0, 18);
+ watch_set_pixel(2, 18);
+ } else {
+ state->animation = 12;
+ }
+ break;
+ case 1:
+ if ( heads ) {
+ watch_set_pixel(1, 18);
+ }
+ break;
+ case 2:
+ if ( heads ) {
+ watch_set_pixel(0, 19);
+ watch_set_pixel(2, 19);
+ }
+ break;
+ case 3:
+ if ( heads ) {
+ watch_clear_pixel(0, 18);
+ watch_clear_pixel(2, 18);
+ }
+ break;
+ case 4:
+ if ( heads ) {
+ watch_clear_pixel(1, 18);
+ }
+ break;
+ case 5:
+ if ( heads ) {
+ watch_clear_pixel(0, 19);
+ watch_clear_pixel(2, 19);
+ watch_set_pixel(1, 17);
+ watch_set_pixel(0, 20);
+ }
+ break;
+ case 6:
+ if ( heads ) {
+ watch_set_pixel(2, 20);
+ watch_set_pixel(0, 21);
+ }
+ break;
+ case 7:
+ if ( heads ) {
+ watch_set_pixel(1, 21);
+ watch_set_pixel(2, 21);
+ }
+ break;
+ case 8:
+ if ( heads ) {
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 20);
+ }
+ break;
+ case 9:
+ if ( heads ) {
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(0, 21);
+ }
+ break;
+ case 10:
+ if ( heads ) {
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 21);
+ watch_set_pixel(1, 22);
+ watch_set_pixel(2, 22);
+ }
+ break;
+ case 11:
+ if ( heads ) {
+ watch_set_pixel(0, 22);
+ }
+ break;
+ case 12:
+ if ( heads ) {
+ watch_set_pixel(2, 23);
+ watch_set_pixel(0, 23);
+ }
+ if ( tails ) {
+ watch_set_pixel(0, 18);
+ watch_set_pixel(2, 18);
+ }
+ break;
+ case 13:
+ if ( heads ) {
+ watch_clear_pixel(1, 22);
+ watch_clear_pixel(2, 22);
+ }
+ if ( tails ) {
+ watch_set_pixel(1, 18);
+ }
+ break;
+ case 14:
+ if ( heads ) {
+ watch_clear_pixel(0, 22);
+ }
+ if ( tails ) {
+ watch_set_pixel(0, 19);
+ watch_set_pixel(2, 19);
+ }
+ break;
+ case 15:
+ if ( heads ) {
+ watch_clear_pixel(2, 23);
+ watch_clear_pixel(0, 23);
+ watch_set_pixel(2, 0);
+ watch_set_pixel(1, 0);
+ }
+ if ( tails ) {
+ watch_clear_pixel(0, 18);
+ watch_clear_pixel(2, 18);
+ }
+ break;
+ case 16:
+ if ( heads ) {
+ watch_set_pixel(2, 1);
+ watch_set_pixel(0, 0);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 18);
+ }
+ break;
+ case 17:
+ if ( heads ) {
+ watch_set_pixel(2, 10);
+ watch_set_pixel(0, 1);
+ }
+ if ( tails ) {
+ watch_clear_pixel(0, 19);
+ watch_clear_pixel(2, 19);
+ watch_set_pixel(1, 17);
+ watch_set_pixel(0, 20);
+ }
+ break;
+ case 18:
+ if ( heads ) {
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(1, 0);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 20);
+ watch_set_pixel(0, 21);
+ }
+ break;
+ case 19:
+ if ( heads ) {
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(0, 0);
+ }
+ if ( tails ) {
+ watch_set_pixel(1, 21);
+ watch_set_pixel(2, 21);
+ }
+ break;
+ case 20:
+ if ( heads ) {
+ watch_set_pixel(2, 1);
+ watch_set_pixel(0, 0);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 20);
+ }
+ break;
+ case 21:
+ if ( heads ) {
+ watch_set_pixel(2, 0);
+ watch_set_pixel(1, 0);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(0, 21);
+ }
+ break;
+ case 22:
+ if ( heads ) {
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(0, 1);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 21);
+ watch_set_pixel(1, 22);
+ watch_set_pixel(2, 22);
+ }
+ break;
+ case 23:
+ if ( heads ) {
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(0, 0);
+ }
+ if ( tails ) {
+ watch_set_pixel(0, 22);
+ }
+ break;
+ case 24:
+ if ( heads ) {
+ watch_set_pixel(2, 23);
+ watch_set_pixel(0, 23);
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(1, 0);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 23);
+ watch_set_pixel(0, 23);
+ }
+ break;
+ case 25:
+ if ( heads ) {
+ watch_set_pixel(0, 22);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 22);
+ watch_clear_pixel(2, 22);
+ }
+ break;
+ case 26:
+ if ( heads ) {
+ watch_set_pixel(1, 22);
+ watch_set_pixel(2, 22);
+ }
+ if ( tails ) {
+ watch_clear_pixel(0, 22);
+ }
+ break;
+ case 27:
+ if ( heads ) {
+ watch_clear_pixel(2, 23);
+ watch_clear_pixel(0, 23);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 23);
+ watch_clear_pixel(0, 23);
+ watch_set_pixel(2, 0);
+ watch_set_pixel(1, 0);
+ }
+ break;
+ case 28:
+ if ( heads ) {
+ watch_clear_pixel(0, 22);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 1);
+ watch_set_pixel(0, 0);
+ }
+ break;
+ case 29:
+ if ( heads ) {
+ watch_set_pixel(1, 21);
+ watch_set_pixel(2, 21);
+ watch_clear_pixel(1, 22);
+ watch_clear_pixel(2, 22);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 10);
+ watch_set_pixel(0, 1);
+ }
+ break;
+ case 30:
+ if ( heads ) {
+ watch_set_pixel(2, 20);
+ watch_set_pixel(0, 21);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 0);
+ watch_clear_pixel(2, 0);
+ }
+ break;
+ case 31:
+ if ( heads ) {
+ watch_set_pixel(1, 17);
+ watch_set_pixel(0, 20);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(0, 0);
+ }
+ break;
+ case 32:
+ if ( heads ) {
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 21);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(0, 1);
+ watch_set_pixel(0, 2);
+ watch_set_pixel(1, 2);
+ }
+ break;
+ case 33:
+ if ( heads ) {
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(0, 21);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 2);
+ watch_set_pixel(0, 3);
+ }
+ break;
+ case 34:
+ if ( heads ) {
+ watch_set_pixel(0, 19);
+ watch_set_pixel(2, 19);
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 20);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 3);
+ watch_set_pixel(0, 4);
+ }
+ break;
+ case 35:
+ if ( heads ) {
+ watch_set_pixel(1, 18);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 2);
+ watch_clear_pixel(0, 2);
+ }
+ break;
+ case 36:
+ if ( heads ) {
+ watch_set_pixel(0, 18);
+ watch_set_pixel(2, 18);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 2);
+ watch_clear_pixel(0, 3);
+ }
+ break;
+ case 37:
+ if ( heads ) {
+ watch_clear_pixel(0, 19);
+ watch_clear_pixel(2, 19);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 3);
+ watch_clear_pixel(0, 4);
+ watch_set_pixel(1, 4);
+ watch_set_pixel(0, 5);
+ }
+ break;
+ case 38:
+ if ( heads ) {
+ watch_clear_pixel(1, 18);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 4);
+ watch_set_pixel(0, 6);
+ }
+ break;
+ case 39:
+ if ( heads ) {
+ watch_clear_pixel(0, 18);
+ watch_clear_pixel(2, 18);
+ }
+ if ( tails ) {
+ watch_set_pixel(1, 6);
+ watch_set_pixel(2, 5);
+ }
+ state->animate = false;
+ state->animation = 0;
+ movement_request_tick_frequency(1);
+ }
+}
+
+// DICE FUNCTIONS /////////////////////////////////////////////////////////////
+
+/** @brief rolls a dice
+ */
+uint8_t roll_dice(uint8_t sides) {
+ uint8_t bits_needed = 0;
+ uint8_t temp_sides = sides - 1;
+ uint8_t result = 0;
+ while (temp_sides > 0) {
+ bits_needed++; // how many bits do we need to represent this number?
+ temp_sides >>= 1; // Shift right to check the next bit
+ }
+ do {
+ result = 0;
+ for (int i = 0; i < bits_needed; i++) {
+ result <<= 1; // Shift left to make room for the next bit
+ result |= divine_bit(); // Add the next bit to the result
+ }
+ } while ( result > sides -1 );
+ return result + 1; // Add 1 to convert the range from 0 to sides-1 to 1 to sides
+}
+
+/** @brief roll multiple dice and print a char array for displaying them
+ */
+static void _roll_dice_multiple(char* result, uint8_t* dice, uint8_t num_dice) {
+ // initialize the result array to all spaces
+ memset(result, ' ', 6);
+
+ // roll the dice and write the result to the result array
+ for (uint8_t i = 0; i < num_dice-1; i++) {
+ uint8_t dice_result = dice[i];
+ uint8_t tens_digit = dice_result / 10;
+ uint8_t ones_digit = dice_result % 10;
+ result[(i * 2)] = tens_digit == 0 ? ' ' : (char)('0' + tens_digit);
+ result[(i * 2) + 1] = (char)('0' + ones_digit);
+ }
+}
+
+/** @brief dice animation
+ */
+static void _dice_animation(toss_up_state_t *state) {
+ watch_display_string(" ", 4);
+ for (uint8_t i = 0; i < state->dice_num; i++) {
+ watch_display_string("0",i*2 + 5);
+ }
+ movement_request_tick_frequency(16);
+ switch ( state->animation ) {
+ case 0:
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 0);
+ watch_clear_pixel(1, 6);
+ break;
+ case 1:
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(1, 0);
+ watch_clear_pixel(0, 6);
+ break;
+ case 2:
+ watch_clear_pixel(2, 21);
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(0, 5);
+ break;
+ case 3:
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(1, 4);
+ break;
+ case 4:
+ watch_clear_pixel(0, 21);
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(2, 4);
+ break;
+ case 5:
+ watch_clear_pixel(0, 20);
+ watch_clear_pixel(0, 1);
+ watch_clear_pixel(2, 5);
+ break;
+ case 6:
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 0);
+ watch_clear_pixel(1, 6);
+ break;
+ case 7:
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(1, 0);
+ watch_clear_pixel(0, 6);
+ break;
+ case 8:
+ watch_clear_pixel(2, 21);
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(0, 5);
+ break;
+ case 9:
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(1, 4);
+ break;
+ case 10:
+ watch_clear_pixel(0, 21);
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(2, 4);
+ break;
+ case 11:
+ watch_clear_pixel(0, 20);
+ watch_clear_pixel(0, 1);
+ watch_clear_pixel(2, 5);
+ state->animate = false;
+ state->animation = 0;
+ movement_request_tick_frequency(1);
+ }
+} \ No newline at end of file
diff --git a/movement/watch_faces/complication/toss_up_face.h b/movement/watch_faces/complication/toss_up_face.h
new file mode 100644
index 00000000..ca6136a7
--- /dev/null
+++ b/movement/watch_faces/complication/toss_up_face.h
@@ -0,0 +1,112 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TOSS_UP_FACE_H_
+#define TOSS_UP_FACE_H_
+
+#include "movement.h"
+
+/*
+ * TOSS UP FACE
+ * ============
+ *
+ * Playful watch face for games of chance or divination using coins or dice.
+ *
+ * LIGHT switches between Coins and Dice mode
+ *
+ * COINS
+ * =====
+ *
+ * ALARM tosses a coin. If it lands on heads it gets sorted to the left side of the
+ * display, if it lands on tails then sorted to the right side.
+ *
+ * LONG PRESSING ALARM adds up to 5 more coins to the toss for more nuance in the decision
+ * making (e.g. three heads vs two tails could be read as "yes, but with serious doubts").
+ *
+ * LONG PRESSING LIGHT flips through additional style for the coins from the default Ө/O
+ * to H/T (heads/tails), Y/N (yes/no), E/Ǝ, C/Ↄ
+ *
+ * LONG PRESSING ALARM on the "Coins" title page resets to one coin.
+ * LONG PRESSING LIGHT on the "Coins" title page resets the style to Ө/O
+ *
+ * DICE
+ * ====
+ *
+ * ALARM rolls a six sided dice.
+ *
+ * LONG PRESSING ALARM adds up to 2 more dice to the roll.
+ *
+ * LONG PRESSING LIGHT flips through other available polyhedral dice types with less or more
+ * than the default 6 sides. The options are D2, D4, D6, D8, D10, D12, D20, D24, D30, D32, D36,
+ * D48, and a hypothetical D99.
+ *
+ * When more than one dice is used for a roll this changes only the last added dice. (see Note
+ * below)
+ *
+ * LONG PRESSING ALARM on the "Dice" title page resets to one dice.
+ * LONG PRESSING LIGHT on the "Dice" title page resets the dice to D6.
+ *
+ * Please Note: If you need let's say a D8, D12, and D20 for your rolls then the procedure to
+ * set this up would be as follows: from the default screen where you can roll the one D6 dice
+ * you would LONG PRESS LIGHT a few times to change the D6 to a D8, then LONG PRESS ALARM to add
+ * a second dice, LONG PRESS LIGHT again until the second dice changes to D12, then LONG PRESS
+ * ALARM to add the third dice and LONG PRESS LIGHT again a few times until it becomes a D20.
+ *
+ */
+
+typedef struct {
+ // Anything you need to keep track of, put it here!
+ uint32_t entropy;
+ uint8_t mode : 4; // 1 coin, 2 coins, 3 coins, 4 coins, dice, iching, geomnc
+ bool setup;
+ bool coins[6];
+ uint8_t coin_num : 3;
+ char coin_style[2];
+ uint8_t coinface : 3;
+ uint8_t dice[3];
+ uint8_t dice_num : 2;
+ uint8_t dd : 6;
+ uint8_t dice_sides[3];
+ uint8_t animation;
+ bool animate;
+} toss_up_state_t;
+
+uint32_t get_true_entropy(void);
+uint8_t divine_bit(void);
+uint8_t roll_dice(uint8_t sides);
+void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void toss_up_face_activate(movement_settings_t *settings, void *context);
+bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void toss_up_face_resign(movement_settings_t *settings, void *context);
+
+#define toss_up_face ((const watch_face_t){ \
+ toss_up_face_setup, \
+ toss_up_face_activate, \
+ toss_up_face_loop, \
+ toss_up_face_resign, \
+ NULL, \
+})
+
+#endif // TOSS_UP_FACE_H_
+